Files
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

82 lines
3.2 KiB
HTML

<!doctype html>
<html lang="en">
<head>
{{- partial "head.html" . }}
</head>
<body>
{{- partial "header.html" . }}
<main id="content">
{{- block "main" . }}{{- end }}
</main>
{{- partial "footer.html" . }}
{{- partial "lightbox.html" . }}
<div id="ribbon"
class="ribbon fixed bottom-0 inset-x-0 z-[100]
flex items-center justify-between gap-4
px-[var(--pad)] py-3
bg-ink text-paper
font-sans font-medium text-[11px] leading-none tracking-[.16em] uppercase
transition-transform duration-300">
<span>Roux № {{ .Site.Params.issueNumber }} — out now. <a href="/issues/{{ .Site.Params.issueNumber }}/" class="border-b border-current ml-1">See the plates →</a></span>
<button id="ribbonClose" aria-label="Dismiss"
class="w-6 h-6 shrink-0 grid place-items-center border border-current/30 rounded-full
opacity-60 hover:opacity-100 transition-opacity duration-200">
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>
</button>
</div>
{{/* Inline posts data for JS search + lightbox */}}
<script id="roux-data" type="application/json">
{{- $posts := slice }}
{{- range (where .Site.RegularPages "Section" "posts") }}
{{- $img := .Resources.GetMatch "*.png" }}
{{- $thumb := "" }}
{{- $card := "" }}
{{- if $img }}
{{- $t := $img.Resize "300x450 webp" }}
{{- $c := $img.Resize "900x1350 webp" }}
{{- $thumb = $t.RelPermalink }}
{{- $card = $c.RelPermalink }}
{{- end }}
{{- $post := dict
"id" .Params.plate
"slug" .Params.slug
"issue" (index (.Params.issues | default (slice "01")) 0)
"title" .Title
"description" .Params.description
"categories" .Params.categories
"tags" .Params.tags
"thumb" $thumb
"card" $card
"url" .RelPermalink
}}
{{- $posts = $posts | append $post }}
{{- end }}
{{- $posts | jsonify | safeJS }}
</script>
{{/* Pure image zoom — opened by hero button or clicking the lightbox main image */}}
<div id="imgZoom" class="img-zoom" role="dialog" aria-modal="true" aria-label="Full size image">
<button id="imgZoomClose"
class="absolute top-4 right-4 z-10
w-10 h-10 grid place-items-center
border border-white/20 rounded-full
text-white/60 hover:text-white hover:bg-white/10
transition-colors duration-200"
aria-label="Close full image">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="M18 6 6 18"/><path d="m6 6 12 12"/>
</svg>
</button>
<img id="imgZoomImg" src="" alt="" class="max-w-[96vw] max-h-[96vh] object-contain select-none" />
</div>
<script src="/js/app.js" defer></script>
</body>
</html>