apply() read the [data-open-slug] marker off the parsed doc after replaceWith() had already reparented #content (and the marker) into the live document, so openSlug came back null and the plate viewer never opened. Read it off the newContent node instead, before the swap. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016h9VwM3YfMpDxaP8KzXcaR
Roux
A slow-publishing fashion journal — one hundred photographs at a time.
Editorial, couture, beauty and backstage plates.
Live: roux.pivoine.art
Stack
| Layer | Tool |
|---|---|
| Static site generator | Hugo Extended (≥ 0.161) |
| CSS | Tailwind CSS v4, compiled by Hugo's css.TailwindCSS |
| JavaScript | Vanilla (no framework) |
| Package manager | pnpm |
| Server | nginx (Docker) |
Development
pnpm install # installs @tailwindcss/cli (Hugo shells out to it)
pnpm dev # hugo server -D → http://localhost:1313
Hugo builds the CSS itself via css.TailwindCSS (see layouts/partials/css.html),
watches assets/, and hot-reloads. There is no separate CSS build step or watcher.
Requires node on PATH — Hugo execs node_modules/.bin/tailwindcss.
Build
pnpm build # hugo --minify → public/
CSS is emitted as a fingerprinted, integrity-hashed resource under public/css/;
hugo_stats.json (the class list Tailwind scans) is generated on every build and
gitignored.
pnpm-workspace.yaml sets preferSymlinkedExecutables: true so pnpm links
node_modules/.bin/tailwindcss as a symlink — Hugo rejects pnpm's default shell
shim (binary "tailwindcss" is not a Node.js script).
Content
Posts live in content/posts/<issue>/<slug>/ as Hugo page bundles (e.g.
content/posts/01/…, content/posts/02/…). Each bundle contains:
index.md— frontmatter withplate,title,description,categories,tags,issues, optionalfeatured: true<slug>.png— the plate image (used for thumbnails, lightbox, og:image)
content/posts/<issue>/_index.md carries build.render: never so only /posts/<issue>/<slug>/
pages are emitted — the per-issue landing page is the taxonomy term at /issues/<issue>/.
Issue membership is the issues taxonomy; each issue also has a branch bundle at
content/issues/<id>/_index.md (title, description, issueNumber, season, status).
Adding a new issue
- Write
data/prompts/issue-NN.json— an array of{slug, title, category, tags, description, prompt, featured?}. - Generate images:
python3 scripts/generate-images.py --prompts data/prompts/issue-NN.json --issue NN(Replicateflux-1.1-proby default; token from$REPLICATE_API_TOKENor~/.env). - Face-swap one consistent model into every plate:
python3 scripts/faceswap-images.py --issue NN --source ~/Bilder/palina.webp(FaceFusion at~/Projekte/facefusion; add--enhancefor a sharper face pass). - Upscale:
python3 scripts/upscale-images.py --issue NN(Upscayl + theremacri-4xmodel, then clamped to a 4992 px long edge;--scale 2or--max-edge 2880keeps the committed PNGs lean). - Build the bundles:
python3 scripts/build-issue.py --issue NN --prompts data/prompts/issue-NN.json - Add
content/issues/NN/_index.md, addcontent/posts/NN/_index.md(build.render: never), flip the previous issue'sstatusaway fromcurrent, and updatehugo.toml[params](issueIds,issueNumber,issueName,issueSeason,issueBlurb).
Steps 2–4 all write content/posts/NN/<slug>/<slug>.png in place and are each idempotent
(re-run to finish a failed batch; --force to redo). Pass --out DIR to stage instead.
Importing issue 01
Issue 01 is sourced from ~/projects/ginger/posts.csv and matching images in
~/projects/ginger/images/final/selected/:
python3 scripts/import-posts.py # writes content/posts/01/
This creates or updates page bundles, assigns plate numbers (sorted alphabetically by filename for stability), normalises descriptions, and copies images.
Design tokens
All colours, fonts, and spacing are defined in assets/css/main.css inside @theme {} and mirrored as CSS custom properties in :root. Edit there — not in a Tailwind config file (v4 has none).
Key tokens: --color-paper --color-ink --color-roux --font-display --font-serif --font-sans --pad --gap
Deployment
Dockerfile is a two-stage build: hugomods/hugo:debian-node-* runs
pnpm install then hugo --minify --environment production (Hugo compiles
Tailwind in-process — no pre-build CSS step), and nginx:alpine serves the
result with nginx.conf. Analytics (Umami) are injected only in the production
environment.
nginx.conf also 301-redirects the legacy flat issue-01 plate URLs
(/posts/<slug>/ → /posts/01/<slug>/); widen its negative-lookahead when
adding issue 03+.
License
All editorial content, photographs, and design © Roux MMXXVI. All rights reserved.
Contents are explicitly excluded from AI/ML training use without written consent.