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

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 with plate, title, description, categories, tags, issues, optional featured: 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

  1. Write data/prompts/issue-NN.json — an array of {slug, title, category, tags, description, prompt, featured?}.
  2. Generate images: python3 scripts/generate-images.py --prompts data/prompts/issue-NN.json --issue NN (Replicate flux-1.1-pro by default; token from $REPLICATE_API_TOKEN or ~/.env).
  3. Face-swap one consistent model into every plate: python3 scripts/faceswap-images.py --issue NN --source ~/Bilder/palina.webp (FaceFusion at ~/Projekte/facefusion; add --enhance for a sharper face pass).
  4. Upscale: python3 scripts/upscale-images.py --issue NN (Upscayl + the remacri-4x model, then clamped to a 4992 px long edge; --scale 2 or --max-edge 2880 keeps the committed PNGs lean).
  5. Build the bundles: python3 scripts/build-issue.py --issue NN --prompts data/prompts/issue-NN.json
  6. Add content/issues/NN/_index.md, add content/posts/NN/_index.md (build.render: never), flip the previous issue's status away from current, and update hugo.toml [params] (issueIds, issueNumber, issueName, issueSeason, issueBlurb).

Steps 24 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.

S
Description
Roux is a slow-publishing fashion journal — one hundred photographs at a time. Editorial, couture, beauty and backstage plates.
https://roux.pivoine.art
Readme
3.3 GiB
Languages
JavaScript 33.9%
HTML 32.5%
Python 23.2%
CSS 9.8%
Dockerfile 0.6%