/* ─────────────────────────────────────────────────────────────────────────
   tehisterav · stylesheet
   Designed-through monochrome. Black, white, four greys, tight type system.
   Every value here resolves to a token below — don't sprinkle one-off sizes,
   colors, or transitions. If you need a new value, add a token first.
   ───────────────────────────────────────────────────────────────────────── */

/* ── Self-hosted faces (CSP fontSrc inherits 'self', no helmet change needed)
      Schibsted Grotesk → editorial sans, multiple weights + italic
      IBM Plex Mono     → reserved for technical chrome (state tags, prompts,
                          numerals, IDs). Don't use mono for body text.        */
@font-face {
  font-family: "Schibsted Grotesk";
  font-style: normal; font-weight: 400; font-display: swap;
  src: url("/assets/fonts/sg-400.woff2") format("woff2");
}
@font-face {
  font-family: "Schibsted Grotesk";
  font-style: italic; font-weight: 400; font-display: swap;
  src: url("/assets/fonts/sg-400i.woff2") format("woff2");
}
@font-face {
  font-family: "Schibsted Grotesk";
  font-style: normal; font-weight: 500; font-display: swap;
  src: url("/assets/fonts/sg-500.woff2") format("woff2");
}
@font-face {
  font-family: "Schibsted Grotesk";
  font-style: normal; font-weight: 600; font-display: swap;
  src: url("/assets/fonts/sg-600.woff2") format("woff2");
}
@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal; font-weight: 400; font-display: swap;
  src: url("/assets/fonts/plex-mono-400.woff2") format("woff2");
}
@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal; font-weight: 500; font-display: swap;
  src: url("/assets/fonts/plex-mono-500.woff2") format("woff2");
}

/* ── Tokens ───────────────────────────────────────────────────────────────  */
:root {
  /* Surface — one paper, four ink shades, two rule weights */
  --paper:   #fbfbfa;   /* off-white; gentler than #fff under any monitor */
  --paper-2: #f3f1ec;   /* inset/code blocks/keyboard chips */
  --ink-1:   #0a0a0a;   /* primary type, wordmark, buttons */
  --ink-2:   #1a1a1a;   /* body text default */
  --ink-3:   #595959;   /* muted/captions/secondary metadata */
  --ink-4:   #a8a8a8;   /* placeholder, disabled, low-emphasis dividers */
  --rule:    #e6e4df;   /* hairline borders, default */
  --rule-2:  #d6d3cc;   /* slightly heavier rule for section separation */

  /* The only chromatic departures: vote up/down. Damped, not signal-bright. */
  --up:      #2f5d2f;
  --down:    #883232;

  /* Type families */
  --font-sans: "Schibsted Grotesk", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  --font-mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace;

  /* Type scale — pick from this. Don't introduce off-scale values. */
  --t-mono:  10.5px;   /* mono micro-caption (state tags, hint) */
  --t-cap:   11px;     /* small caps caption */
  --t-sm:    13px;     /* secondary body */
  --t-md:    15px;     /* body */
  --t-lg:    17px;     /* lightbox headline / subhead */
  --t-xl:    22px;     /* h2 / wordmark */
  --t-2xl:   28px;     /* h1 small */
  --t-3xl:   36px;     /* h1 prose */
  --t-disp:  44px;     /* reserved for hero, currently unused */

  /* Space scale — 4px grid */
  --s-1: 4px;  --s-2: 8px;  --s-3: 12px; --s-4: 16px;
  --s-5: 24px; --s-6: 32px; --s-7: 48px; --s-8: 64px; --s-9: 96px;

  /* Motion — one curve, two durations. */
  --ease:   cubic-bezier(.22,.61,.36,1);
  --t-fast: 180ms;
  --t-slow: 600ms;

  /* Body defaults applied via :root so they inherit cleanly */
  font-family: var(--font-sans);
  font-size: var(--t-md);
  font-weight: 400;
  line-height: 1.5;
  color: var(--ink-2);
  background: var(--paper);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
  font-variant-numeric: lining-nums;
}

* { box-sizing: border-box; }
html, body { margin: 0; }
body { background: var(--paper); color: var(--ink-2); }

::selection { background: var(--ink-1); color: var(--paper); }

a { color: inherit; }
a:focus-visible, button:focus-visible, summary:focus-visible, [tabindex]:focus-visible {
  outline: 2px solid var(--ink-1); outline-offset: 2px;
}

/* ── Masthead ─────────────────────────────────────────────────────────────
   Two-line composition: wordmark + nav on the top baseline; tagline microcopy
   on a second baseline beneath the wordmark. No harsh border-bottom — section
   separation is a single hairline rule. */
.site-header {
  display: grid; grid-template-columns: 1fr auto;
  align-items: baseline; row-gap: var(--s-1);
  padding: var(--s-6) var(--s-5) var(--s-4);
  max-width: 1500px; margin: 0 auto;
  border-bottom: 1px solid var(--rule);
}
.site-header .brand {
  font-family: var(--font-sans);
  font-size: var(--t-xl);
  font-weight: 500;
  letter-spacing: -0.012em;
  color: var(--ink-1);
  text-decoration: none;
  line-height: 1;
}
.site-header .tagline {
  grid-column: 1 / -1;
  font-family: var(--font-mono);
  font-size: var(--t-cap);
  letter-spacing: 0.04em;
  color: var(--ink-3);
  text-transform: lowercase;
}
.site-header nav { display: flex; gap: var(--s-4); align-items: baseline; }
.site-header nav a {
  font-family: var(--font-sans);
  font-size: var(--t-sm);
  font-weight: 500;
  color: var(--ink-3);
  text-decoration: none;
  letter-spacing: 0.01em;
  position: relative;
  padding-bottom: 2px;
  transition: color var(--t-fast) var(--ease);
}
.site-header nav a::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0;
  height: 1px; background: var(--ink-1);
  transform: scaleX(0); transform-origin: left center;
  transition: transform var(--t-fast) var(--ease);
}
.site-header nav a:hover { color: var(--ink-1); }
.site-header nav a:hover::after { transform: scaleX(1); }

