Files
roux/CLAUDE.md
T
valknarandClaude Sonnet 5 2dc7a0063f Add issue No. 02 "Obsession" + reusable image pipeline
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
2026-08-29 21:11:24 +02:00

7.3 KiB
Raw Blame History

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 a plate number, issues, categories (single item), tags, featured flag, and a .png image resource. Permalink is /posts/<issue>/<slug>/.
  • content/posts/<issue>/_index.mdbuild.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 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.

The homepage hero resolves the current issue via .Site.Params.issueNumberGetPage "/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/, writes content/posts/01/ deterministically (sorted by filename → stable plate numbers).
  • scripts/generate-images.py — reusable Replicate generator. --prompts <json> --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 <prompts>.generated.json provenance manifest.
  • scripts/faceswap-images.py--issue NN --source <face> (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/<slug>/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-imagesfaceswap-imagesupscale-imagesbuild-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+.