Content - 100 new plates under content/posts/02/ — one recurring red-haired model across 10 editorial registers (Haute Couture, Film Noir, Boudoir, Avant-Garde, Minimalism, Baroque, Street Style, Surrealism, Monochrome, Nocturne), generated with flux-1.1-pro, face-swapped to a consistent identity (FaceFusion), and upscaled (Upscayl + Remacri, 3328x4992). - content/issues/02/_index.md; issue 01 -> status archived; hugo.toml params (issueIds newest-first, issueNumber/Name/Season/Blurb). Structure - Plate bundles grouped by issue: content/posts/<issue>/<slug>/, with build.render:never stubs so /posts/<issue>/ isn't emitted. nginx 301s the legacy flat /posts/<slug>/ URLs to /posts/01/<slug>/. The /posts/ archive uses .RegularPagesRecursive. Image pipeline (scripts/, data/ gitignored) - generate-images.py — Replicate flux-1.1-pro, 960x1440, idempotent. - faceswap-images.py — FaceFusion batch-run, one consistent face. - upscale-images.py — Upscayl + remacri-4x, long edge clamped to 4992. - build-issue.py — prompts JSON -> content/posts/<issue>/*/index.md. Build - CSS now compiled by Hugo's css.TailwindCSS (partials/css.html + templates.Defer); dropped the standalone Tailwind CLI step, concurrently, and the gitignored static/css/main.css. Requires Hugo >= 0.161; Dockerfile collapsed to a single hugomods/hugo:debian-node build stage. pnpm-workspace.yaml: preferSymlinkedExecutables + allowBuilds. Front end - Pagination 8 per page. - Lightbox: brand mark and category link out; robust SPA back/forward (fetch before startViewTransition, single-flight guard, swallow the transition abort rejection, popstate re-renders / reopens the viewer). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DZPmxGywFnAhmYJB1eh9fm
7.3 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Commands
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/<slug>/<slug>.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/<slug>/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/<issue>/<slug>/index.md— page bundles grouped by issue dir (01/,02/, …); each has aplatenumber,issues,categories(single item),tags,featuredflag, and a.pngimage resource. Permalink is/posts/<issue>/<slug>/.content/posts/<issue>/_index.md—build.render: never; suppresses the intermediate/posts/<issue>/list page (the per-issue landing page is the taxonomy term/issues/<issue>/).content/issues/<id>/_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 havestatus: "current".content/imprint.md— standalone page usinglayout: 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_SLUGis 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/<num>". 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/, writescontent/posts/01/deterministically (sorted by filename → stable plate numbers).scripts/generate-images.py— reusable Replicate generator.--prompts <json> --issue NN; token from$REPLICATE_API_TOKENor~/.env; default modelblack-forest-labs/flux-1.1-pro, 960×1440 PNG (custom aspect); idempotent (skips existing), writes a<prompts>.generated.jsonprovenance manifest.scripts/faceswap-images.py—--issue NN --source <face>(default~/Bilder/palina.webp). Runs FaceFusionbatch-run(via~/Projekte/facefusion/.venv) once over the whole issue, swapping in one consistent face; writes back in place.--enhanceadds the face_enhancer pass. Idempotent viaissue-NN.faceswap.jsonkeyed on the source hash.scripts/upscale-images.py—--issue NN. Runsupscayl-bin(/usr/share/upscayl/bin) with theremacri-4xmodel once over the issue, then clamps the long edge to--max-edge(default 4992 ≈ issue-01 sources) with ImageMagick; writes back in place. Idempotent viaissue-NN.upscale.json.scripts/build-issue.py— generic; reads the same prompts JSON, writescontent/posts/NN/<slug>/index.md(plate numbers by stable slug sort),featuredsourced 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.jsonmanifests are local pipeline scratch, not committed. The plate bundles undercontent/posts/NN/are the tracked output.
Full pipeline order: generate-images → faceswap-images → upscale-images → build-issue. All four operate on content/posts/NN/<slug>/<slug>.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/<issueNumber>" 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/<slug>/) to their new home under /posts/01/<slug>/; extend the negative-lookahead when adding issue 03+.