main { min-height: 60vh; }

/* ── Footer ───────────────────────────────────────────────────────────────  */
.site-footer {
  margin-top: var(--s-9);
  padding: var(--s-6) var(--s-5);
  border-top: 1px solid var(--rule);
  font-size: var(--t-sm);
  color: var(--ink-3);
  line-height: 1.6;
}
.site-footer .disclaimer { max-width: 64ch; margin: 0 auto var(--s-3); }
.site-footer .disclaimer strong { color: var(--ink-2); font-weight: 500; }
.site-footer .footer-meta {
  max-width: 64ch; margin: 0 auto;
  font-family: var(--font-mono);
  font-size: var(--t-mono); letter-spacing: 0.02em;
  color: var(--ink-4);
}

/* ── Gallery ──────────────────────────────────────────────────────────────
   2-col desktop (~720px tiles), 1-col on narrow viewports. Bigger thumbs =
   more dramatic LQ vs HQ contrast — at 700px+ width JPEG artefacts and the
   resolution gap are unmissable. The grid metrics here are load-bearing for
   that effect. */
.gallery {
  display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--s-3);
  padding: var(--s-5);
  max-width: 1500px; margin: 0 auto;
}
@media (max-width: 700px) {
  .gallery { grid-template-columns: 1fr; padding: var(--s-3); gap: var(--s-3); }
}

/* Tile is a fixed grid cell with overflow:hidden — the IMAGES inside scale,
   not the box. Scaling the box lifts the tile outside its cell on top of
   neighbours and reads as a layout glitch. Scaling the inner images instead
   gives a contained zoom that's clipped by the tile edges, so the grid stays
   still. (Locked by CLAUDE.md — don't restore .tile:hover transform.) */
.tile {
  aspect-ratio: 4 / 3; position: relative; overflow: hidden;
  background: var(--paper-2); cursor: pointer; display: block; text-decoration: none;
  outline: none;
}
.tile:focus-visible { outline: 2px solid var(--ink-1); outline-offset: -2px; }
.tile img {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; display: block;
  transform: scale(1); transform-origin: center center;
  transition: transform var(--t-slow) var(--ease),
              opacity   var(--t-slow) var(--ease);
  will-change: transform, opacity;
}
.tile .thumb-hover { opacity: 0; }

/* Cascade-peek = phantom hover. Same image-zoom + LQ→HQ swap as real hover.
   :not(:hover) ensures real-hover specificity always wins (and prevents
   fighting). The cascade itself is disabled in JS but we keep the rules in
   parity with :hover in case the cascade returns. */
.tile.peeking .thumb-hover { opacity: 1; }
.tile.peeking:not(:hover) img { transform: scale(1.06); }

/* User hover — identical look, plus a fade-in legibility gradient at the
   bottom so .tile-meta sits on a designed surface rather than relying on
   raw text-shadow. */
.tile:hover .thumb-hover { opacity: 1; }
.tile:hover img, .tile:focus-within img { transform: scale(1.06); }
.tile::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 80px;
  background: linear-gradient(to top, rgba(0,0,0,0.55), rgba(0,0,0,0));
  opacity: 0; transition: opacity var(--t-fast) var(--ease);
  pointer-events: none; z-index: 1;
}
.tile:hover::after, .tile:focus-within::after { opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  .tile.peeking:not(:hover) img { transform: none; }
}

.tile-meta {
  position: absolute; left: var(--s-3); bottom: var(--s-2); right: var(--s-3);
  font-family: var(--font-mono);
  font-size: var(--t-mono);
  letter-spacing: 0.06em;
  color: rgba(255,255,255,0.96);
  opacity: 0;
  transition: opacity var(--t-fast) var(--ease);
  line-height: 1.3;
  display: flex; justify-content: space-between; gap: var(--s-2);
  z-index: 2; pointer-events: none;
}
.tile:hover .tile-meta, .tile:focus-within .tile-meta { opacity: 1; }
.tile-meta .title {
  font-family: var(--font-sans);
  font-size: var(--t-sm); font-weight: 400; letter-spacing: 0;
  color: rgba(255,255,255,0.96);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  flex: 1 1 auto; min-width: 0;
}
.tile-meta .vote { font-variant-numeric: tabular-nums; flex: 0 0 auto; }

.feed-meta {
  text-align: center;
  font-family: var(--font-mono);
  font-size: var(--t-mono); letter-spacing: 0.08em;
  color: var(--ink-3);
  padding: var(--s-6) 0 var(--s-4);
  text-transform: lowercase;
}

/* ── Lightbox ─────────────────────────────────────────────────────────────
   Chrome-less. The image is the show; everything else (title, source, prompt,
   share) sits in a thin dark gradient strip overlaying the bottom of the
   image. No white card panel — the dark backdrop bleeds straight into the
   image edges so attention stays on the picture. (Locked by CLAUDE.md.) */
.lightbox-backdrop {
  position: fixed; inset: 0;
  background: rgba(0,0,0,0.86); backdrop-filter: blur(10px);
  z-index: 100; display: none; align-items: center; justify-content: center;
  padding: var(--s-4);
}
.lightbox-backdrop.open { display: flex; }
.lightbox-row { display: flex; gap: var(--s-4); align-items: stretch; max-width: 100%; }

