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

96 lines
7.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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/<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.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 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/<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-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+.