# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Commands ```bash pnpm install # installs @tailwindcss/cli; Hugo shells out to it pnpm dev # hugo server -D (Hugo compiles Tailwind + hot-reloads) pnpm build # hugo --minify → public/ python3 scripts/import-posts.py # Import issue 01 from ~/projects/ginger/posts.csv → content/posts/01/ python3 scripts/generate-images.py --prompts data/prompts/issue-NN.json --issue NN # Replicate flux-1.1-pro → content/posts/NN//.png python3 scripts/faceswap-images.py --issue NN --source ~/Bilder/palina.webp # FaceFusion: swap one consistent face into every plate (in place) python3 scripts/upscale-images.py --issue NN # Upscayl + Remacri 4x → clamp to 4992px long edge (in place) python3 scripts/build-issue.py --issue NN --prompts data/prompts/issue-NN.json # write content/posts/NN//index.md ``` Hugo (≥ 0.161) compiles Tailwind itself during the build — no separate CSS step. ## Architecture **Hugo Extended** static site with **Tailwind CSS v4** (no config file — design tokens live in `@theme {}` in `assets/css/main.css`). ### CSS pipeline `layouts/partials/css.html` runs `resources.Get "css/main.css" | css.TailwindCSS` inside a `templates.Defer` block (invoked from `head.html`). Hugo shells out to `node_modules/.bin/tailwindcss`, which scans `hugo_stats.json` (written each build by `[build.buildStats]`, `@source`'d in `main.css`, mounted to `assets/notwatching/` and cache-busted per `hugo.toml`). Dev serves unminified `/css/main.css`; production emits a fingerprinted, SRI-hashed file. `assets/css/main.css` — `@theme {}` defines all design tokens (`--color-paper`, `--color-roux`, `--font-display`, …); global resets are in `@layer base` so Tailwind utilities override them. `pnpm-workspace.yaml` sets `preferSymlinkedExecutables: true` (Hugo rejects pnpm's default shell shim for `tailwindcss`) and `allowBuilds` for `@parcel/watcher` (so `pnpm run` doesn't fail on the ignored native build). ### Content model - `content/posts///index.md` — page bundles grouped by issue dir (`01/`, `02/`, …); each has a `plate` number, `issues`, `categories` (single item), `tags`, `featured` flag, and a `.png` image resource. Permalink is `/posts///`. - `content/posts//_index.md` — `build.render: never`; suppresses the intermediate `/posts//` list page (the per-issue landing page is the taxonomy term `/issues//`). - `content/issues//_index.md` — branch bundle for an issue; metadata (`title`, `description`, `issueNumber`, `season`, `status`) drives the homepage hero and issue list page. Only one issue should have `status: "current"`. - `content/imprint.md` — standalone page using `layout: imprint`. Taxonomies: `categories`, `tags`, `issues` (defined in `hugo.toml`). ### Layout hierarchy ``` layouts/baseof.html ← shell: head, header, main, footer, lightbox, roux-data JSON layouts/index.html ← homepage: current-issue hero + up to 5 featured plates from that issue layouts/_default/list.html ← two branches: kind="taxonomy" (term cards) | kind="term"/section (paginated post grid) layouts/_default/single.html ← post plate page (triggers lightbox via window.__ROUX_OPEN_SLUG) layouts/_default/imprint.html← prose page layout layouts/issues/list.html ← paginated post grid scoped to an issue layouts/issues/terms.html ← issue index, reads issueIds from hugo.toml params layouts/404.html ← editorial 404 page ``` Key partials: `head.html`, `css.html` (Tailwind build, see CSS pipeline), `header.html`, `footer.html`, `card.html` (reusable post card), `lightbox.html`, `pagination.html`, `logo.html`. ### JavaScript (`static/js/app.js`) Single vanilla JS file, no build step. Loaded from `baseof.html`. Reads `#roux-data` (JSON injected by Hugo containing all posts) at runtime. Responsibilities: - **Inverted search index** — weighted token scoring across title, tags, categories, description; prefix matching; renders into `#searchpop`. - **Lightbox** — full-screen plate viewer with keyboard nav, thumbnail strip, share/copy URL, meta panel. Opened automatically when `window.__ROUX_OPEN_SLUG` is set (direct URL to a post). - **Ribbon** dismiss, tab sync, view transitions (`document.startViewTransition`), masthead date. ### Data flow: homepage featured plates The homepage hero resolves the current issue via `.Site.Params.issueNumber` → `GetPage "/issues/"`. The featured grid shows up to 5 posts with `featured: true` **within that current issue** (`where $plates ".Params.featured" true | first 5`), so each issue's prompts file flags its own 5. ### Content pipeline - `scripts/import-posts.py` — issue 01 only; reads `~/projects/ginger/posts.csv` + `~/projects/ginger/images/final/selected/`, writes `content/posts/01/` deterministically (sorted by filename → stable plate numbers). - `scripts/generate-images.py` — reusable Replicate generator. `--prompts --issue NN`; token from `$REPLICATE_API_TOKEN` or `~/.env`; default model `black-forest-labs/flux-1.1-pro`, 960×1440 PNG (custom aspect); idempotent (skips existing), writes a `.generated.json` provenance manifest. - `scripts/faceswap-images.py` — `--issue NN --source ` (default `~/Bilder/palina.webp`). Runs FaceFusion `batch-run` (via `~/Projekte/facefusion/.venv`) once over the whole issue, swapping in one consistent face; writes back in place. `--enhance` adds the face_enhancer pass. Idempotent via `issue-NN.faceswap.json` keyed on the source hash. - `scripts/upscale-images.py` — `--issue NN`. Runs `upscayl-bin` (`/usr/share/upscayl/bin`) with the `remacri-4x` model once over the issue, then clamps the long edge to `--max-edge` (default 4992 ≈ issue-01 sources) with ImageMagick; writes back in place. Idempotent via `issue-NN.upscale.json`. - `scripts/build-issue.py` — generic; reads the same prompts JSON, writes `content/posts/NN//index.md` (plate numbers by stable slug sort), `featured` sourced from the JSON. - `data/prompts/issue-NN.json` — array of `{slug, title, category, tags, description, prompt, featured?}`. `data/` is gitignored: the prompts file and the `*.generated/faceswap/upscale.json` manifests are local pipeline scratch, not committed. The plate bundles under `content/posts/NN/` are the tracked output. Full pipeline order: `generate-images` → `faceswap-images` → `upscale-images` → `build-issue`. All four operate on `content/posts/NN//.png` in place and are individually idempotent; pass `--out DIR` to any of the image scripts to stage results without touching the bundles (that run is then untracked). ### Hugo params (hugo.toml) `issueNumber`, `issueName`, `issueSeason`, `issueBlurb` are fallback values used when `$.Site.GetPage "/issues/"` returns nil. `issueIds` (newest-first) drives the issues terms page order. `umamiSrc`/`umamiId` are injected only in production (`hugo.IsProduction`). ### nginx `nginx.conf` at repo root is the production server config (used in Docker). `error_page 404 /404.html` is already wired. A `location ~ ^/posts/(?!0[12]/)…` block 301-redirects the legacy flat issue-01 plate URLs (`/posts//`) to their new home under `/posts/01//`; extend the negative-lookahead when adding issue 03+.