/* Card is a positioning container for the stack + bottom overlay. No
   background, no shadow — the backdrop already provides darkness; any chrome
   on the card just steals pixels from the image. */
.lightbox-card {
  width: min(1700px, 96vw); max-width: 96vw;
  height: 96vh; max-height: 96vh;
  background: transparent; border-radius: 0; overflow: hidden; box-shadow: none;
  display: flex; flex-direction: column; position: relative;
}

/* Image stack: fills the entire card; image inside uses object-fit:contain so
   any aspect (4:3 TV stills, 16:9, portrait) is shown whole. */
.stack {
  position: relative; flex: 1 1 auto; min-height: 0; overflow: hidden;
  user-select: none; cursor: pointer; background: #000;
  -webkit-touch-callout: none; -webkit-user-select: none; touch-action: pan-y;
}
.stack img {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain;
  -webkit-user-drag: none; pointer-events: none;
}
.stack .layer-old { z-index: 1; }
.stack .layer-new { z-index: 2; opacity: 0; transition: opacity 1s ease; }
.stack.revealed .layer-new { opacity: 1; }
.stack.peeking .layer-new { opacity: 0; transition: opacity var(--t-fast) ease; }

/* ENNE / PÄRAST — letter-spaced caps in mono. The peeking state is signalled
   by INVERSION (light text on dark → dark text on light), keeping the lightbox
   strictly monochromatic. The previous warm-rust tint (rgba(180,80,40,0.55))
   was intentional UX cue; inversion preserves the cue with a stronger
   monochrome reading. Don't reintroduce chromatic peek state. */
.state-tag {
  position: absolute; top: var(--s-3); left: var(--s-3);
  font-family: var(--font-mono);
  font-size: var(--t-mono);
  font-weight: 500;
  letter-spacing: 0.32em;
  color: rgba(255,255,255,0.92);
  padding: var(--s-1) var(--s-2);
  background: rgba(0,0,0,0.30);
  backdrop-filter: blur(6px);
  border-radius: 1px;
  z-index: 3;
  transition: background var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
  text-transform: uppercase;
}
.stack.peeking .state-tag { background: rgba(255,255,255,0.94); color: var(--ink-1); }

/* Hint pinned to top-center so it doesn't collide with the bottom overlay. */
.hint {
  position: absolute; top: var(--s-3); left: 50%; transform: translateX(-50%);
  font-family: var(--font-mono);
  font-size: var(--t-mono);
  letter-spacing: 0.16em;
  color: rgba(255,255,255,0.78);
  padding: var(--s-1) var(--s-3);
  background: rgba(0,0,0,0.28);
  backdrop-filter: blur(6px);
  border-radius: 999px;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.4s var(--ease);
  z-index: 3;
  text-transform: lowercase;
}
.stack.revealed .hint { opacity: 1; }
.stack.peeking .hint { opacity: 0; }

/* Bottom overlay strip — title + attribution + prompt + share, on a gradient
   that keeps text legible against any image content. The strip itself is
   pointer-events: none so press-and-hold (peek) still works through the
   strip; only links / buttons / summary intercept. (Locked by CLAUDE.md —
   never set pointer-events:auto on the whole strip.) */
.lb-overlay {
  position: absolute; left: 0; right: 0; bottom: 0;
  padding: var(--s-7) var(--s-5) var(--s-4);
  background: linear-gradient(to top,
    rgba(0,0,0,0.88) 0%,
    rgba(0,0,0,0.55) 55%,
    rgba(0,0,0,0)    100%);
  color: rgba(228,228,225,0.94);
  pointer-events: none;
  display: flex; flex-direction: column; gap: var(--s-2);
  z-index: 5;
}
.lb-overlay a, .lb-overlay button, .lb-overlay summary { pointer-events: auto; }

.lb-meta { padding: 0; }
.lb-meta h2 {
  margin: 0 0 var(--s-1);
  font-family: var(--font-sans);
  font-size: var(--t-lg); font-weight: 500;
  letter-spacing: -0.005em;
  color: rgba(248,248,246,0.98);
  line-height: 1.25;
}
.lb-meta .submitter {
  font-family: var(--font-mono);
  font-size: var(--t-mono);
  letter-spacing: 0.08em;
  color: rgba(180,178,170,0.85);
}
.lb-meta .submitter strong { font-weight: 500; color: rgba(228,226,220,0.95); }
.lb-meta .submitter a {
  color: rgba(228,226,220,0.95);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-decoration-color: rgba(228,226,220,0.30);
  text-underline-offset: 2px;
  transition: text-decoration-color var(--t-fast) var(--ease);
}
.lb-meta .submitter a:hover { text-decoration-color: rgba(228,226,220,0.92); }

.lb-overlay-row {
  display: flex; align-items: center; gap: var(--s-4); flex-wrap: wrap;
  margin-top: var(--s-1);
}
.lb-prompt {
  font-family: var(--font-mono);
  font-size: var(--t-mono);
  letter-spacing: 0.04em;
  color: rgba(160,158,152,0.78);
}
.lb-prompt summary {
  cursor: pointer;
  list-style: none;
  display: inline-flex; align-items: center; gap: 6px;
  text-transform: lowercase;
  transition: color var(--t-fast) var(--ease);
}
.lb-prompt summary::-webkit-details-marker { display: none; }
.lb-prompt summary::before {
  content: "+"; font-weight: 500; width: 10px; text-align: center;
  transition: transform var(--t-fast) var(--ease);
}
.lb-prompt[open] summary::before { content: "−"; }
.lb-prompt summary:hover { color: rgba(228,226,220,0.96); }
.lb-prompt code {
  display: block;
  margin-top: var(--s-2); padding: var(--s-2) var(--s-3);
  background: rgba(0,0,0,0.55);
  border-left: 1px solid rgba(255,255,255,0.18);
  border-radius: 0;
  color: rgba(220,218,212,0.88);
  font-family: var(--font-mono);
  font-size: var(--t-mono);
  letter-spacing: 0.02em;
  max-width: 800px;
  white-space: pre-wrap; word-break: break-word;
}

