Compare commits
26 Commits
zanneth/pe
...
1ef4f8c3b4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1ef4f8c3b4 | ||
|
|
0c1be5cf8c | ||
|
|
1ef85973a4 | ||
|
|
f63f0fc36a | ||
|
|
e5e2d5a411 | ||
|
|
a17d821b0e | ||
| 1cc464fa9b | |||
| 4c4e79e384 | |||
| ae57353e8f | |||
| fdb4840912 | |||
| 6f4ae177da | |||
| 9b9648d033 | |||
| 4ffc138909 | |||
|
|
08354c8905 | ||
| dc8ab1a0a7 | |||
|
|
4e19b2a1b0 | ||
| 803f07a1b7 | |||
| fd17e511dc | |||
| 29030c573c | |||
| 3fc555844b | |||
| 182ebd14b3 | |||
| da10b32dfa | |||
|
|
4aa310c008 | ||
| 8c3232c788 | |||
| f3eed964f0 | |||
|
|
896a67ca8a |
55
.gitea/workflows/build-pdf.yml
Normal file
@@ -0,0 +1,55 @@
|
||||
name: Build PDF
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main, master ]
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
pdf:
|
||||
name: make pdf
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Go (from go.mod)
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
cache: true
|
||||
|
||||
- name: Install Chrome and tools (no snap)
|
||||
run: |
|
||||
set -euxo pipefail
|
||||
if command -v sudo >/dev/null 2>&1; then SUDO=sudo; else SUDO=; fi
|
||||
$SUDO apt-get update
|
||||
# Base tools and fonts
|
||||
DEBIAN_FRONTEND=noninteractive $SUDO apt-get install -y --no-install-recommends \
|
||||
ca-certificates gnupg make fonts-liberation
|
||||
|
||||
# Add Google's official Chrome APT repo (avoids Ubuntu's snap-only chromium)
|
||||
$SUDO install -d -m 0755 /etc/apt/keyrings
|
||||
curl -fsSL https://dl.google.com/linux/linux_signing_key.pub | $SUDO gpg --dearmor -o /etc/apt/keyrings/google-linux-signing-keyring.gpg
|
||||
echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/google-linux-signing-keyring.gpg] https://dl.google.com/linux/chrome/deb/ stable main" | \
|
||||
$SUDO tee /etc/apt/sources.list.d/google-chrome.list >/dev/null
|
||||
$SUDO apt-get update
|
||||
DEBIAN_FRONTEND=noninteractive $SUDO apt-get install -y --no-install-recommends google-chrome-stable
|
||||
|
||||
# Clean up apt lists to keep image lean
|
||||
$SUDO rm -rf /var/lib/apt/lists/*
|
||||
|
||||
- name: Build PDF
|
||||
env:
|
||||
# Ensure our tools pick Chrome first if multiple are present
|
||||
CHROME_PATH: /usr/bin/google-chrome-stable
|
||||
run: make pdf
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: output-pdf
|
||||
path: _dist/output.pdf
|
||||
if-no-files-found: error
|
||||
4
Makefile
@@ -34,8 +34,8 @@ deps:
|
||||
|
||||
.PHONY: pdf
|
||||
pdf: build deps
|
||||
@echo "Generating PDF with headless Chrome..."
|
||||
@$(GO) run ./cmd/pdf --in $(OUT_DIR)/print.html --out $(PDF)
|
||||
@echo "Generating PDF by rendering each page and merging..."
|
||||
@$(GO) run ./cmd/pdfbook --pages $(OUT_DIR) --order pages.yaml --out $(PDF)
|
||||
|
||||
.PHONY: pdf-2up
|
||||
pdf-2up: build deps
|
||||
|
||||
@@ -3,6 +3,11 @@
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Heading Now";
|
||||
src: url("../font/HeadingNow-95Medium.otf");
|
||||
}
|
||||
|
||||
/* Base */
|
||||
html, body {
|
||||
height: 100%;
|
||||
@@ -20,6 +25,39 @@ html, body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.interlude {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
color: white;
|
||||
padding: 4rem;
|
||||
z-index: 0; /* ensure stacking context */
|
||||
}
|
||||
|
||||
.interlude::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
filter: grayscale(20%) brightness(0.7);
|
||||
z-index: -2;
|
||||
}
|
||||
|
||||
.interlude::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
/*background: rgba(128, 0, 128, 0.3); /* purple tint */ */
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.interlude-caption {
|
||||
color: rgba(225, 225, 225, 1.0);
|
||||
font-family: "Heading Now";
|
||||
line-height: 2em;
|
||||
rotate: 5deg;
|
||||
}
|
||||
|
||||
/* Use border-box sizing everywhere to keep dimensions predictable */
|
||||
html { box-sizing: border-box; }
|
||||
*, *::before, *::after { box-sizing: inherit; }
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
#page {
|
||||
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Heading Now";
|
||||
src: url("../font/HeadingNow-95Medium.otf");
|
||||
}
|
||||
|
||||
.ytmnd-1 {
|
||||
color: #fff;
|
||||
text-shadow:
|
||||
0px 10px 0 rgb(209, 1, 209),
|
||||
0px 20px 0 black;
|
||||
}
|
||||
|
||||
.ytmnd-2 {
|
||||
color: #fff;
|
||||
text-shadow:
|
||||
0px 6px 0 rgb(209, 1, 209);
|
||||
}
|
||||
|
||||
BIN
assets/font/HeadingNow-95Medium.otf
Normal file
BIN
assets/font/aerosoldier.otf
Normal file
BIN
assets/img/broken-screen.jpg
Normal file
|
After Width: | Height: | Size: 3.0 MiB |
BIN
assets/img/circuit-board.jpg
Normal file
|
After Width: | Height: | Size: 5.9 MiB |
BIN
assets/img/computer_2_cool-3.png
Normal file
|
After Width: | Height: | Size: 687 B |
BIN
assets/img/cover.jpg
Normal file
|
After Width: | Height: | Size: 1.3 MiB |
BIN
assets/img/crashman/crashman-1.png
Normal file
|
After Width: | Height: | Size: 247 KiB |
BIN
assets/img/crashman/crashman-2.png
Normal file
|
After Width: | Height: | Size: 227 KiB |
BIN
assets/img/crashman/crashman-3.png
Normal file
|
After Width: | Height: | Size: 227 KiB |
BIN
assets/img/crashman/crashman-bg.png
Normal file
|
After Width: | Height: | Size: 3.0 MiB |
BIN
assets/img/fashion.jpg
Normal file
|
After Width: | Height: | Size: 1.3 MiB |
BIN
assets/img/free-dirt.jpg
Normal file
|
After Width: | Height: | Size: 5.8 MiB |
BIN
assets/img/hack-the-planet.jpg
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
assets/img/javaguy.jpg
Normal file
|
After Width: | Height: | Size: 993 KiB |
BIN
assets/img/mr-nickel.jpg
Normal file
|
After Width: | Height: | Size: 2.8 MiB |
BIN
assets/img/streetgeek-dogtags.jpeg
Normal file
|
After Width: | Height: | Size: 5.9 MiB |
BIN
assets/img/streetgeek-evangelion.png
Normal file
|
After Width: | Height: | Size: 1.9 MiB |
BIN
assets/img/streetgeek-java.png
Normal file
|
After Width: | Height: | Size: 2.0 MiB |
BIN
assets/img/vrankle.png
Normal file
|
After Width: | Height: | Size: 2.0 MiB |
BIN
assets/img/windows-shirt.jpg
Normal file
|
After Width: | Height: | Size: 2.5 MiB |
BIN
assets/img/wtf.png
Normal file
|
After Width: | Height: | Size: 2.5 MiB |
@@ -160,21 +160,12 @@ func main() {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Build print.html containing all pages one after another for PDF
|
||||
printTplPath := filepath.Join(templatesDir, "print.gohtml")
|
||||
printTpl := template.Must(template.New("print").Parse(mustReadFile(printTplPath)))
|
||||
// Prepare pages for aggregate printing (2-up, etc.)
|
||||
var printPages []PrintPage
|
||||
for _, pagePath := range pageFiles {
|
||||
content := strings.TrimSpace(mustReadFile(pagePath))
|
||||
printPages = append(printPages, PrintPage{Content: template.HTML(content)})
|
||||
}
|
||||
var outPrint bytes.Buffer
|
||||
if err := printTpl.Execute(&outPrint, map[string]any{"Pages": printPages, "PageSizeAttr": pageSizeStyleAttr(config.PageWidthIn, config.PageHeightIn)}); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := writeFile(filepath.Join(outDir, "print.html"), outPrint.Bytes()); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Build 2-up print HTML (letter pages with two half-letter pages side by side)
|
||||
var sheets []TwoUpSheet
|
||||
|
||||
158
cmd/pdfbook/main.go
Normal file
@@ -0,0 +1,158 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"smartbar/internal/config"
|
||||
|
||||
"github.com/chromedp/cdproto/emulation"
|
||||
"github.com/chromedp/cdproto/page"
|
||||
"github.com/chromedp/chromedp"
|
||||
pdfapi "github.com/pdfcpu/pdfcpu/pkg/api"
|
||||
)
|
||||
|
||||
func must(err error) {
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func parseYAMLListOfStrings(src string) ([]string, error) {
|
||||
var out []string
|
||||
for _, line := range strings.Split(src, "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" || strings.HasPrefix(line, "#") {
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(line, "-") {
|
||||
item := strings.TrimSpace(strings.TrimPrefix(line, "-"))
|
||||
if item != "" {
|
||||
out = append(out, item)
|
||||
}
|
||||
} else {
|
||||
return nil, fmt.Errorf("invalid yaml line (expect '- item'): %s", line)
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func findChromeExec() (string, error) {
|
||||
if v := os.Getenv("CHROME_PATH"); v != "" {
|
||||
return v, nil
|
||||
}
|
||||
candidates := []string{"google-chrome-stable", "google-chrome", "chromium-browser", "chromium", "chrome"}
|
||||
for _, name := range candidates {
|
||||
if p, err := exec.LookPath(name); err == nil {
|
||||
return p, nil
|
||||
}
|
||||
}
|
||||
// macOS common app bundle paths
|
||||
macPaths := []string{
|
||||
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
||||
"/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary",
|
||||
"/Applications/Chromium.app/Contents/MacOS/Chromium",
|
||||
}
|
||||
for _, p := range macPaths {
|
||||
if _, err := os.Stat(p); err == nil {
|
||||
return p, nil
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("no Chrome/Chromium executable found; set CHROME_PATH or install chromium/google-chrome")
|
||||
}
|
||||
|
||||
func main() {
|
||||
var (
|
||||
pagesDir string
|
||||
orderPath string
|
||||
outPDF string
|
||||
width float64
|
||||
height float64
|
||||
)
|
||||
flag.StringVar(&pagesDir, "pages", "_dist", "path to compiled pages directory (required)")
|
||||
flag.StringVar(&orderPath, "order", "pages.yaml", "path to YAML file listing page order (required)")
|
||||
flag.StringVar(&outPDF, "out", "_dist/output.pdf", "output PDF path (required)")
|
||||
flag.Float64Var(&width, "w", config.PageWidthIn, "page width in inches")
|
||||
flag.Float64Var(&height, "h", config.PageHeightIn, "page height in inches")
|
||||
flag.Parse()
|
||||
|
||||
if pagesDir == "" || orderPath == "" || outPDF == "" {
|
||||
log.Fatal("--pages, --order and --out are required")
|
||||
}
|
||||
|
||||
// Load order
|
||||
data, err := os.ReadFile(orderPath)
|
||||
must(err)
|
||||
|
||||
ordered, err := parseYAMLListOfStrings(string(data))
|
||||
must(err)
|
||||
|
||||
// Resolve Chrome
|
||||
chromeExec, err := findChromeExec()
|
||||
must(err)
|
||||
|
||||
opts := append(chromedp.DefaultExecAllocatorOptions[:], chromedp.ExecPath(chromeExec), chromedp.Flag("headless", true), chromedp.Flag("disable-gpu", true), chromedp.Flag("hide-scrollbars", true))
|
||||
allocCtx, cancel := chromedp.NewExecAllocator(context.Background(), opts...)
|
||||
defer cancel()
|
||||
|
||||
ctx, cancel := chromedp.NewContext(allocCtx)
|
||||
defer cancel()
|
||||
|
||||
// Temp dir for individual PDFs
|
||||
tmpDir, err := os.MkdirTemp("", "smartbar_pdfs_*")
|
||||
must(err)
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
var partPDFs []string
|
||||
for _, rel := range ordered {
|
||||
abs := filepath.Join(pagesDir, rel)
|
||||
if _, err := os.Stat(abs); err != nil {
|
||||
log.Printf("skip missing page: %s", rel)
|
||||
continue
|
||||
}
|
||||
absHTML, _ := filepath.Abs(abs)
|
||||
url := "file://" + absHTML
|
||||
|
||||
outPath := filepath.Join(tmpDir, strings.TrimSuffix(filepath.Base(rel), filepath.Ext(rel)) + ".pdf")
|
||||
// Render one page PDF via printToPDF
|
||||
err := chromedp.Run(ctx, chromedp.Tasks{
|
||||
chromedp.Navigate(url),
|
||||
chromedp.ActionFunc(func(ctx context.Context) error {
|
||||
if err := emulation.SetEmulatedMedia().WithMedia("print").Do(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
params := page.PrintToPDF().
|
||||
WithPaperWidth(width).
|
||||
WithPaperHeight(height).
|
||||
WithMarginTop(0).WithMarginBottom(0).WithMarginLeft(0).WithMarginRight(0).
|
||||
WithPrintBackground(true)
|
||||
buf, _, err := params.Do(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(outPath, buf, 0o644)
|
||||
}),
|
||||
})
|
||||
must(err)
|
||||
partPDFs = append(partPDFs, outPath)
|
||||
}
|
||||
|
||||
if len(partPDFs) == 0 {
|
||||
log.Fatal("no pages produced; check order file")
|
||||
}
|
||||
|
||||
// Merge PDFs into single output
|
||||
must(os.MkdirAll(filepath.Dir(outPDF), 0o755))
|
||||
|
||||
// pdfcpu expects []string in order and writes outPDF
|
||||
must(pdfapi.MergeCreateFile(partPDFs, outPDF, false, nil))
|
||||
fmt.Printf("Wrote %s (%d pages)\n", outPDF, len(partPDFs))
|
||||
}
|
||||
|
||||
|
||||
17
go.mod
@@ -1,11 +1,14 @@
|
||||
module smartbar
|
||||
|
||||
go 1.21
|
||||
go 1.23.0
|
||||
|
||||
toolchain go1.24.2
|
||||
|
||||
require (
|
||||
github.com/chromedp/cdproto v0.0.0-20240202021202-6d0b6a386732
|
||||
github.com/chromedp/chromedp v0.9.5
|
||||
github.com/fsnotify/fsnotify v1.9.0
|
||||
github.com/pdfcpu/pdfcpu v0.11.0
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -13,7 +16,17 @@ require (
|
||||
github.com/gobwas/httphead v0.1.0 // indirect
|
||||
github.com/gobwas/pool v0.2.1 // indirect
|
||||
github.com/gobwas/ws v1.3.2 // indirect
|
||||
github.com/hhrutter/lzw v1.0.0 // indirect
|
||||
github.com/hhrutter/pkcs7 v0.2.0 // indirect
|
||||
github.com/hhrutter/tiff v1.0.2 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
golang.org/x/sys v0.16.0 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
golang.org/x/crypto v0.38.0 // indirect
|
||||
golang.org/x/image v0.27.0 // indirect
|
||||
golang.org/x/sys v0.33.0 // indirect
|
||||
golang.org/x/text v0.25.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
)
|
||||
|
||||
28
go.sum
@@ -12,14 +12,40 @@ github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
|
||||
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||
github.com/gobwas/ws v1.3.2 h1:zlnbNHxumkRvfPWgfXu8RBwyNR1x8wh9cf5PTOCqs9Q=
|
||||
github.com/gobwas/ws v1.3.2/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
|
||||
github.com/hhrutter/lzw v1.0.0 h1:laL89Llp86W3rRs83LvKbwYRx6INE8gDn0XNb1oXtm0=
|
||||
github.com/hhrutter/lzw v1.0.0/go.mod h1:2HC6DJSn/n6iAZfgM3Pg+cP1KxeWc3ezG8bBqW5+WEo=
|
||||
github.com/hhrutter/pkcs7 v0.2.0 h1:i4HN2XMbGQpZRnKBLsUwO3dSckzgX142TNqY/KfXg+I=
|
||||
github.com/hhrutter/pkcs7 v0.2.0/go.mod h1:aEzKz0+ZAlz7YaEMY47jDHL14hVWD6iXt0AgqgAvWgE=
|
||||
github.com/hhrutter/tiff v1.0.2 h1:7H3FQQpKu/i5WaSChoD1nnJbGx4MxU5TlNqqpxw55z8=
|
||||
github.com/hhrutter/tiff v1.0.2/go.mod h1:pcOeuK5loFUE7Y/WnzGw20YxUdnqjY1P0Jlcieb/cCw=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80 h1:6Yzfa6GP0rIo/kULo2bwGEkFvCePZ3qHDDTC3/J9Swo=
|
||||
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde h1:x0TT0RDC7UhAVbbWWBzr41ElhJx5tXPWkIHA2HWPRuw=
|
||||
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
|
||||
github.com/pdfcpu/pdfcpu v0.11.0 h1:mL18Y3hSHzSezmnrzA21TqlayBOXuAx7BUzzZyroLGM=
|
||||
github.com/pdfcpu/pdfcpu v0.11.0/go.mod h1:F1ca4GIVFdPtmgvIdvXAycAm88noyNxZwzr9CpTy+Mw=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
|
||||
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
|
||||
golang.org/x/image v0.27.0 h1:C8gA4oWU/tKkdCfYT6T2u4faJu3MeNS5O8UPWlPF61w=
|
||||
golang.org/x/image v0.27.0/go.mod h1:xbdrClrAUway1MUTEZDq9mz/UpRwYAkFFNUslZtcB+g=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
|
||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
|
||||
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
|
||||
18
pages.yaml
@@ -1,11 +1,23 @@
|
||||
# locations in pages/
|
||||
|
||||
- cover.html
|
||||
- bouba.html
|
||||
- hack-the-planet.html
|
||||
- wtf.html
|
||||
- ai-disclosure.html
|
||||
- drivingmissmuni.html
|
||||
- crashman.html
|
||||
- vranklevictim.html
|
||||
- pearlstreetcafe.html
|
||||
- gtkapplang-1.html
|
||||
- gtkapplang-2.html
|
||||
- gtkapplang-3.html
|
||||
- gtkapplang-4.html
|
||||
- drivingmissmuni.html
|
||||
- pearlstreetcafe.html
|
||||
- broken-screen.html
|
||||
- fashion.html
|
||||
- streetgeek-1.html
|
||||
- streetgeek-2.html
|
||||
- windows-shirt.html
|
||||
- free-dirt.html
|
||||
- mr-nickel.html
|
||||
- javaguy.html
|
||||
|
||||
|
||||
129
pages/ai-disclosure.html
Normal file
@@ -0,0 +1,129 @@
|
||||
<style>
|
||||
#ai-disclosure-page {
|
||||
background: url("assets/img/circuit-board.jpg") no-repeat center center;
|
||||
background-size: cover;
|
||||
|
||||
font: 8.75pt/1.40 Tahoma, sans-serif;
|
||||
padding: 10px;
|
||||
hyphens: auto;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-family: "Heading Now";
|
||||
font-size: 23px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-family: "Heading Now";
|
||||
font-size: 24px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
p {
|
||||
font-family: Helvetica, sans-serif;
|
||||
font-size: 12.75pt;
|
||||
}
|
||||
|
||||
/* Windows 98 styles — green/teal variant */
|
||||
div#overlay-window {
|
||||
background: #d4d0c8;
|
||||
|
||||
/* Vertically center the window in the page */
|
||||
position: relative;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
h1#titlebar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0;
|
||||
padding: 4px 8px;
|
||||
min-height: 22px;
|
||||
font-weight: bold;
|
||||
color: #ffffff;
|
||||
background: linear-gradient(90deg, #004000 0%, #10a050 50%, #004000 100%);
|
||||
}
|
||||
|
||||
h1#titlebar img {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
image-rendering: pixelated;
|
||||
}
|
||||
|
||||
h1#titlebar span {
|
||||
margin: 0 8px;
|
||||
}
|
||||
|
||||
div#content {
|
||||
background: #f0f0e8c0;
|
||||
padding: 10px;
|
||||
color: #000000;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
span#dumb {
|
||||
font-family: "Heading Now";
|
||||
font-size: large;
|
||||
font-stretch: condensed;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
span.stuff {
|
||||
font-weight: bold;
|
||||
font-style: italic;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="ai-disclosure-page" class="page-base">
|
||||
<div id="overlay-window">
|
||||
<h1 id="titlebar">
|
||||
<img src="assets/img/computer_2_cool-3.png" alt="">
|
||||
<span>AI Disclosure</span>
|
||||
<img src="assets/img/computer_2_cool-3.png" alt="">
|
||||
</h1>
|
||||
<div id="content">
|
||||
<h2>A DISCLOSURE ABOUT AI USAGE</h2>
|
||||
|
||||
<p>
|
||||
Seems like everything is slop now days. And I'm not just talking about stuff
|
||||
generated by the <span class="stuff">"Large Language Models."</span>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
You, dear reader, deserve better than slop. Therefore, we are proud to declare that
|
||||
nothing in this issue of <strong>SMART BAR</strong> is generated by A.I. We're
|
||||
talking 100% pure homegrown human creativity, the best this side of the Bay Area has
|
||||
to offer.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
That's not to say that we didn't try. Writing good quality content is hard, which is
|
||||
why we tasked our <strong>Tech Wizard</strong> <em>P. Michael Cho</em> with employing the
|
||||
newest, hottest transformers to create hard-hitting philosophical pieces for this
|
||||
issue.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Unfortunately, <span id="dumb">WE COULDN'T FIGURE IT OUT</span>.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Despite paying a hundred bucks for <span class="stuff">G.P.T. Pro Max
|
||||
Extreme</span>, getting <span class="stuff">"API Keys,"</span> and doodling a
|
||||
couple of <span class="stuff">shell scripts</span>, we kept getting back <span
|
||||
class="stuff">MUFFIN RECIPES</span> and <span class="stuff">PYTHON
|
||||
PROGRAMS</span>. Needless to say, this is not content that you spent your hard
|
||||
earned cash on to read.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
In any case, we hope you enjoy these words and ideas from a couple of baseline
|
||||
humans.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,9 +0,0 @@
|
||||
<style>
|
||||
#bouba-page {
|
||||
background-color: red;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="bouba-page" class="page-base">
|
||||
This is a test kiki.
|
||||
</div>
|
||||
19
pages/broken-screen.html
Normal file
@@ -0,0 +1,19 @@
|
||||
<style>
|
||||
#broken-screen::before {
|
||||
background-image: url("assets/img/broken-screen.jpg");
|
||||
}
|
||||
|
||||
#broken-screen-caption {
|
||||
position: absolute;
|
||||
bottom: 25%;
|
||||
right: 15%;
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="broken-screen" class="page-base interlude">
|
||||
<div class="interlude-caption" id="broken-screen-caption">
|
||||
have you tried<br/>
|
||||
turning it off and on again?
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,9 +1,49 @@
|
||||
<style>
|
||||
#cover-page {
|
||||
background-color: green;
|
||||
position: relative;
|
||||
background:
|
||||
linear-gradient(rgba(0,0,0,0.35), rgba(0,0,0,0.35)),
|
||||
url("assets/img/cover.jpg") no-repeat center center;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
#logo {
|
||||
position: absolute;
|
||||
top: 29px;
|
||||
left: 8px;
|
||||
|
||||
transform: scaleY(1.85);
|
||||
|
||||
font-family: "Heading Now";
|
||||
font-size: 68px;
|
||||
|
||||
color: #fff;
|
||||
text-shadow:
|
||||
0px 10px 0 rgb(209, 1, 209),
|
||||
0px 20px 0 black;
|
||||
}
|
||||
|
||||
#edition {
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
bottom: 20px;
|
||||
|
||||
transform: scaleY(0.8);
|
||||
|
||||
font-family: "Heading Now";
|
||||
font-size: 32px;
|
||||
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="cover-page" class="page-base">
|
||||
welcome to smart bar
|
||||
|
||||
<div id="logo" class="ytmnd-1">smart bar</div>
|
||||
|
||||
<div id="edition" class="ytmnd-2">
|
||||
<div>sf cyberculture zine</div>
|
||||
<div>issue 1, 2025</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
89
pages/crashman.html
Normal file
@@ -0,0 +1,89 @@
|
||||
<style>
|
||||
#crashman-page {
|
||||
position: relative;
|
||||
background: url("assets/img/crashman/crashman-bg.png") no-repeat center center;
|
||||
background-size: cover;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#snips {
|
||||
height: 120px;
|
||||
width: 620px;
|
||||
background-color: rgb(209, 1, 209);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 5px;
|
||||
|
||||
position: absolute;
|
||||
bottom: 30px;
|
||||
left: -50px;
|
||||
transform: rotate(-5deg);
|
||||
}
|
||||
|
||||
#snips img {
|
||||
flex: 1 1 0;
|
||||
width: auto;
|
||||
object-fit: contain;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
#title {
|
||||
position: absolute;
|
||||
top: 350px;
|
||||
right: 8px;
|
||||
|
||||
font-family: "Heading Now";
|
||||
font-size: 48px;
|
||||
|
||||
color: #fff;
|
||||
text-shadow:
|
||||
0px 10px 0 rgb(209, 1, 209),
|
||||
0px 20px 0 black;
|
||||
}
|
||||
|
||||
#text {
|
||||
position: absolute;
|
||||
top: 390px;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
background-color: rgba(62, 6, 62, 0.704);
|
||||
font: 11.75pt/1.40 Tahoma, sans-serif;
|
||||
padding: 24px;
|
||||
color: #fff;
|
||||
line-height: 1.9em;
|
||||
}
|
||||
|
||||
.stretch {
|
||||
font-weight: 800;
|
||||
transform: scaleX(2.1);
|
||||
transform-origin: left;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<div id="crashman-page" class="page-base">
|
||||
|
||||
<div id="text">
|
||||
<p>
|
||||
<b>Rusty Haight</b> is the human crash-test dummy. As director of the San Diego-based Collision Safety Institute,
|
||||
Rusty has experienced more than 950 violent vehicle crash tests at speeds of up to 54 mph,
|
||||
and taken <span class="stretch">140 air bags to the face.</span>
|
||||
</p>
|
||||
<p>
|
||||
Haight has contributed several publications on the topic of automobile safety
|
||||
in <b>Collision Magazine</b> such as <em>Hyundai and Kia Crash Data: the Indispensable Compendium.</em>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div id="snips">
|
||||
<img src="assets/img/crashman/crashman-1.png" />
|
||||
<img src="assets/img/crashman/crashman-2.png" />
|
||||
<img src="assets/img/crashman/crashman-3.png" />
|
||||
</div>
|
||||
|
||||
<div id="title" class="ytmnd-1">crash man</div>
|
||||
|
||||
</div>
|
||||
9
pages/fashion.html
Normal file
@@ -0,0 +1,9 @@
|
||||
<style>
|
||||
#fashion {
|
||||
background-image: url("assets/img/fashion.jpg");
|
||||
background-size: contain;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="fashion" class="page-base">
|
||||
</div>
|
||||
15
pages/free-dirt.html
Normal file
@@ -0,0 +1,15 @@
|
||||
<style>
|
||||
#free-dirt::before {
|
||||
background-image: url("assets/img/free-dirt.jpg");
|
||||
}
|
||||
|
||||
#free-dirt-caption {
|
||||
position: absolute;
|
||||
bottom: 25%;
|
||||
right: 25%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="free-dirt" class="page-base interlude">
|
||||
<div class="interlude-caption" id="free-dirt-caption">thanks...</div>
|
||||
</div>
|
||||
10
pages/hack-the-planet.html
Normal file
@@ -0,0 +1,10 @@
|
||||
<style>
|
||||
#hack-the-planet-page {
|
||||
background: url("assets/img/hack-the-planet.jpg") no-repeat center center;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<div id="hack-the-planet-page" class="page-base">
|
||||
</div>
|
||||
18
pages/javaguy.html
Normal file
@@ -0,0 +1,18 @@
|
||||
<style>
|
||||
#javaguy::before {
|
||||
background-image: url("assets/img/javaguy.jpg");
|
||||
}
|
||||
|
||||
#javaguy-caption {
|
||||
position: absolute;
|
||||
top: 5%;
|
||||
left: 5%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="javaguy" class="page-base interlude">
|
||||
<div class="interlude-caption" id="javaguy-caption">
|
||||
i'm living in the real world<br/>
|
||||
and he's living in a <em>virtual machine...</em><br/>
|
||||
</div>
|
||||
</div>
|
||||
19
pages/mr-nickel.html
Normal file
@@ -0,0 +1,19 @@
|
||||
<style>
|
||||
#mr-nickel::before {
|
||||
background-image: url("assets/img/mr-nickel.jpg");
|
||||
}
|
||||
|
||||
#mr-nickel-caption {
|
||||
position: absolute;
|
||||
bottom: 10%;
|
||||
right: 5%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="mr-nickel" class="page-base interlude">
|
||||
<div class="interlude-caption" id="mr-nickel-caption">
|
||||
mr. nickel says<br/>
|
||||
the financial collapse is imminent<br/>
|
||||
and it's all your fault!
|
||||
</div>
|
||||
</div>
|
||||
194
pages/streetgeek-1.html
Normal file
@@ -0,0 +1,194 @@
|
||||
<style>
|
||||
|
||||
@font-face {
|
||||
font-family: "Aerosoldier";
|
||||
src: url("assets/font/aerosoldier.otf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "COMPUTER Robot";
|
||||
src: url("assets/font/computer-robot.ttf");
|
||||
}
|
||||
|
||||
span.computer {
|
||||
font-family: "COMPUTER Robot", monospace;
|
||||
}
|
||||
|
||||
#streetgeek1-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0.4in 0.42in;
|
||||
background-color: white;
|
||||
color: black;
|
||||
font: 8.6pt/1.42 'Times New Roman', serif;
|
||||
}
|
||||
|
||||
#sg1-kicker {
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
font-size: 7pt;
|
||||
font-weight: bold;
|
||||
letter-spacing: 0.22em;
|
||||
text-transform: uppercase;
|
||||
color: rgb(209, 1, 209);
|
||||
margin-bottom: 3pt;
|
||||
}
|
||||
|
||||
#streetgeek1-page h1 {
|
||||
font-family: Aerosoldier, Helvetica, sans-serif;
|
||||
font-size: 38pt;
|
||||
line-height: 0.85;
|
||||
color: black;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#sg1-deck {
|
||||
font-family: Georgia, 'Times New Roman', serif;
|
||||
font-style: italic;
|
||||
font-size: 10pt;
|
||||
color: #222;
|
||||
margin: 5pt 0 3pt 0;
|
||||
}
|
||||
|
||||
#sg1-byline {
|
||||
font-size: 8pt;
|
||||
font-style: italic;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#sg1-rule {
|
||||
border: none;
|
||||
border-top: 1.4pt solid black;
|
||||
margin: 6pt 0 9pt 0;
|
||||
}
|
||||
|
||||
#sg1-body {
|
||||
flex: 1;
|
||||
columns: 2;
|
||||
column-gap: 0.28in;
|
||||
}
|
||||
|
||||
#sg1-body p {
|
||||
color: black;
|
||||
text-indent: 2em;
|
||||
text-align: justify;
|
||||
text-justify: inter-word;
|
||||
hyphens: auto;
|
||||
overflow-wrap: break-word;
|
||||
margin: 0 0 7pt 0;
|
||||
}
|
||||
|
||||
.drop-cap::first-letter {
|
||||
float: left;
|
||||
font-size: 4em;
|
||||
line-height: 0.8;
|
||||
padding-right: 8px;
|
||||
padding-top: 0px;
|
||||
font-weight: bold;
|
||||
font-family: Georgia, serif;
|
||||
}
|
||||
|
||||
.drop-cap {
|
||||
text-indent: 0 !important;
|
||||
}
|
||||
|
||||
#sg1-pullquote {
|
||||
break-inside: avoid;
|
||||
-webkit-column-break-inside: avoid;
|
||||
font-family: Georgia, 'Times New Roman', serif;
|
||||
font-style: italic;
|
||||
font-size: 13.5pt;
|
||||
line-height: 1.25;
|
||||
color: #111;
|
||||
text-indent: 0;
|
||||
border-left: 3pt solid rgb(209, 1, 209);
|
||||
padding: 2pt 0 2pt 10pt;
|
||||
margin: 4pt 0 9pt 0;
|
||||
}
|
||||
|
||||
#sg1-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
border-top: 0.75pt solid black;
|
||||
margin-top: 6pt;
|
||||
padding-top: 4pt;
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
font-size: 6.5pt;
|
||||
letter-spacing: 0.1em;
|
||||
text-transform: uppercase;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<div id="streetgeek1-page" class="page-base">
|
||||
|
||||
<div id="sg1-header">
|
||||
<h1>Street Geek</h1>
|
||||
<p id="sg1-deck">Notes from the front lines of hacker haute couture.</p>
|
||||
<p id="sg1-byline">By <strong>Bram Noidz</strong></p>
|
||||
</div>
|
||||
|
||||
<hr id="sg1-rule" />
|
||||
|
||||
<div id="sg1-body">
|
||||
|
||||
<p class="drop-cap">
|
||||
My day in meatspace wasn't going well. I woke up in the late morning and acquired the day's
|
||||
outfit using my usual clothing drawer LIFO mechanism. This week's permutation of clothing in the
|
||||
drawer left me with a depressingly common combination: black t-shirt with black jeans. I thought
|
||||
for a brief second that I should break out of the LIFO ordering and make a daring attempt at a
|
||||
more arbitrarily stylish outfit. Brief panic attack. Ultimately I decided against it. <i>The
|
||||
purpose of a system is what it does.</i> If I decide for even one day to ignore my LIFO
|
||||
clothing system, then it really is no system at all.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
I hobbled downstairs in my algorithmically chosen outfit and made a cup of coffee. Double
|
||||
espresso with a bit of hot water mixed in. While reading something on my computer, I
|
||||
accidentally spilled about half of the coffee onto my shirt. Luckily for me, the stains were
|
||||
practically invisible on the black t-shirt, and very little ended up on my pants. The only
|
||||
evidence that I had spilled something on myself that morning was the faint coffee smell that
|
||||
lingered after I mopped it up. But who doesn't love the smell of coffee? I actually wouldn't
|
||||
mind a cologne with the scent of the glorious roasted bean imbued into it. Similar to the
|
||||
clickety-clack sound of the mechanical keyboard, the coffee cologne might have the Pavlovian
|
||||
effect of stimulating the electronic productivity of the people around you. Free idea for any of
|
||||
our young entrepreneurial readers out there.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
You might be wondering why I spent more time than usual at this time of the morning thinking
|
||||
about what I'm wearing. Today, I am meeting my friend <span class="computer">ByteLord</span> for lunch at the vegan
|
||||
restaurant <i>Cha-Ya</i> on Valencia. <span class="computer">ByteLord</span> is a one-of-a-kind genius level programmer
|
||||
that I met on an IRC channel one evening. This guy slings code so neat and clean, he puts Linus
|
||||
Torvalds and that guy who made the <i>McMaster-Carr</i> catalog website to shame. I had never
|
||||
met <span class="computer">ByteLord</span> A.F.K. before. Therefore, needless to say, I felt intimidated and eager to
|
||||
please.
|
||||
</p>
|
||||
|
||||
<div id="sg1-pullquote">“The purpose of a system is what it does.”</div>
|
||||
|
||||
<p>
|
||||
I got to <i>Cha-Ya</i> a little bit early and ordered a green tea while waiting for
|
||||
<span class="computer">ByteLord</span> to arrive. I spilled a little bit of this on my shirt as well. Unfortunately
|
||||
now, mixed with the coffee stain from earlier, the combination gave off a toilet smell that was
|
||||
quite unpleasant. I started praying that <span class="computer">ByteLord</span> had a cold and wouldn't be able to
|
||||
smell anything. Luckily the stains were still mostly invisible on my black t-shirt.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
When <span class="computer">ByteLord</span> walked in the front door, I almost mistook him for one of those cool
|
||||
hip-hopper guys. He was decked out from head to toe and looked <i>on fleek</i>.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
<!--
|
||||
<div id="sg1-footer">
|
||||
<span>Smart Bar</span>
|
||||
<span>Continued on page 02 →</span>
|
||||
<span>01</span>
|
||||
</div>
|
||||
-->
|
||||
|
||||
</div>
|
||||
241
pages/streetgeek-2.html
Normal file
@@ -0,0 +1,241 @@
|
||||
<style>
|
||||
|
||||
@font-face {
|
||||
font-family: "Aerosoldier";
|
||||
src: url("assets/font/aerosoldier.otf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "COMPUTER Robot";
|
||||
src: url("assets/font/computer-robot.ttf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "TS Block Bold";
|
||||
src: url("assets/font/ts-block-bold.ttf");
|
||||
}
|
||||
|
||||
span.computer {
|
||||
font-family: "COMPUTER Robot", monospace;
|
||||
}
|
||||
|
||||
span.cyberwheel {
|
||||
font-family: "TS Block Bold";
|
||||
}
|
||||
|
||||
#streetgeek2-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0.3in 0.4in;
|
||||
background-color: white;
|
||||
color: black;
|
||||
font: 8.6pt/1.42 'Times New Roman', serif;
|
||||
}
|
||||
|
||||
#sg2-topbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
font-size: 6.5pt;
|
||||
font-weight: bold;
|
||||
letter-spacing: 0.16em;
|
||||
text-transform: uppercase;
|
||||
color: #333;
|
||||
border-bottom: 0.75pt solid black;
|
||||
padding-bottom: 4pt;
|
||||
margin-bottom: 8pt;
|
||||
}
|
||||
|
||||
#sg2-topbar span:first-child {
|
||||
color: rgb(209, 1, 209);
|
||||
}
|
||||
|
||||
#sg2-headline {
|
||||
font-family: Aerosoldier, Helvetica, sans-serif;
|
||||
font-size: 24pt;
|
||||
line-height: 0.9;
|
||||
margin: 0 0 3pt 0;
|
||||
}
|
||||
|
||||
#sg2-subhead {
|
||||
font-family: Georgia, 'Times New Roman', serif;
|
||||
font-style: italic;
|
||||
font-size: 9.5pt;
|
||||
color: #222;
|
||||
margin: 0 0 10pt 0;
|
||||
}
|
||||
|
||||
#sg2-gallery {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
gap: 0.2in;
|
||||
margin: 3pt 0 8pt 0;
|
||||
}
|
||||
|
||||
.sg2-frame {
|
||||
background: white;
|
||||
padding: 6px 6px 5px 6px;
|
||||
border: 1px solid #ddd;
|
||||
box-shadow: 0 3px 10px rgba(0,0,0,0.28);
|
||||
break-inside: avoid;
|
||||
-webkit-column-break-inside: avoid;
|
||||
}
|
||||
|
||||
.sg2-frame.left, .sg2-frame.right {
|
||||
width: 1.95in;
|
||||
}
|
||||
|
||||
.sg2-frame.left { transform: rotate(-2.4deg); }
|
||||
.sg2-frame.right { transform: rotate(2.2deg); }
|
||||
|
||||
.sg2-frame.solo {
|
||||
float: right;
|
||||
width: 46%;
|
||||
margin: 2pt 0 4pt 10px;
|
||||
transform: rotate(-1.6deg);
|
||||
}
|
||||
|
||||
.sg2-frame img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.sg2-caption {
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
font-size: 6.5pt;
|
||||
letter-spacing: 0.1em;
|
||||
text-transform: uppercase;
|
||||
text-align: center;
|
||||
color: #333;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
#sg2-text {
|
||||
flex: 1;
|
||||
columns: 2;
|
||||
column-gap: 0.26in;
|
||||
}
|
||||
|
||||
#sg2-text p {
|
||||
text-indent: 2em;
|
||||
text-align: justify;
|
||||
text-justify: inter-word;
|
||||
hyphens: auto;
|
||||
overflow-wrap: break-word;
|
||||
margin: 0 0 4pt 0;
|
||||
}
|
||||
|
||||
#sg2-text p:first-child {
|
||||
text-indent: 0;
|
||||
}
|
||||
|
||||
#sg2-text p:first-child::first-letter {
|
||||
float: left;
|
||||
font-size: 3em;
|
||||
line-height: 0.78;
|
||||
padding-right: 6px;
|
||||
font-weight: bold;
|
||||
font-family: Georgia, serif;
|
||||
}
|
||||
|
||||
#sg2-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
border-top: 0.75pt solid black;
|
||||
margin-top: 6pt;
|
||||
padding-top: 4pt;
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
font-size: 6.5pt;
|
||||
letter-spacing: 0.1em;
|
||||
text-transform: uppercase;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<div id="streetgeek2-page" class="page-base">
|
||||
|
||||
<!--
|
||||
<div id="sg2-topbar">
|
||||
<span>Street Geek</span>
|
||||
<span>Field Report</span>
|
||||
<span>Page 02</span>
|
||||
</div>
|
||||
-->
|
||||
|
||||
<!--
|
||||
<div id="sg2-header">
|
||||
<h2 id="sg2-headline">The Look</h2>
|
||||
<p id="sg2-subhead">ByteLord, spotted in the wild at Cha-Ya on Valencia.</p>
|
||||
</div>
|
||||
-->
|
||||
|
||||
<div id="sg2-gallery">
|
||||
<div class="sg2-frame left">
|
||||
<img src="assets/img/streetgeek-java.png" alt="ByteLord wearing a Java t-shirt under a gray plaid overshirt" />
|
||||
<div class="sg2-caption">"Java"</div>
|
||||
</div>
|
||||
<div class="sg2-frame right">
|
||||
<img src="assets/img/streetgeek-evangelion.png" alt="ByteLord wearing an Evangelion t-shirt under a black overshirt" />
|
||||
<div class="sg2-caption">"Evangelion"</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="sg2-text">
|
||||
|
||||
<p>
|
||||
A typically chilly San Francisco summer afternoon, he was dressed for warmth. A beanie with an
|
||||
off-white pattern, reminiscent of the Windows XP start menu background. A comfy gray overshirt
|
||||
with a clean geometric pattern. Some kind of smart watch whose battery was obviously dead. Most
|
||||
tellingly, a <i>Java</i> t-shirt underneath the overshirt which, in the context of the rest of
|
||||
the outfit, gave the programming language an undeserved level of coolness.
|
||||
</p>
|
||||
|
||||
<div class="sg2-frame solo">
|
||||
<img src="assets/img/streetgeek-dogtags.jpeg" alt="ByteLord's necklace: a Mac OS X Panther dog tag on a ball chain" />
|
||||
</div>
|
||||
|
||||
<p>
|
||||
Besides the comfortable textiles, I was impressed with <span class="computer">ByteLord</span>'s choice of accessories.
|
||||
A wallet chain, which I hadn't seen anyone wear since the mid 2000's, added a metallic balance
|
||||
to the lower half of his appearance. His neck was adorned with a necklace that didn't appear to
|
||||
serve any functional purpose, but matched the wallet chain quite nicely. Finally, he was wearing
|
||||
rather large, television-sized eyeglass frames. Frankly, I had never given much thought to
|
||||
accessories in an outfit, unless they have some kind of microchip in them. But <span class="computer">ByteLord</span>
|
||||
had arranged them in such a way to make the outfit look effortless, yet stylish.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
We ordered <i>zaru soba</i> with <i>vegetable tempura</i> and spoke about various topics from
|
||||
cyberspace. After we finished talking about the guy that supposedly defaced fellow editor P. M.
|
||||
Cho's website, we started talking about fashion. I was surprised to hear that <span class="computer">ByteLord</span>
|
||||
was quite into fashion on a deeply philosophical level. He explained to me that what you wear in
|
||||
meatspace is like the <i>graphical shell</i> for a person. A hacker should present himself to
|
||||
others in the same way that a computer program presents itself to users. You wouldn't want to
|
||||
walk away from an interaction with someone having the same feeling as using the <i>git</i>
|
||||
command line interface.
|
||||
</p>
|
||||
|
||||
<p> I paid for the <i>zaru soba</i> and <span class="computer">ByteLord</span> sent me some <span class="cyberwheel">SMARTCOIN</span> for his
|
||||
portion. We parted ways, each walking opposite directions on Valencia street. After our meeting,
|
||||
I couldn't help but notice and observe all of the outfits people were wearing that day in the
|
||||
Mission. Thousands of distinct <i>graphical shells</i> all populating the virtual desktop that
|
||||
is our city. I smiled, and started thinking about a new ordering system for my wardrobe.
|
||||
<span class="endmark">■</span>
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
<!--
|
||||
<div id="sg2-footer">
|
||||
<span>Smart Bar</span>
|
||||
<span>■</span>
|
||||
<span>02</span>
|
||||
</div>
|
||||
-->
|
||||
|
||||
</div>
|
||||
148
pages/vranklevictim.html
Normal file
@@ -0,0 +1,148 @@
|
||||
<meta charset="UTF-8">
|
||||
|
||||
<style>
|
||||
body {
|
||||
width: 5.5in;
|
||||
height: 8.5in;
|
||||
position: relative;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
h1,h2,h3 {
|
||||
text-align: center;
|
||||
margin-top: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 4rem;
|
||||
font-weight: bold;
|
||||
font-family: "Impact";
|
||||
color: darkred;
|
||||
rotate: 5deg;
|
||||
border: solid;
|
||||
border-width: 0.125in;
|
||||
padding: 8px;
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin-left: 0.25in;
|
||||
margin-right: 0.25in;
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 4px;
|
||||
margin-bottom: 4px;
|
||||
margin-left: 0.25in;
|
||||
margin-right: 0.25in;
|
||||
text-align: justify;
|
||||
}
|
||||
|
||||
.root {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
grey 0%,
|
||||
black 45%,
|
||||
black, black
|
||||
);
|
||||
color: white;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
div * {
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
img {
|
||||
margin-top: 2.85in;
|
||||
width: 50%;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
rotate: -10deg;
|
||||
scale: 115%;
|
||||
filter: saturate(0.45) sepia(0.8) brightness(0.6);
|
||||
}
|
||||
|
||||
aside {
|
||||
width: 100%;
|
||||
text-transform: uppercase;
|
||||
text-align: center;
|
||||
margin-left: 0.25in;
|
||||
margin-right: 0.25in;
|
||||
}
|
||||
|
||||
figure {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.logo {
|
||||
text-transform: uppercase;
|
||||
background-image: radial-gradient(yellow, goldenrod);
|
||||
color: transparent;
|
||||
background-clip: text;
|
||||
font-family: sans-serif;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.byline {
|
||||
background-color: #F0E0D6;
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
bottom: 0px;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<div class="root">
|
||||
<aside style="position: absolute; top: 0;">
|
||||
Coming exclusively to <span class="logo">ULTRA-PLUS®</span> on Christmas Day
|
||||
</aside>
|
||||
|
||||
<figure style="height: 0.3in"></figure>
|
||||
|
||||
<h1>
|
||||
Vrankle: A Victim
|
||||
</h1>
|
||||
|
||||
<img src="assets/img/vrankle.png"/>
|
||||
|
||||
<h2>
|
||||
The true story of an innocent man's struggle
|
||||
</h2>
|
||||
|
||||
<p>
|
||||
Jer Vrankle was attending a fun demonstration downtown when a brute ignorantly called his baseball cap "cool" in the co-op grocer's parking lot. Vrankle was thrust into crisis. Recovering from a small cold and running a mild fever, Jer was the opposite of "cool". The property security dismissed complaints about the comment.
|
||||
</p>
|
||||
|
||||
<figure style="height: 2.5in;"></figure>
|
||||
|
||||
<p>
|
||||
The 48 episode docuseries, directed by the award-winning Selgus Bango, deals with the complex struggles plaguing society today: relationships, decency, and verbal communication.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Learn about every aspect of the conflict. Make up your own mind about the innocent Jer and antagonistic brute. Hear interviews with bystanders parking in the lot, watch the security camera footage of the event, and listen to Ivy League professors unpack every detail.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Famed composer Hilden Valeigh's soundtrack is a complex mix of droning repetitive ambiance. The 9 disc soundtrack is releasing exclusively for streaming on <span class="logo">ULTRA-PLUS®</span> later this winter.
|
||||
</p>
|
||||
|
||||
<aside style="position: absolute; bottom: 32px;">
|
||||
Watch the 48 episode docuseries on <span class="logo">ULTRA-PLUS®</span>
|
||||
</aside>
|
||||
|
||||
<div class="byline">
|
||||
<span style="color: black;">by</span>
|
||||
<span style="color: black;">[+]</span>
|
||||
<span style="font-weight: bold; color: forestgreen;">Max Res Default</span>
|
||||
</div>
|
||||
</div>
|
||||
19
pages/windows-shirt.html
Normal file
@@ -0,0 +1,19 @@
|
||||
<style>
|
||||
#windows-shirt::before {
|
||||
background-image: url("assets/img/windows-shirt.jpg");
|
||||
}
|
||||
|
||||
#windows-shirt-caption {
|
||||
position: absolute;
|
||||
bottom: 10%;
|
||||
left: 10%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="windows-shirt" class="page-base interlude">
|
||||
<div class="interlude-caption" id="windows-shirt-caption">
|
||||
we line up to store<br/>
|
||||
only 4 gigabytes allowed<br/>
|
||||
comrade, be grateful!
|
||||
</div>
|
||||
</div>
|
||||
136
pages/wtf.html
Normal file
@@ -0,0 +1,136 @@
|
||||
<style>
|
||||
#wtf-page {
|
||||
background: url("assets/img/wtf.png") no-repeat center center;
|
||||
background-size: cover;
|
||||
|
||||
font: 8.75pt/1.40 Tahoma, sans-serif;
|
||||
padding: 10px;
|
||||
hyphens: auto;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-family: "Heading Now";
|
||||
font-size: 23px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-family: "Heading Now";
|
||||
font-size: 24px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
p {
|
||||
font-family: Helvetica, sans-serif;
|
||||
font-size: 12.75pt;
|
||||
}
|
||||
|
||||
/* Windows 98 styles */
|
||||
.win98-window {
|
||||
background: #c0c0c0c0;
|
||||
border: 2px solid;
|
||||
border-color: #ffffff #404040 #404040 #ffffff; /* light top/left, dark bottom/right */
|
||||
padding: 6px;
|
||||
|
||||
/* Vertically center the window in the page */
|
||||
position: relative;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.win98-titlebar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 0 0 6px 0;
|
||||
padding: 4px 8px;
|
||||
min-height: 22px;
|
||||
font-weight: bold;
|
||||
color: #ffffff;
|
||||
background: linear-gradient(90deg, #000080 0%, #1084d0 100%);
|
||||
border: 2px solid;
|
||||
border-color: #ffffff #404040 #404040 #ffffff;
|
||||
}
|
||||
|
||||
.win98-content {
|
||||
background: #ffffffc0;
|
||||
border: 2px solid;
|
||||
border-color: #808080 #ffffff #ffffff #808080; /* inset look */
|
||||
padding: 10px;
|
||||
color: #000000;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.feces {
|
||||
transform: skewX(-10deg);
|
||||
transform-origin: bottom center;
|
||||
display: inline-block;
|
||||
color: rgb(154, 77, 0);
|
||||
font-stretch: condensed;
|
||||
font-family: inherit;
|
||||
font-size: 90%;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.round-boxes {
|
||||
display: inline-block;
|
||||
font-stretch: condensed;
|
||||
font-family: inherit;
|
||||
border-radius: 10px;
|
||||
|
||||
background: rgb(166, 205, 211);
|
||||
padding: 2px 4px;
|
||||
}
|
||||
|
||||
.real {
|
||||
display: inline-block;
|
||||
transform: scaleX(2.5);
|
||||
transform-origin: left;
|
||||
font-weight: bold;
|
||||
width: 80px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="wtf-page" class="page-base">
|
||||
<div class="win98-window">
|
||||
<h1 class="win98-titlebar">wtf??? is this???</h1>
|
||||
<div id="wtf-text" class="win98-content">
|
||||
<h2>AH, THE CITY BY THE BAY,</h2>
|
||||
|
||||
<p>
|
||||
If you ask me, I say the city needs some more grime. I
|
||||
know that’s probably not what you were thinking when
|
||||
you stepped over the <span class="feces">HUMAN FECES</span>
|
||||
this morning on the
|
||||
way from picking up your $6 latte. And for damn sure it
|
||||
wasn’t what your UX designer or whatever he calls himself
|
||||
was thinking when he was drawing his stupid fucking
|
||||
<span class="round-boxes">ROUND BOXES</span> for the hundreth time.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Put the round boxes on ice. This is SMART BAR. The
|
||||
“mission district before it was cool” publication. The
|
||||
“noisebridge before they got kicked out” edition.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
You see, here at SMART BAR we don’t give a shit. But we
|
||||
also care. No, not about you. We have our own agenda.
|
||||
What is our agenda? That’s our business.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
SMART BAR is the little bit o’ dirt that makes virtual
|
||||
reality feel more <span class="real">real</span>. It’s the disgusting mess of
|
||||
cables behind your desk that you’re constantly trying
|
||||
to hide. It’s the malware with the pretty UI that tries to
|
||||
steal people’s bitcoin.
|
||||
</p>
|
||||
|
||||
<p>Log on, if you dare.</p>
|
||||
|
||||
<h3>TRENT SNEEK</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -5,6 +5,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Print</title>
|
||||
<link rel="stylesheet" href="assets/css/base.css" />
|
||||
<link rel="stylesheet" href="assets/css/smartbar.css" />
|
||||
<style>
|
||||
/* Ensure a clean print with no gaps between pages */
|
||||
@media screen { body { background: #eeeeee; } }
|
||||
|
||||