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

83 lines
3.8 KiB
HTML

{{ define "main" }}
{{- $issueNum := .Site.Params.issueNumber }}
{{- $issuePage := $.Site.GetPage (printf "/issues/%s" $issueNum) }}
{{- $plates := cond (ne $issuePage nil) $issuePage.Pages .Site.RegularPages }}
{{- $total := len $plates }}
{{- $featured := where $plates ".Params.featured" true }}
{{/* ── Hero ── */}}
<section id="hero"
class="grid max-[820px]:block
[grid-template-columns:1fr_1px_auto]
border-b border-[var(--rule)]">
{{/* Left: brand statement */}}
<div class="px-[var(--pad)] pt-[clamp(40px,6vw,80px)] pb-[clamp(32px,4vw,60px)]">
<div class="font-sans font-medium text-[10px] leading-none tracking-[.22em] uppercase text-ink-soft mb-6">
№ {{ $issueNum }} · {{ .Site.Params.issueSeason }}
</div>
<h1 class="font-display font-normal text-[clamp(42px,6vw,88px)] leading-[0.94] m-0 mb-5">
{{- if $issuePage }}{{ $issuePage.Title }}{{ else }}{{ .Site.Params.issueName }}{{ end }}
</h1>
<p class="font-serif italic text-[clamp(14px,1.1vw,17px)] leading-[1.65] text-ink-2 max-w-[44ch] m-0 mb-8">
{{- if $issuePage }}{{ $issuePage.Params.description }}{{ else }}{{ .Site.Params.issueBlurb }}{{ end }}
</p>
<div class="flex items-center gap-4 flex-wrap">
<a href="/issues/{{ $issueNum }}/"
class="inline-flex items-center gap-2 px-6 py-3
bg-roux text-paper
font-sans font-medium text-[11px] leading-none tracking-[.16em] uppercase
transition-colors duration-200 hover:bg-roux-deep">
Enter the issue
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14"/><path d="m12 5 7 7-7 7"/></svg>
</a>
<a href="/issues/"
class="font-sans font-medium text-[11px] leading-none tracking-[.16em] uppercase text-ink-soft
border-b border-[var(--rule)] pb-px transition-colors duration-200 hover:text-ink hover:border-ink">
All issues
</a>
</div>
</div>
{{/* Divider */}}
<div class="bg-[var(--rule)] max-[820px]:hidden"></div>
{{/* Right: issue identity */}}
<div class="flex flex-col items-center justify-center gap-3
px-[clamp(24px,4vw,60px)] py-[clamp(32px,4vw,60px)]
border-l-0 max-[820px]:border-t max-[820px]:border-[var(--rule)]
text-center">
{{- partial "logo.html" . }}
<div class="font-sans font-medium text-[10px] leading-none tracking-[.22em] uppercase text-ink-soft mt-2">
{{- if $issuePage }}{{ $issuePage.Params.season }}{{ else }}{{ .Site.Params.issueSeason }}{{ end }}
</div>
<div class="font-mono text-[11px] tracking-[.14em] text-ink-soft tabular-nums">
{{ $total }} plates
</div>
</div>
</section>
{{/* ── Plate preview ── */}}
<section id="grid"
class="grid [grid-template-columns:repeat(auto-fill,minmax(220px,1fr))] gap-[var(--gap)] px-[var(--pad)] pt-[var(--gap)] pb-0">
{{- range $i, $p := (first 5 $featured) }}
{{- partial "card.html" (dict "Page" $p "Index" $i) }}
{{- end }}
</section>
{{/* ── CTA strip ── */}}
<div class="flex items-center justify-between gap-6 flex-wrap
px-[var(--pad)] py-[clamp(24px,3vw,40px)]
border-t border-[var(--rule)] mt-[var(--gap)]">
<p class="font-serif italic text-[clamp(15px,1.2vw,18px)] text-ink-2 m-0">
One hundred plates — <em>fabric, light, and the human form in motion.</em>
</p>
<a href="/issues/{{ $issueNum }}/"
class="shrink-0 inline-flex items-center gap-2
font-sans font-medium text-[11px] leading-none tracking-[.16em] uppercase text-ink
border-b border-[var(--rule)] pb-px transition-colors duration-200 hover:border-ink">
View all {{ $total }} plates →
</a>
</div>
{{ end }}