.lb-share { display: inline-flex; align-items: center; gap: var(--s-2); }
.lb-share button {
  font-family: var(--font-mono);
  font-size: var(--t-mono);
  letter-spacing: 0.16em;
  text-transform: lowercase;
  padding: var(--s-1) var(--s-3);
  border: 1px solid rgba(255,255,255,0.22);
  background: transparent;
  color: rgba(228,226,220,0.92);
  cursor: pointer;
  border-radius: 1px;
  transition: background var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease);
}
.lb-share button:hover { background: rgba(255,255,255,0.10); border-color: rgba(255,255,255,0.40); }
.lb-share #lb-share-status {
  font-family: var(--font-mono);
  font-size: var(--t-mono);
  letter-spacing: 0.06em;
  color: rgba(180,178,170,0.85);
}

/* Top-right toolbar — single horizontal row with thumbs-up/down + counts +
   close button. Replaces the old side-column .vote-rail (which got squeezed
   against the close-X when the card grew to 96vw). One line, one row.
   (Locked by CLAUDE.md — don't restore the side-column rail.) */
.lightbox-toolbar {
  position: absolute; top: var(--s-3); right: var(--s-3);
  z-index: 10; display: flex; align-items: center; gap: var(--s-3);
}
.lightbox-close {
  width: 34px; height: 34px; border-radius: 50%;
  background: rgba(255,255,255,0.06);
  backdrop-filter: blur(8px);
  display: flex; align-items: center; justify-content: center;
  color: #fff; cursor: pointer;
  border: 1px solid rgba(255,255,255,0.18);
  user-select: none;
  transition: background var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease);
}
.lightbox-close svg { width: 14px; height: 14px; stroke: currentColor; fill: none; }
.lightbox-close:hover { background: rgba(255,255,255,0.18); border-color: rgba(255,255,255,0.40); }

/* Prev/next nav — sit at viewport edges, semi-transparent until hover. */
.lightbox-nav {
  position: absolute; top: 50%; transform: translateY(-50%);
  width: 52px; height: 52px; border-radius: 50%;
  background: rgba(255,255,255,0.06);
  backdrop-filter: blur(8px);
  border: 1px solid rgba(255,255,255,0.18);
  color: #fff; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  padding: 0; z-index: 11;
  transition: background var(--t-fast) var(--ease),
              border-color var(--t-fast) var(--ease),
              transform var(--t-fast) var(--ease);
}
.lightbox-nav svg { width: 22px; height: 22px; stroke: currentColor; fill: none; stroke-width: 1.6; }
.lightbox-nav:hover { background: rgba(255,255,255,0.18); border-color: rgba(255,255,255,0.40); transform: translateY(-50%) scale(1.04); }
.lightbox-nav.prev { left: var(--s-4); }
.lightbox-nav.next { right: var(--s-4); }
@media (max-width: 700px) {
  .lightbox-nav { width: 42px; height: 42px; }
  .lightbox-nav.prev { left: max(var(--s-2), env(safe-area-inset-left)); }
  .lightbox-nav.next { right: max(var(--s-2), env(safe-area-inset-right)); }
  .lightbox-nav svg { width: 18px; height: 18px; }
}

/* Vote rail lives inside .lightbox-toolbar as a single horizontal row:
   thumb + count side-by-side. */
.vote-rail { display: flex; flex-direction: row; align-items: center; gap: var(--s-2); padding: 0; }
.vote-block { display: flex; flex-direction: row; align-items: center; gap: var(--s-1); }
.vote-btn {
  width: 34px; height: 34px; border-radius: 50%;
  background: rgba(255,255,255,0.06);
  backdrop-filter: blur(8px);
  border: 1px solid rgba(255,255,255,0.18);
  display: flex; align-items: center; justify-content: center;
  color: #fff; cursor: pointer; padding: 0;
  transition: background var(--t-fast) var(--ease),
              border-color var(--t-fast) var(--ease),
              transform var(--t-fast) var(--ease);
}
.vote-btn svg { width: 14px; height: 14px; stroke: currentColor; fill: none; stroke-width: 1.6; }
.vote-btn:hover { background: rgba(255,255,255,0.18); border-color: rgba(255,255,255,0.40); transform: scale(1.04); }
.vote-btn.voted { background: rgba(255,255,255,0.32); border-color: rgba(255,255,255,0.58); }
.vote-count {
  font-family: var(--font-mono);
  font-weight: 500;
  font-size: var(--t-cap);
  font-variant-numeric: tabular-nums;
  color: rgba(228,226,220,0.85);
  letter-spacing: 0.04em;
  min-width: 14px;
}

@media (max-width: 700px) {
  /* Mobile lightbox: full-screen, no padding, image stack expands. */
  .lightbox-backdrop { padding: 0; align-items: stretch; }
  .lightbox-row { flex-direction: column; gap: 0; width: 100vw; height: 100vh; height: 100dvh; }
  .lightbox-card { width: 100vw; max-width: 100vw; height: 100%; }
  .stack { aspect-ratio: auto; flex: 1 1 auto; min-height: 55vh; }
  .lb-overlay { padding: var(--s-6) var(--s-3) var(--s-3); }
  .lb-meta h2 { font-size: var(--t-md); }
  .lightbox-toolbar { top: max(var(--s-2), env(safe-area-inset-top)); right: max(var(--s-2), env(safe-area-inset-right)); gap: var(--s-1); }
  .vote-rail { gap: var(--s-1); }
  .vote-btn, .lightbox-close { width: 30px; height: 30px; }
}

/* ── Forms ────────────────────────────────────────────────────────────────
   Underline-only inputs. The boxed border felt generic; bottom rule lets the
   field breathe and keeps the page reading as paper rather than a console. */
.form-section {
  max-width: 520px; margin: var(--s-7) auto; padding: 0 var(--s-5);
}
.form-section h1 {
  font-family: var(--font-sans);
  font-size: var(--t-2xl); font-weight: 500; letter-spacing: -0.012em;
  margin: 0 0 var(--s-2);
  color: var(--ink-1);
}
.form-section .lead {
  color: var(--ink-3); font-size: var(--t-md); line-height: 1.55;
  margin: 0 0 var(--s-7);
}
form label { display: block; margin-bottom: var(--s-5); font-size: var(--t-sm); }
form label > span {
  display: block; margin-bottom: var(--s-1);
  font-family: var(--font-mono);
  font-size: var(--t-mono); font-weight: 500;
  letter-spacing: 0.10em; text-transform: uppercase;
  color: var(--ink-3);
}
form label > span em { color: var(--ink-1); font-style: normal; font-weight: 600; }
form label small {
  font-family: var(--font-mono);
  color: var(--ink-4); font-weight: 400; letter-spacing: 0.04em;
  text-transform: lowercase;
}
form input[type=text], form input[type=email], form input[type=url], form textarea {
  width: 100%;
  padding: var(--s-2) 0;
  border: none; border-bottom: 1px solid var(--rule-2);
  background: transparent;
  font-family: var(--font-sans);
  font-size: 16px;             /* ≥16px prevents iOS focus-zoom */
  color: var(--ink-1);
  border-radius: 0;
  transition: border-color var(--t-fast) var(--ease);
}
form input:focus, form textarea:focus {
  outline: none;
  border-bottom-color: var(--ink-1);
}
form textarea { resize: vertical; min-height: 4em; line-height: 1.55; }
form button[type=submit] {
  margin-top: var(--s-3);
  padding: var(--s-3) var(--s-5);
  border: 1px solid var(--ink-1);
  background: var(--ink-1); color: var(--paper);
  font-family: var(--font-sans);
  font-weight: 500; font-size: var(--t-sm);
  letter-spacing: 0.10em; text-transform: uppercase;
  cursor: pointer; border-radius: 0;
  transition: background var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
}
form button[type=submit]:hover { background: var(--paper); color: var(--ink-1); }
form .form-status {
  font-family: var(--font-mono);
  font-size: var(--t-cap); letter-spacing: 0.04em;
  margin-top: var(--s-3); min-height: 1.4em;
  color: var(--ink-3);
}
form .form-status.ok  { color: var(--up); }
form .form-status.err { color: var(--down); }

/* Dropzone — dashed rule, generous padding, refined typography */
.dropzone {
  border: 1px dashed var(--rule-2);
  padding: var(--s-7) var(--s-4);
  text-align: center; cursor: pointer;
  background: var(--paper); border-radius: 0;
  font-family: var(--font-sans); color: var(--ink-3); font-size: var(--t-sm);
  transition: border-color var(--t-fast) var(--ease), background var(--t-fast) var(--ease);
}
.dropzone:hover { border-color: var(--ink-3); }
.dropzone.drag { border-color: var(--ink-1); background: var(--paper-2); color: var(--ink-1); }
.dropzone input[type=file] { display: none; }
.dropzone img { max-width: 100%; max-height: 220px; margin-top: var(--s-3); }

/* ── Prose pages ──────────────────────────────────────────────────────────  */
.prose {
  max-width: 64ch; margin: var(--s-7) auto; padding: 0 var(--s-5);
  line-height: 1.65; font-size: var(--t-md); color: var(--ink-2);
}
.prose h1 {
  font-family: var(--font-sans);
  font-size: var(--t-3xl); font-weight: 500;
  letter-spacing: -0.018em; line-height: 1.1;
  margin: 0 0 var(--s-5);
  color: var(--ink-1);
}
.prose h2 {
  font-family: var(--font-sans);
  font-size: var(--t-lg); font-weight: 500;
  letter-spacing: -0.005em;
  margin-top: var(--s-7); padding-top: var(--s-4);
  border-top: 1px solid var(--rule);
  color: var(--ink-1);
}
.prose p { margin: 0 0 var(--s-4); }
.prose p strong { font-weight: 500; color: var(--ink-1); }
.prose a {
  color: var(--ink-1);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-decoration-color: var(--ink-3);
  text-underline-offset: 3px;
  transition: text-decoration-color var(--t-fast) var(--ease);
}
.prose a:hover { text-decoration-color: var(--ink-1); }
.prose .disclaimer { color: var(--ink-3); font-size: var(--t-sm); }
.prose .disclaimer strong { color: var(--ink-2); }

/* ── Admin ────────────────────────────────────────────────────────────────  */
.admin-page {
  padding: var(--s-5); max-width: 1200px; margin: 0 auto;
  font-size: var(--t-sm);
}
.admin-page h1 {
  font-family: var(--font-sans);
  font-size: var(--t-xl); font-weight: 500; letter-spacing: -0.01em;
  margin: 0 0 var(--s-4); color: var(--ink-1);
}

/* Admin sub-nav (Järjekord · Sõnumid · Statistika). Same hover-underline-grow
   as the public masthead. The `unread` modifier (set when unread_count > 0)
   takes the link to var(--down) + bold + a "(N)" count suffix — the only
   place in admin where ink-color carries semantic urgency. */
.admin-nav {
  display: flex; gap: var(--s-5);
  font-family: var(--font-sans);
  font-size: var(--t-sm);
  margin: 0 0 var(--s-5); padding: 0 0 var(--s-3);
  border-bottom: 1px solid var(--rule);
}
.admin-nav a {
  color: var(--ink-3);
  text-decoration: none;
  font-weight: 500;
  letter-spacing: 0.01em;
  position: relative;
  padding-bottom: 2px;
  transition: color var(--t-fast) var(--ease);
}
.admin-nav a::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0;
  height: 1px; background: var(--ink-1);
  transform: scaleX(0); transform-origin: left center;
  transition: transform var(--t-fast) var(--ease);
}
.admin-nav a:hover { color: var(--ink-1); }
.admin-nav a:hover::after { transform: scaleX(1); }
.admin-nav a.active { color: var(--ink-1); }
.admin-nav a.active::after { transform: scaleX(1); }

.admin-nav a.unread        { color: var(--down); font-weight: 700; }
.admin-nav a.unread:hover  { color: var(--down); }
.admin-nav a.unread::after { background: var(--down); }
.admin-nav a.unread.active::after { transform: scaleX(1); }
.admin-table { width: 100%; border-collapse: collapse; margin-top: var(--s-3); }
.admin-table th, .admin-table td {
  text-align: left; padding: var(--s-2) var(--s-3);
  border-bottom: 1px solid var(--rule);
  font-size: var(--t-sm); vertical-align: top;
}
.admin-table th {
  font-family: var(--font-mono);
  font-size: var(--t-mono); font-weight: 500;
  letter-spacing: 0.10em; text-transform: uppercase;
  color: var(--ink-3); padding-bottom: var(--s-2);
}
.admin-meta {
  display: grid; grid-template-columns: 160px 1fr;
  gap: var(--s-2) var(--s-4); font-size: var(--t-sm);
}
.admin-meta dt {
  font-family: var(--font-mono); font-size: var(--t-mono);
  letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink-3);
}
.messages { list-style: none; padding: 0; }
.messages li {
  border: 1px solid var(--rule); padding: var(--s-3); margin-bottom: var(--s-2);
  border-radius: 0; background: var(--paper);
}
.messages li.unread { background: var(--paper-2); border-color: var(--rule-2); }
.messages li header {
  display: flex; gap: var(--s-3);
  font-family: var(--font-mono); font-size: var(--t-mono);
  letter-spacing: 0.04em; color: var(--ink-3); margin-bottom: var(--s-2);
}
.messages li p { margin: 0; white-space: pre-wrap; }

/* Admin status badges are intentionally kept chromatic — the public-facing
   monochrome system doesn't extend here. Reason: admin chrome is a working
   tool, and at-a-glance color coding helps the operator scan a queue table
   faster than mono-typographic distinctions ever could. The hues here are
   damped to parchment-friendly tones so they sit comfortably alongside the
   ink-on-paper body, but they remain decisively coloured. Don't promote
   these tokens into :root. */
.status-badge {
  display: inline-block; padding: 2px var(--s-2); border-radius: 0;
  font-family: var(--font-mono); font-size: var(--t-mono); font-weight: 500;
  letter-spacing: 0.10em; text-transform: uppercase;
}
.status-pending  { background: var(--paper-2); color: var(--ink-3); }
.status-queued   { background: #efe7d4;        color: #6e5a25; }
.status-running  { background: #d8e6f5;        color: #1f4f8c; }
.status-done     { background: #d8ead4;        color: #2f5d2f; }
.status-failed   { background: #ecd2d2;        color: #883232; }
.status-approved { background: var(--ink-1);   color: var(--paper); }
.status-rejected { background: var(--ink-3);   color: var(--paper); }

/* Admin danger button + variants */
button.danger {
  background: var(--paper); color: var(--down);
  border: 1px solid var(--down);
  padding: var(--s-1) var(--s-3); font-size: var(--t-sm);
  cursor: pointer; border-radius: 0;
  margin-left: var(--s-1);
  font-family: var(--font-sans); font-weight: 500;
  letter-spacing: 0.04em;
  transition: background var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
}
button.danger:hover { background: var(--down); color: var(--paper); }
.variants li.deleted { opacity: 0.55; }
.variants li.deleted strong { font-style: italic; }
.variants li.published {
  background: linear-gradient(180deg, #eaf6ea 0%, #f4faf4 100%);
  border-left: 3px solid var(--up);
  padding: var(--s-3) var(--s-3) var(--s-3) var(--s-4);
  border-radius: 0;
  box-shadow: 0 2px 8px rgba(47,93,47,0.08);
}
.variants li.published:target { animation: published-flash 1.4s ease-out; }
.variant-header { display: flex; align-items: center; gap: var(--s-3); margin-bottom: var(--s-1); }
.badge-published {
  background: var(--up); color: var(--paper);
  font-family: var(--font-mono); font-size: var(--t-mono); font-weight: 500;
  letter-spacing: 0.12em; padding: 3px var(--s-2); border-radius: 0;
  text-transform: uppercase;
}
@keyframes published-flash {
  0%   { box-shadow: 0 0 0 0 rgba(47,93,47,0.55);  transform: scale(1); }
  20%  { box-shadow: 0 0 0 12px rgba(47,93,47,0); transform: scale(1.005); }
  100% { box-shadow: 0 2px 8px rgba(47,93,47,0.08); transform: scale(1); }
}
.variant-params {
  font-family: var(--font-mono); font-size: var(--t-mono);
  color: var(--ink-3); margin: var(--s-1) 0; letter-spacing: 0.04em;
}
.thinking-trace { margin: var(--s-2) 0 var(--s-1); font-size: var(--t-sm); }
.thinking-trace summary { cursor: pointer; color: var(--ink-3); user-select: none; }
.thinking-trace summary:hover { color: var(--ink-1); }
.thinking-trace pre {
  background: var(--paper-2); border-left: 3px solid var(--rule-2);
  padding: var(--s-3); margin: var(--s-2) 0 0;
  font-family: var(--font-mono); font-size: var(--t-sm); line-height: 1.5;
  color: var(--ink-2);
  white-space: pre-wrap; word-wrap: break-word;
  max-height: 320px; overflow-y: auto;
}

/* Admin zoom modal */
img.zoomable { cursor: zoom-in; }
.zoom-modal {
  position: fixed; inset: 0; background: rgba(0,0,0,0.92);
  z-index: 9999; display: none; align-items: center; justify-content: center;
  padding: var(--s-5); cursor: zoom-out;
}
.zoom-modal.open { display: flex; }
.zoom-modal img { max-width: 100%; max-height: 100%; object-fit: contain; box-shadow: 0 30px 80px rgba(0,0,0,0.6); }
.zoom-modal .zoom-close {
  position: absolute; top: var(--s-3); right: var(--s-4);
  width: 38px; height: 38px; border-radius: 50%;
  background: rgba(255,255,255,0.10); backdrop-filter: blur(8px);
  border: 1px solid rgba(255,255,255,0.20); color: #fff;
  font-size: 22px; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
}
.zoom-modal .zoom-meta {
  position: absolute; bottom: var(--s-3); left: var(--s-4);
  font-family: var(--font-mono); font-size: var(--t-mono);
  color: rgba(255,255,255,0.7); letter-spacing: 0.04em;
}

/* Admin run form — temp/top_p/imageSize knobs + presets */
.run-form { max-width: 720px; }
.run-form > label { display: block; margin-bottom: var(--s-3); }
.run-form textarea {
  width: 100%; padding: var(--s-2) var(--s-3);
  border: 1px solid var(--rule-2);
  font-family: var(--font-mono); font-size: var(--t-sm);
  border-radius: 0; resize: vertical;
  background: var(--paper);
}
.run-form textarea:focus { outline: none; border-color: var(--ink-1); }
.run-knobs { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: var(--s-4); margin-bottom: var(--s-3); }
.knob {
  display: grid; grid-template-columns: 1fr auto;
  column-gap: var(--s-2); row-gap: var(--s-2);
  align-items: baseline;
  padding: var(--s-3) var(--s-3);
  background: var(--paper-2);
  border: 1px solid var(--rule);
  border-radius: 0;
}
.knob-name {
  grid-column: 1;
  font-family: var(--font-mono); font-size: var(--t-mono); font-weight: 500;
  color: var(--ink-3); letter-spacing: 0.10em; text-transform: uppercase;
}
.knob-value {
  grid-column: 2;
  font-family: var(--font-mono); font-size: var(--t-lg); font-weight: 500;
  color: var(--ink-1); font-variant-numeric: tabular-nums;
}
.knob-value-slot { grid-column: 2; visibility: hidden; }
.knob input[type=range] {
  grid-column: 1 / -1; width: 100%; height: 2px;
  -webkit-appearance: none; appearance: none;
  background: var(--rule-2);
  border-radius: 0; outline: none; cursor: pointer;
}
.knob input[type=range]::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 16px; height: 16px; border-radius: 50%;
  background: var(--ink-1); cursor: grab;
  border: 2px solid var(--paper);
  box-shadow: 0 1px 2px rgba(0,0,0,0.2);
}
.knob input[type=range]::-moz-range-thumb {
  width: 14px; height: 14px; border-radius: 50%;
  background: var(--ink-1); cursor: grab;
  border: 2px solid var(--paper);
  box-shadow: 0 1px 2px rgba(0,0,0,0.2);
}
.knob input[type=range]:active::-webkit-slider-thumb { cursor: grabbing; }
.knob select {
  grid-column: 1 / -1;
  padding: var(--s-1) var(--s-2);
  font-family: var(--font-sans); font-size: var(--t-sm);
  border: 1px solid var(--rule-2); border-radius: 0;
  background: var(--paper);
}

/* Inline help "?" icon with hover tooltip */
.help {
  display: inline-flex; align-items: center; justify-content: center;
  width: 14px; height: 14px; margin-left: var(--s-1);
  border-radius: 50%; background: var(--ink-3); color: var(--paper);
  font-family: var(--font-sans); font-size: 10px; font-weight: 600;
  cursor: help; position: relative; vertical-align: middle;
  text-transform: none; letter-spacing: 0;
}
.help::before {
  content: attr(data-tip);
  position: absolute; bottom: calc(100% + 8px); left: 50%; transform: translateX(-50%);
  width: 280px; padding: var(--s-2) var(--s-3);
  background: var(--ink-1); color: var(--paper);
  font-family: var(--font-sans); font-size: var(--t-sm); font-weight: 400;
  line-height: 1.5; letter-spacing: 0;
  text-align: left; border-radius: 0;
  box-shadow: 0 8px 24px rgba(0,0,0,0.30);
  opacity: 0; pointer-events: none;
  transition: opacity var(--t-fast) ease 0.15s;
  z-index: 50; white-space: normal;
}
.help::after {
  content: ""; position: absolute;
  bottom: calc(100% + 2px); left: 50%; transform: translateX(-50%);
  border: 6px solid transparent; border-top-color: var(--ink-1);
  opacity: 0; transition: opacity var(--t-fast) ease 0.15s;
}
.help:hover::before, .help:hover::after, .help:focus::before, .help:focus::after { opacity: 1; }
@media (max-width: 700px) {
  .help::before { width: 240px; left: 0; transform: none; }
  .help::after { left: 7px; transform: none; }
}
.run-presets {
  display: flex; align-items: center; gap: var(--s-2);
  margin-bottom: var(--s-3);
  font-family: var(--font-mono); font-size: var(--t-mono);
  color: var(--ink-3); letter-spacing: 0.04em;
}
.run-presets button {
  padding: var(--s-1) var(--s-3);
  font-family: var(--font-mono); font-size: var(--t-mono);
  letter-spacing: 0.06em; text-transform: lowercase;
  background: var(--paper); color: var(--ink-1);
  border: 1px solid var(--rule-2);
  cursor: pointer; border-radius: 0;
  transition: background var(--t-fast) var(--ease), color var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease);
}
.run-presets button:hover { background: var(--ink-1); color: var(--paper); border-color: var(--ink-1); }

/* ─── Mobile per-tile juxtapose slider ────────────────────────────────────
   Active only on touch-only devices (`hover: none` + `pointer: coarse`).
   Desktop and hybrid touch laptops keep the existing hover behaviour. */

/* Register --juxt-x as a <percentage> so @keyframes can interpolate it.
   Browsers without @property snap mid-animation but still land at the final
   value, so the feature degrades gracefully.
   inherits: TRUE is load-bearing — descendants of .tile read the variable
   from clip-path / left positions, and inherits:false silently overrides
   the default custom-property inheritance. (Locked by CLAUDE.md.) */
@property --juxt-x {
  syntax: '<percentage>';
  inherits: true;
  initial-value: 0%;
}

/* Entrance sweep: line starts at 0% (all LQ), races to 95% to fully reveal
   the AI-restored HQ, then eases back to 50% — the classic before/after
   midline split. Per-keyframe timing-functions: ease-out for the reveal so
   the line glides to a stop at 95%, ease-in-out for the return so it lands
   smoothly at 50%. User drag (with !important) overrides at any time. */
@keyframes juxt-entrance {
  0%   { --juxt-x: 0%;  animation-timing-function: cubic-bezier(.22,.61,.36,1); }
  40%  { --juxt-x: 95%; animation-timing-function: cubic-bezier(.42,0,.58,1); }
  100% { --juxt-x: 50%; }
}

@media (hover: hover) {
  /* Devices with a real pointer: hide the slider chrome entirely. */
  .juxt-line, .juxt-handle { display: none; }
}

@media (hover: none) and (pointer: coarse) {
  /* Touch-only: vertical pan default, horizontal drag intercepted in JS.
     The entrance animation runs once on page load; user drag (via inline
     style) wins immediately if they grab the slider before it finishes. */
  .tile {
    touch-action: pan-y;
    animation: juxt-entrance 3s forwards;
  }
  /* Stagger top tiles 200ms apart; capped at tile 6 so longer galleries
     don't trail off into a slow wave. */
  .tile:nth-child(2)    { animation-delay: 0.2s; }
  .tile:nth-child(3)    { animation-delay: 0.4s; }
  .tile:nth-child(4)    { animation-delay: 0.6s; }
  .tile:nth-child(5)    { animation-delay: 0.8s; }
  .tile:nth-child(n+6)  { animation-delay: 1s; }

  /* Suppress desktop hover-scale on touch — clip-path needs unscaled image
     coordinates, otherwise the visible split would not align with the handle. */
  .tile img { transform: none !important; }

  /* HQ image always visible on touch; revealed on the LEFT of the slider
     line via clip-path inset from the right. */
  .tile .thumb-hover {
    opacity: 1;
    clip-path: inset(0 calc(100% - var(--juxt-x, 50%)) 0 0);
    transition: clip-path 0.18s ease;
  }
  .tile.dragging .thumb-hover { transition: none; }

  .juxt-line {
    position: absolute; top: 0; bottom: 0;
    left: var(--juxt-x, 50%); width: 1.5px;
    background: rgba(255,255,255,0.55);
    box-shadow: 0 0 6px rgba(0,0,0,0.35);
    pointer-events: none; z-index: 3;
    transition: left 0.18s ease;
  }
  .juxt-handle {
    position: absolute; top: 50%; left: var(--juxt-x, 50%);
    transform: translate(-50%, -50%);
    width: 28px; height: 28px; border-radius: 50%;
    background: rgba(255,255,255,0.92);
    box-shadow: 0 2px 8px rgba(0,0,0,0.35);
    display: flex; align-items: center; justify-content: center;
    color: var(--ink-1);
    pointer-events: none; z-index: 4;
    transition: left 0.18s ease;
  }
  .juxt-handle svg { display: block; }
  .tile.dragging .juxt-line, .tile.dragging .juxt-handle { transition: none; }

  /* Hide tile-meta and the bottom gradient on touch — the slider IS the
     affordance. Bringing back the meta strip on touch would clutter the
     before/after composition. (Behaviour change: previous CSS exposed
     .tile-meta on :focus-within even on touch; in practice that selector
     never fired on touch-only devices, so the user-visible behaviour is
     unchanged. If we ever want a touch-tile caption, attach it next to the
     handle, not as a bottom strip.) */
  .tile::after, .tile-meta { display: none; }
}

@media (prefers-reduced-motion: reduce) and (hover: none) and (pointer: coarse) {
  .tile { animation: none; --juxt-x: 50%; }
  .tile .thumb-hover, .juxt-line, .juxt-handle { transition: none; }
}
