/*
  SOMEWHERE — Round 1 (FAMILY) landing page
  ------------------------------------------------------------------
  Design intent (v7): not a startup/service landing page. This should
  read as a single sheet of carefully edited print — the kind of
  handout you'd find at a small gallery, a call for submissions in an
  independent comics anthology, or the colophon page of a literary
  magazine. Quiet, a little imperfect, edited by a human hand.

  Palette: effectively two colors, plus one deliberately loud one.
    - warm off-white (paper)
    - near-black / soft charcoal ink — hairlines, small labels, and
      the submission entry all use this now (previously a pale blue;
      dropped so it wouldn't compete with the yellow below)
    - fluorescent yellow (#EFFF00) — the one accent, treated as a
      flat spot-ink color printed on top of the black-and-white photo
      book, not a UI accent. Rhythm across the four plates goes
      light -> strong -> strongest -> nearly gone (see the Visual
      scenes section and the HTML comment above the plates).

  No cards, no gradients, no shadows, no icons, no glow, no rounded
  corners, no animation beyond the smallest hover transitions and the
  sight-line growth on scroll.
*/

:root {
  --color-paper: #f4efe4;    /* warm off-white, unbleached paper */
  --color-ink: #1c1a17;      /* near-black ink */
  --color-ink-soft: #55504a; /* muted ink, for secondary text */
  --color-rule: rgba(28, 26, 23, 0.15); /* hairline dividers */
  --color-accent: #55504a;   /* hairlines/labels/CTA — soft charcoal, not a color */

  /* Fluorescent yellow — the page's one loud color. Flat spot-ink,
     always at full strength (#EFFF00 itself, no tints/opacity drops),
     never a wash, gradient, or glow. Used at the hero, once per
     plate (with escalating boldness — see "Visual scenes" below),
     and at the CTA. */
  --color-fluoro: #efff00;
  --color-fluoro-ink: #1c1a17; /* text set on top of the fluoro colour */

  /* Headings: a plain old-book/journal serif, not a display or
     "invitation" typeface. Body text uses the same family so the
     whole page reads as one continuous, readable setting.

     Widened for i18n (this pass): system-font fallbacks per script,
     so ja/zh-Hans/zh-Hant/ko/ar/hi/bn/th text never falls through to
     tofu/□ even though none of those languages are translated yet
     (see locales/*.json). No font files are downloaded for this —
     every name below is something the OS itself already ships or, on
     platforms that bundle Noto (Android/ChromeOS/many Linux
     desktops), a Noto family already installed system-wide. Order
     matters only a little here: browsers already substitute a
     system font for glyphs the earlier names in the list can't
     render, so this list mostly just makes that substitution more
     deliberate/consistent instead of leaving it entirely to whatever
     the browser would have picked anyway. */
  --font-serif: Charter, 'Iowan Old Style', Georgia, 'Times New Roman',
    'Hiragino Mincho ProN', 'Yu Mincho', 'Noto Serif CJK JP',
    'Songti SC', 'Noto Serif SC', 'SimSun',
    'Songti TC', 'Noto Serif TC', 'PMingLiU',
    'Apple SD Gothic Neo', 'Noto Serif KR', Batang,
    'Geeza Pro', 'Al Bayan', 'Noto Naskh Arabic',
    'Kohinoor Devanagari', 'Noto Serif Devanagari', Mangal,
    'Bangla Sangam MN', 'Noto Serif Bengali', Vrinda,
    Thonburi, 'Noto Serif Thai', 'Leelawadee UI',
    'Noto Serif', serif;

  /* Labels only (wordmark, ROUND 1 / FAMILY, dt labels, footer):
     a plain utilitarian sans with generous tracking. Same
     per-script fallback reasoning as --font-serif above. */
  --font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Helvetica Neue',
    Arial,
    'Hiragino Sans', 'Yu Gothic', 'Noto Sans CJK JP',
    'PingFang SC', 'Noto Sans SC', 'Microsoft YaHei',
    'PingFang TC', 'Noto Sans TC', 'Microsoft JhengHei',
    'Apple SD Gothic Neo', 'Noto Sans KR', 'Malgun Gothic',
    'Geeza Pro', 'Al Bayan', 'Noto Sans Arabic',
    'Kohinoor Devanagari', 'Noto Sans Devanagari', Mangal,
    'Bangla Sangam MN', 'Noto Sans Bengali', Vrinda,
    Thonburi, 'Noto Sans Thai', 'Leelawadee UI',
    'Noto Sans', sans-serif;

  --measure: 33rem; /* comfortable reading width for the statement */
  --container: 40rem;
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  background: var(--color-paper);
}

body {
  margin: 0;
  color: var(--color-ink);
  font-family: var(--font-serif);
  font-size: 1rem;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;

  /* Extremely faint paper grain. Self-contained (inline SVG noise,
     no external image, no photograph). Kept deliberately subtle —
     this should not read as a "texture", only feel slightly less
     flat than a solid color. */
  background-color: var(--color-paper);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch' result='noise'/%3E%3CfeColorMatrix in='noise' type='matrix' values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.025 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  background-repeat: repeat;
  background-blend-mode: multiply;
}

a {
  color: inherit;
}

.rule {
  border: none;
  border-top: 1px solid var(--color-rule);
  margin: 0;
}

/* ---------------------------------------------------------------
   Hero / first view
   Treated like the opening page of a printed booklet: left-set,
   not centered, with size/spacing contrast doing the work rather
   than sheer scale.
--------------------------------------------------------------- */

.hero {
  position: relative;
  overflow: hidden; /* clips .hero__ink-block so only part of it shows */
  padding: 3.75rem 1.5rem 4.5rem;
}

.hero__inner {
  max-width: var(--container);
  margin: 0 auto;
}

/* One flat fluorescent-yellow rectangle, offset behind the headline
   like a sheet of colored paper glued on slightly askew — not a
   highlighter box wrapped tightly around the text. It's a direct
   child of .hero (not .hero__inner) so its percentages and the
   overflow: hidden clip both read against .hero's full width;
   negative z-index keeps it below the (unpositioned, static) text
   content in the same stacking context regardless of DOM order.
   ~75% of the rectangle ends up clipped away, leaving roughly a
   quarter of it visible. */
.hero__ink-block {
  position: absolute;
  z-index: -1;
  top: 46%;
  left: 88%;
  width: 48%;
  height: 34%;
  background: var(--color-fluoro);
  pointer-events: none;
}

.wordmark {
  margin: 0 0 3.25rem;
  font-family: var(--font-sans);
  font-size: 0.85rem;
  font-weight: 600;
  letter-spacing: 0.34em;
  text-transform: uppercase;
}

.round-tag {
  margin: 0 0 2.75rem;
  font-family: var(--font-sans);
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.26em;
  text-transform: uppercase;
  color: var(--color-accent);
  line-height: 2;
}

.hero__headline {
  margin: 0;
  display: flex;
  flex-direction: column;
  font-weight: 400;
  letter-spacing: -0.005em;
  line-height: 1.22;
  font-size: clamp(1.85rem, 8.2vw, 3.1rem);
}

.hero__line--offset {
  margin-left: clamp(0.75rem, 7vw, 3rem);
  margin-top: 0.15em;
}

/* "Sight line" — a thin rule in the one accent color, standing for a
   line of sight reaching out toward somewhere else. Used once under
   the hero headline, once per plate, and once near the CTA. Always a
   flat 1px rule; never a block, never a fill. Length varies by
   context (see each usage) but the growth behavior is shared. */
.grow-line {
  height: 1px;
  background: var(--color-accent);
  transform-origin: left center;
}

.hero .grow-line {
  width: 9rem;
  max-width: 40%;
  margin-top: 1.75rem;
}

/* Default state: fully visible, no animation — this only becomes a
   "grows on scroll" element once JS confirms IntersectionObserver
   support and the visitor has not requested reduced motion (see
   .will-animate below). A visitor with JS disabled, or who prefers
   reduced motion, simply sees the line already drawn. */
@media (prefers-reduced-motion: no-preference) {
  .grow-line.will-animate {
    transform: scaleX(0);
    transition: transform 1.1s ease-out;
  }

  .grow-line.will-animate.is-visible {
    transform: scaleX(1);
  }
}

/* ---------------------------------------------------------------
   Visual scenes (v6)
   Full-bleed "plates" between paragraphs of the letter: one looping
   video (Tokyo), then three still photographs. Photography is kept
   quiet (low saturation, moderate contrast, slightly faded) — closer
   to a printed photo book than a crisp digital photo. Line art is an
   empty, ready slot for now — see the HTML comment above the visual
   sequence for why, and what's still needed.

   No color block of any kind sits on top of a photo in this version.
   The only accent color per plate is a thin .grow-line, placed
   outside the photo (between the frame and its caption), so nothing
   ever covers the people or buildings in the picture.

   .image-slot is a self-contained frame:
     - shows a quiet placeholder pattern (not a flat grey box) when
       no photo/video is present yet
     - never shows a broken-image icon: any missing file hides
       itself via onerror, falling back to the layer below it, and
       finally to the placeholder pattern
--------------------------------------------------------------- */

.visual-scene {
  margin: 0;
}

.visual-scene__stage {
  position: relative;
}

/* Editorial rhythm: not every plate is full-bleed at the same size.
   .visual-scene--inset pulls a plate in from the right and caps its
   width, so it reads as a smaller print tipped into the page rather
   than a repeat of the plate before it. Currently used for the two
   "inside a home" plates (II and V) so they visually echo each
   other at the same position. */
.visual-scene--inset .visual-scene__stage {
  max-width: 30rem;
  margin-left: clamp(1.5rem, 12vw, 5rem);
  margin-right: 0;
}

.visual-scene--inset .visual-scene__plate {
  text-align: right;
  padding-right: clamp(1.5rem, 12vw, 5rem);
}

.image-slot {
  position: relative;
  z-index: 1;
  width: 100%;
  aspect-ratio: var(--scene-ratio, 4 / 5);
  overflow: hidden;
  border-top: 1px solid var(--color-rule);
  border-bottom: 1px solid var(--color-rule);
  background:
    repeating-linear-gradient(135deg, rgba(28, 26, 23, 0.05) 0 1px, transparent 1px 14px),
    linear-gradient(180deg, #efe8d9, #e4dac5);
}

/* ---------------------------------------------------------------
   Fluorescent yellow per plate (v9)
   Same flat spot-ink color throughout (#EFFF00, full strength,
   no gradient/glow/shadow/radius).

   Plate I is now the only plate using .plate-ink-block: a "behind"
   block that lives in .visual-scene__stage, alongside (not inside)
   .image-slot, so it can extend past the frame's own edge without
   being clipped by the frame's own overflow: hidden. z-index 0,
   below .image-slot's z-index: 1 — never touches the photo itself.

   Plates II, III, IV, and V all use the shared-rectangle system
   further down (.plate-mask / .plate-lineart) instead — see that
   section. */

.plate-ink-block {
  position: absolute;
  z-index: 0;
  background: var(--color-fluoro);
  pointer-events: none;
}

/* Plate I — light. A single large block tucked behind the frame's
   lower edge; bold in size but kept out of the photo entirely. */
.plate-ink-block--tokyo {
  right: 6%;
  bottom: -2.5rem;
  width: 34%;
  height: 5.5rem;
}

/* Plates II, III, IV, and V no longer use .plate-ink-block — all four
   moved to the shared-rectangle .plate-mask/.plate-lineart system
   below. Only Plate I still uses .plate-ink-block. */

/* ---------------------------------------------------------------
   Drawn line art through a clean rectangle (v18) — Plates II, III,
   IV, V
   All four "interior/street" plates use real drawn line art (see
   SOURCE_NOTES.md for provenance — provided by the user, generated
   externally; this environment has no image-generation tool).

   The fluorescent yellow stands for the viewer's own eye/gaze, not a
   decorative background: a photo, seen through it, becomes a comic.
   That relationship needs to read at a glance, so the opening is a
   plain, clearly geometric rectangle — no torn/irregular edge, no
   gradient/fade/brush texture. .plate-mask (the yellow fill) and
   .plate-lineart (the drawing, mix-blend-mode: multiply) share the
   exact same rectangle via four custom properties —
   --mask-top/right/bottom/left, set once inline on each plate's
   .image-slot — so the two can never drift apart. These four values
   are static (not animated) — see .reveal-window below for how the
   growth animation actually works.

   Rectangle size/position is deliberately different per plate, never
   the same template repeated — see each plate's inline style and the
   HTML comment above the visual sequence:
     Plate II   fairly large, bottom-right (includes the person)
     Plate III  the biggest of the four — both people head-to-toe,
                plus a little of the stall/produce around them
     Plate IV   left side of frame, near-full height — the two people
                on the sidewalk, head to toe
     Plate V    the smallest, a small corner over the stove only

   Stacking inside .image-slot: photo (1) -> .reveal-window (2, the
   element the growth animation actually applies to) containing
   .plate-mask (flat yellow, opaque) below .plate-lineart
   (mix-blend-mode: multiply, so white background lets the yellow
   show through and dark ink reads as ink) -> vignette (::after, 6). */
.plate-mask {
  position: absolute;
  inset: 0;
  z-index: 1;
  background: var(--color-fluoro);
  clip-path: inset(
    var(--mask-top, 0%) var(--mask-right, 0%) var(--mask-bottom, 0%) var(--mask-left, 0%)
  );
  pointer-events: none;
}

.plate-lineart {
  position: absolute;
  inset: 0;
  z-index: 2;
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  mix-blend-mode: multiply;
  pointer-events: none;
  /* No clip-path here (deliberately, as of the Plate V px-correction
     pass below) — .reveal-window, this element's parent, already
     clips to the exact same --mask-* rectangle, so relying on that
     alone is behaviorally identical here for every plate that doesn't
     transform this element. It's required for Plate V specifically:
     a translate() on an element clips against its OWN (already-
     translated) box, so a clip-path directly on this element would
     have shifted the visible WINDOW along with the content — moving
     the yellow/line-art boundary itself, which must stay fixed. With
     the clip-path only on the untransformed parent, the window stays
     exactly where .plate-mask's matching clip-path says it should,
     and only this element's painted content shifts. */
}

/* europe-kitchen-lineart-v2.png replaces the original
   europe-kitchen-lineart.png (kept, unused — see SOURCE_NOTES.md): a
   full-scene redraw of the whole kitchen, not just the stove/counter
   corner, and a much more precise trace of europe-home-photo.jpg —
   confirmed by overlaying the two at plain object-position: center,
   where the window frame, cabinet, shelf, radiator, and floor lines
   all land almost exactly on the photo's own edges across the entire
   frame.

   Two `translate()` corrections were tried and removed again — see
   SOURCE_NOTES.md for the full history — because each one, tuned to
   match objects in one part of the (then much smaller) mask window,
   made objects in another part worse: the underlying distortion
   between this line art and the photo isn't uniform, so no single
   shift can be exactly right everywhere at once. Rather than keep
   chasing that, the mask itself was widened from "just the stove
   corner" to the whole left counter (see the HTML comment on this
   plate) — with that much more of the frame in view, whatever small
   drift remains reads as a natural hand-drawn quality throughout
   rather than a jump at one specific seam, and a plain, unshifted
   `object-position: center` (matching the photo, the CSS default —
   no override needed here at all) checked out as the best overall fit
   once re-verified against this wider area: sink, dish rack, window
   edge, radiator, pan, mug, and burners all read as single, clean
   outlines together, not just whichever one object a translate had
   been tuned for. */

/* Reveal animation (v17 — fade, replacing the v15/v16 clip-path
   wipe): "photo -> the yellow rectangle -> the area it covers becomes
   line art" is now a plain opacity fade on .reveal-window, not a
   directional curtain. .reveal-window's clip-path is ALWAYS the
   plate's final rectangle (same --mask-* values .plate-mask/
   .plate-lineart use) — it never changes — so the yellow+line-art
   shape itself is correct and stationary from the very first paint;
   only the wrapper's opacity animates, 0 -> 1. Because yellow and
   line art still live inside one wrapper and fade as one unit, they
   remain structurally incapable of appearing out of sync with each
   other — same guarantee as the wipe had, simpler mechanism.
   (data-wipe on each plate's .reveal-window in the HTML is left over
   from the wipe version and no longer read by any CSS here — harmless
   but unused.)

   Default (no JS, or prefers-reduced-motion: reduce): no .reveal-armed
   class, so .reveal-window has no opacity override at all and each
   plate's full final photo + yellow + line art shows immediately. The
   page never depends on JS or motion to be usable.

   When JS confirms IntersectionObserver support and the visitor has
   not asked for reduced motion, it adds .reveal-armed up front (see
   the script at the end of the body), which sets opacity: 0 with no
   transition — an instant, invisible snap, not an animated fade-out.
   Once the plate is meaningfully in view (a separate, higher-
   threshold IntersectionObserver watching each plate's .image-slot —
   not .reveal-window itself, see the HTML comment above the visual
   sequence for why), JS adds .is-visible, which fades opacity back to
   1 over 1.8s (1.5s on Plate IV — see its own rule below). Plays
   once — the observer unobserves after the first intersection, so
   scrolling back and forth does not replay it. */
.reveal-window {
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  clip-path: inset(
    var(--mask-top, 0%) var(--mask-right, 0%) var(--mask-bottom, 0%) var(--mask-left, 0%)
  );
}

@media (prefers-reduced-motion: no-preference) {
  /* No `transition` here: going from the default (fully opaque) state
     to "armed" (invisible) must be an instant snap, not an animated
     fade-out. (This bit the wipe version too, in an earlier pass, when
     `transition` lived on .reveal-armed alone — the very first style
     change JS ever makes, snapping shut, got animated too, playing a
     spurious effect on page load before anything had scrolled into
     view. Keeping `transition` scoped to only the .is-visible rule
     below avoids that regardless of which property animates.) */
  .reveal-window.reveal-armed {
    opacity: 0;
  }

  /* Only the closed -> open step is animated. */
  .reveal-window.reveal-armed.is-visible {
    opacity: 1;
    transition: opacity 1.8s cubic-bezier(0.22, 0.61, 0.36, 1);
  }

  /* Plate IV (New York) only: a staggered two-part fade instead of
     the single shared opacity fade above, per instruction — "photo ->
     yellow appears -> line art fades in on top of the yellow" as two
     separate, slightly offset steps, rather than one flat cross-fade.
     Overrides .reveal-window's own opacity so the wrapper itself stays
     fully opaque throughout; .plate-mask and .plate-lineart (its two
     children) each get their own opacity transition instead, still
     keyed off the exact same .reveal-armed / .is-visible classes the
     shared IntersectionObserver already adds to .reveal-window — no
     JS changes needed, only different CSS reacting to it.
       yellow (.plate-mask):    0 -> 1 opacity, 0.6s, no delay
       line art (.plate-lineart): 0 -> 1 opacity, 1.5s, 0.3s delay
     0.3s + 1.5s = 1.8s total, landing in the requested ~1.6-2.0s
     window, with the line art beginning a beat after the yellow
     rather than in lockstep with it. */
  [data-scene="new-york"] .reveal-window.reveal-armed {
    opacity: 1;
  }

  [data-scene="new-york"] .reveal-window.reveal-armed .plate-mask,
  [data-scene="new-york"] .reveal-window.reveal-armed .plate-lineart {
    opacity: 0;
  }

  [data-scene="new-york"] .reveal-window.reveal-armed.is-visible .plate-mask {
    opacity: 1;
    transition: opacity 0.6s cubic-bezier(0.22, 0.61, 0.36, 1);
  }

  [data-scene="new-york"] .reveal-window.reveal-armed.is-visible .plate-lineart {
    opacity: 1;
    transition: opacity 1.5s cubic-bezier(0.22, 0.61, 0.36, 1) 0.3s;
  }
}

/* ---------------------------------------------------------------
   Comic fragments (v8) — Plate I only now
   "Photo = reality. Fluorescent yellow = a hole torn in reality.
   Comic fragment = another world, glimpsed for a moment through
   that hole." A fragment is a small transparent PNG showing one or
   two specific, deliberately arbitrary things — never a full
   illustrated scene. Plates II, III, IV, and V have all moved to the
   photo-derived line art above; Plate I is the only plate still using
   an invented fragment.

   Every fragment:
     - sits on/near its plate's yellow block, with part of its own
       bounding box extending past that block's edge into the photo
       or the paper margin (the "leaking out of the hole" effect —
       achieved simply by sizing/positioning each fragment slightly
       larger or offset from the block it's paired with, since the
       PNG itself is expected to have transparent space around the
       actual drawn marks)
     - object-fit: contain, so the whole fragment is visible rather
       than cropped — these are small deliberate marks, not photos
     - mix-blend-mode: multiply, so dark ink reads as ink on the
       yellow/photo beneath it rather than a flat opaque sticker
     - a slight rotation, as if placed by hand rather than aligned
       to a grid
   No two fragments share the same width, opacity, or rotation. */
.comic-fragment {
  position: absolute;
  display: block;
  height: auto;
  object-fit: contain;
  mix-blend-mode: multiply;
  pointer-events: none;
}

/* Plate I: the smallest and most restrained fragment on the page.
   Sits on the yellow sliver peeking below the frame (z-index 2,
   above that block's 0), offset slightly left of it so a little of
   the fragment's own space grazes past the block into the paper. */
.comic-fragment--shibuya {
  z-index: 2;
  right: 16%;
  bottom: -2.7rem;
  width: 15%;
  opacity: 0.9;
  transform: rotate(-3deg);
}


/* Vignette + very fine film grain, folded into one layer so no extra
   DOM elements are needed. Sits above everything inside the frame,
   including a comic fragment (Plate I) — keeps the fragment under the
   same paper texture as the photo around it. */
.image-slot::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 6;
  background-image:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='2' stitchTiles='stitch' result='n'/%3E%3CfeColorMatrix in='n' type='matrix' values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.09 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23g)'/%3E%3C/svg%3E"),
    linear-gradient(180deg, rgba(28, 26, 23, 0.04), rgba(28, 26, 23, 0.24));
  background-repeat: repeat, no-repeat;
  background-size: 140px 140px, cover;
  mix-blend-mode: multiply, multiply;
  pointer-events: none;
}

/* Photography stays quiet on purpose (see file header): low
   saturation, a little warmth, moderate (not flat, not punchy)
   contrast, slightly faded brightness — closer to an old printed
   photo book than a crisp digital photograph. */
.photo-layer,
.video-scene {
  position: absolute;
  inset: 0;
  z-index: 1;
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  filter: saturate(0.35) sepia(0.12) contrast(0.9) brightness(1.03);
}

.video-scene {
  z-index: 2;
}

/* Per-plate sight line: sits between the frame and its caption, in
   the page's own whitespace — never over the photo. Length varies a
   little per plate for rhythm (see inline width on each in the
   HTML); Plate V's is the shortest, matching its quieter mood. */
.visual-scene .grow-line {
  margin: 0.85rem 0 0.6rem;
}

.visual-scene--inset .grow-line {
  margin-left: auto;
  margin-right: clamp(1.5rem, 12vw, 5rem);
}

.visual-scene:not(.visual-scene--inset) .grow-line {
  margin-left: 1.5rem;
}

.visual-scene__plate {
  max-width: var(--container);
  margin: 0 auto;
  padding: 0 1.5rem;
  font-family: var(--font-sans);
  font-size: 0.68rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--color-ink-soft);
}

@media (prefers-reduced-motion: reduce) {
  .video-scene {
    display: none;
  }
}

/* Scroll reveal: only applied once JS confirms IntersectionObserver
   support and the visitor has not requested reduced motion. Without
   JS, .image-slot never receives .will-animate and stays fully
   visible — the page never depends on this to be usable. Shares the
   same "armed" class name as .grow-line (see above) so one script
   loop can handle both. */
@media (prefers-reduced-motion: no-preference) {
  .image-slot.will-animate {
    opacity: 0;
    transform: translateY(14px);
    transition: opacity 0.9s ease, transform 0.9s ease;
  }

  .image-slot.will-animate.is-visible {
    opacity: 1;
    transform: translateY(0);
  }
}

/* ---------------------------------------------------------------
   Statement
   Read as a short letter, not a poem: normal prose wrapping, no
   forced line breaks. Readability first.
--------------------------------------------------------------- */

.statement {
  padding: 3rem 1.5rem;
}

.statement__inner {
  max-width: var(--measure);
  margin: 0 auto;
}

.statement p {
  margin: 0 0 1.7em;
  font-size: clamp(1.05rem, 2.3vw, 1.15rem);
  line-height: 1.8;
  color: var(--color-ink);
}

.statement p:last-child {
  margin-bottom: 0;
}

.statement__opening {
  color: var(--color-ink-soft);
}

/* ---------------------------------------------------------------
   Overview — set like a colophon / exhibition handout, not a
   feature list. Hairlines and whitespace only, no boxes.
--------------------------------------------------------------- */

.overview {
  padding: 3.5rem 1.5rem 4rem;
}

.overview__inner {
  max-width: var(--measure);
  margin: 0 auto;
}

.overview__kicker {
  margin: 0 0 2.75rem;
  font-family: var(--font-sans);
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--color-ink-soft);
}

.overview dl {
  margin: 0;
}

.overview__item {
  padding: 1.5rem 0;
  border-top: 1px solid var(--color-rule);
}

.overview__item:last-child {
  border-bottom: 1px solid var(--color-rule);
}

.overview dt {
  font-family: var(--font-sans);
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--color-ink-soft);
  margin-bottom: 0.55rem;
}

.overview dd {
  margin: 0;
  font-size: 1.02rem;
  line-height: 1.7;
}

.overview dd.overview__emphasis {
  font-size: clamp(1.1rem, 3.6vw, 1.3rem);
  line-height: 1.45;
  color: var(--color-accent);
}

/* ---------------------------------------------------------------
   Big type / breath section
   A declaration, set with a slight editorial stagger rather than
   perfectly centered blocks. Dark ground, no gradient.
--------------------------------------------------------------- */

.big-type {
  padding: 5rem 1.5rem;
  background: var(--color-ink);
  color: var(--color-paper);
}

.big-type__inner {
  max-width: var(--container);
  margin: 0 auto;
}

.big-type__line {
  margin: 0;
  font-weight: 400;
  letter-spacing: -0.005em;
  line-height: 1.15;
  font-size: clamp(1.85rem, 8vw, 3rem);
}

.big-type__line--offset {
  margin-top: 0.85em;
  margin-left: clamp(0.5rem, 6vw, 2.5rem);
  color: rgba(244, 239, 228, 0.82);
}

/* ---------------------------------------------------------------
   CTA — a quiet entry point, not a service-style call-to-action
   button. Set like the last line of a printed application form:
   hairlines above and below, no fill, minimal hover.
--------------------------------------------------------------- */

.cta {
  padding: 4.5rem 1.5rem 5.5rem;
}

.cta__inner {
  position: relative;
  max-width: var(--container);
  margin: 0 auto;
}

.cta-entry {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.65rem;
  width: 100%;
  padding: 1.15rem 1rem;
  border-top: 1px solid var(--color-accent);
  border-bottom: 1px solid var(--color-accent);
  text-decoration: none;
  color: var(--color-ink);
  transition: color 0.15s ease, border-color 0.15s ease;
}

.cta-entry__mark {
  font-family: var(--font-serif);
  font-size: 1.1rem;
  color: var(--color-accent);
  transition: transform 0.15s ease;
}

/* Fluorescent-yellow accent (3 of 3): a slender band behind just the
   label text, not the whole button — the arrow and the hairlines
   stay as they are, so this reads as a printed spot-color mark next
   to the entry rather than a "yellow web button". */
.cta-entry__label {
  background: var(--color-fluoro);
  color: var(--color-fluoro-ink);
  font-family: var(--font-sans);
  font-size: 0.92rem;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  padding: 0.2em 0.5em;
  box-decoration-break: clone;
  -webkit-box-decoration-break: clone;
}

.cta-entry:hover,
.cta-entry:focus-visible {
  color: var(--color-accent);
}

.cta-entry:hover .cta-entry__mark,
.cta-entry:focus-visible .cta-entry__mark {
  transform: translateX(0.2em);
}

/* ---------------------------------------------------------------
   Footer
--------------------------------------------------------------- */

.site-footer {
  padding: 2.75rem 1.5rem 3.5rem;
  text-align: center;
  font-family: var(--font-sans);
  color: var(--color-ink-soft);
}

.site-footer p {
  margin: 0;
  font-size: 0.75rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

.site-footer p:first-child {
  margin-bottom: 0.5rem;
  font-weight: 600;
  color: var(--color-ink);
}

/* ---------------------------------------------------------------
   Slightly larger screens: a little more room, and a touch more
   asymmetry between sections (a slightly narrower overview column
   than the statement above it). Mobile remains the primary target;
   this is a light enhancement only.
--------------------------------------------------------------- */

@media (min-width: 640px) {
  .hero {
    padding: 5.5rem 1.5rem 6rem;
  }

  .statement {
    padding-top: 4.5rem;
    padding-bottom: 4.5rem;
  }

  .overview {
    padding-top: 5rem;
    padding-bottom: 5.5rem;
  }

  .overview__inner {
    max-width: 30rem;
  }

  .big-type {
    padding: 7rem 1.5rem;
  }

  .cta {
    padding-top: 5.5rem;
    padding-bottom: 7rem;
  }

  .cta-entry {
    width: auto;
    min-width: 20rem;
    margin: 0 auto;
    padding-left: 2rem;
    padding-right: 2rem;
  }
}

/* ---------------------------------------------------------------
   i18n (added this pass — see locales/*.json, script.js)

   Three independent things live in this section:
     1. The language <select>, styled to disappear into the page.
     2. Per-script letter-spacing overrides.
     3. RTL (Arabic only, for now).

   None of this touches .visual-scene, .image-slot, .reveal-window,
   .plate-mask, .plate-lineart, or any plate/photo positioning rule
   above — per instruction, photo placement, the yellow rectangles,
   and the line-art reveal are staying exactly as they are regardless
   of language or direction.
--------------------------------------------------------------- */

/* 1. Language selector — a native <select> (see the HTML comment
   where it's declared): real keyboard/screen-reader behavior for
   free, no custom open/close JS. Visually it's meant to read as a
   small printed marginal note, not a UI control — no border, fill,
   or focus ring of its own beyond the default outline, which is left
   alone for accessibility. Positioned in the hero's own top padding,
   not fixed/sticky, so it scrolls away with the rest of the hero
   rather than sitting over the photos below. */
.lang-selector {
  position: absolute;
  top: 1.1rem;
  right: 1.25rem;
  z-index: 2;
  display: flex;
  align-items: center;
  gap: 0.15rem;
  padding: 0.2rem 0.3rem;
  font-family: var(--font-sans);
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-ink-soft);
}

/* The two visible pieces of the closed control ("EN" + "▾"). Plain
   text, no pill/border/background — updated from script.js whenever
   the locale changes. Never interactive themselves; the real <select>
   underneath (below) is what actually receives clicks/keyboard focus. */
.lang-selector__code,
.lang-selector__arrow {
  pointer-events: none;
}

.lang-selector__arrow {
  font-size: 0.6rem;
}

/* The actual control. Sized to cover the whole "EN ▾" label exactly
   (position: absolute + inset: 0 against .lang-selector's own box) and
   made fully invisible with opacity, not visibility/display, so it
   stays focusable and clickable — a real native <select>, just drawn
   with zero opacity so the two spans above show through instead of its
   own chrome. Opening it still lists every language by its own native
   name via the <option> text set in script.js. */
.lang-selector select {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  border: none;
  margin: 0;
  padding: 0;
  cursor: pointer;
}

.lang-selector:focus-within {
  outline: 1px solid var(--color-accent);
  outline-offset: 2px;
}

@media (min-width: 640px) {
  .lang-selector {
    top: 1.5rem;
    right: 1.75rem;
  }
}

/* 2. Letter-spacing per script. The tracking values used across the
   page (.wordmark, .round-tag, .visual-scene__plate, .overview__kicker,
   .overview dt, .cta-entry__label, .site-footer p — all generous
   positive tracking on uppercase Latin labels — plus the small
   *negative* tracking on .hero__headline / .big-type__line, an
   optical tightening for large Latin display type) exist because
   Latin capitals read better with a little extra air at these sizes.
   The same values read as broken, uneven spacing once real text
   lands in a script that doesn't work that way — per instruction,
   CJK gets reduced (not zero) tracking, and Arabic/Hindi/Bengali/Thai
   get none at all.

   Keyed off [data-script] on <html>, set by script.js alongside
   lang/dir on every locale switch — see setLocale() there. Grouping
   by script (rather than repeating every rule per language code) is
   what keeps this from being 4x/6x as long; add a new CJK-like
   language later by adding its code to CJK_LOCALES in script.js, not
   by touching this CSS. */
html[data-script='cjk'] .wordmark { letter-spacing: 0.05em; }
html[data-script='cjk'] .round-tag { letter-spacing: 0.04em; }
html[data-script='cjk'] .overview__kicker { letter-spacing: 0.03em; }
html[data-script='cjk'] .visual-scene__plate,
html[data-script='cjk'] .overview dt,
html[data-script='cjk'] .cta-entry__label,
html[data-script='cjk'] .site-footer p,
html[data-script='cjk'] .submit-form__section legend,
html[data-script='cjk'] .dropzone__or,
html[data-script='cjk'] .dropzone__button,
html[data-script='cjk'] .submit-button__label {
  letter-spacing: 0.02em;
}
html[data-script='cjk'] .hero__headline,
html[data-script='cjk'] .big-type__line,
html[data-script='cjk'] .submit__heading {
  letter-spacing: 0;
}

html[data-script='no-tracking'] .wordmark,
html[data-script='no-tracking'] .round-tag,
html[data-script='no-tracking'] .overview__kicker,
html[data-script='no-tracking'] .visual-scene__plate,
html[data-script='no-tracking'] .overview dt,
html[data-script='no-tracking'] .cta-entry__label,
html[data-script='no-tracking'] .site-footer p,
html[data-script='no-tracking'] .hero__headline,
html[data-script='no-tracking'] .big-type__line,
html[data-script='no-tracking'] .submit__heading,
html[data-script='no-tracking'] .submit-form__section legend,
html[data-script='no-tracking'] .dropzone__or,
html[data-script='no-tracking'] .dropzone__button,
html[data-script='no-tracking'] .submit-button__label {
  letter-spacing: normal;
}

/* 3. RTL (Arabic only right now — see RTL_LOCALES in script.js).
   dir="rtl" on <html> already right-aligns and reverses the reading
   order of ordinary block/inline text on its own, which is exactly
   what's wanted for the statement/requirements/footer prose — no
   extra CSS needed there. The two places that need a deliberate
   touch are a physical arrow glyph (flipped so "forward" still
   points the right way) and confirming nothing here reaches into
   .visual-scene/.image-slot territory, which stays as-is by design. */
[dir='rtl'] .cta-entry__mark {
  display: inline-block;
  transform: scaleX(-1);
}

/* ---------------------------------------------------------------
   Submission page (submit.html)
   Same paper/ink/fluoro system as the rest of the site — a printed
   form, not a SaaS upload widget. No cards, no rounded corners, no
   drop shadows. The dropzone is the one place fluoro appears here,
   and only as a thin accent on hover/dragover, not a filled block.
--------------------------------------------------------------- */

.hero--compact {
  padding: 1.75rem 1.5rem 2.25rem;
}

.hero--compact .hero__inner {
  max-width: var(--container);
  margin: 0 auto;
}

.wordmark--link {
  display: inline-block;
  margin: 0;
  text-decoration: none;
  color: inherit;
}

.submit {
  padding: 2.5rem 1.5rem 4rem;
}

.submit__inner {
  max-width: var(--measure);
  margin: 0 auto;
}

.submit__heading {
  margin: 0 0 1rem;
  font-family: var(--font-sans);
  font-size: 1.4rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.submit__intro {
  margin: 0 0 2.75rem;
  color: var(--color-ink-soft);
  font-size: 1rem;
  line-height: 1.7;
}

.submit-form__section {
  margin: 0 0 2.25rem;
  padding: 1.75rem 0 0;
  border: none;
  border-top: 1px solid var(--color-rule);
}

.submit-form__section legend {
  padding: 0;
  margin-bottom: 1.25rem;
  font-family: var(--font-sans);
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--color-ink-soft);
}

.submit-form__honeypot {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

.form-field {
  margin-bottom: 1.4rem;
}

.form-field label {
  display: block;
  margin-bottom: 0.5rem;
  font-family: var(--font-sans);
  font-size: 0.85rem;
  font-weight: 600;
}

.form-field__note {
  font-weight: 400;
  color: var(--color-ink-soft);
  text-transform: none;
  letter-spacing: normal;
  margin-left: 0.35em;
}

.form-field input[type="text"],
.form-field input[type="email"],
.form-field textarea {
  width: 100%;
  padding: 0.65rem 0.75rem;
  font: inherit;
  font-size: 1rem;
  color: var(--color-ink);
  background: var(--color-paper);
  border: 1px solid var(--color-rule);
  border-radius: 0;
  outline: none;
  transition: border-color 0.15s ease;
}

.form-field input[type="text"]:focus,
.form-field input[type="email"]:focus,
.form-field textarea:focus {
  border-color: var(--color-accent);
}

.form-field textarea {
  resize: vertical;
  line-height: 1.6;
}

.form-field__error {
  margin: 0.5rem 0 0;
  font-family: var(--font-sans);
  font-size: 0.82rem;
  color: #a8341f;
  min-height: 0;
}

.form-field__error:empty {
  display: none;
}

/* Dropzone: a large bordered area, not a filled card. Border goes
   from a plain hairline to the fluoro accent only while a file is
   being dragged over it — a small, deliberate signal rather than a
   colored block. */
.dropzone {
  position: relative;
  padding: 2.5rem 1.25rem;
  text-align: center;
  border: 1px dashed var(--color-rule);
  transition: border-color 0.15s ease, background-color 0.15s ease;
}

.dropzone.is-dragover {
  border-color: var(--color-fluoro);
  border-style: solid;
  background: rgba(239, 255, 0, 0.08);
}

.dropzone__input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  cursor: pointer;
}

.dropzone__text-desktop {
  margin: 0 0 0.75rem;
  font-size: 1rem;
  color: var(--color-ink-soft);
}

.dropzone__or {
  margin: 0 0 0.75rem;
  font-family: var(--font-sans);
  font-size: 0.75rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--color-ink-soft);
}

.dropzone__button {
  display: inline-block;
  padding: 0.55rem 1.25rem;
  font-family: var(--font-sans);
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  border: 1px solid var(--color-accent);
  cursor: pointer;
  pointer-events: none; /* the real, invisible file input handles clicks */
}

.dropzone__hint {
  margin: 1rem 0 0;
  font-family: var(--font-sans);
  font-size: 0.72rem;
  color: var(--color-ink-soft);
}

/* On touch/mobile, dragging isn't the primary interaction — hide the
   "drag & drop" copy and "or" divider so only "Choose files" shows,
   per instruction. Detected via a coarse-pointer media query, not
   viewport width, so it tracks input method rather than screen size. */
@media (pointer: coarse) {
  .dropzone__text-desktop,
  .dropzone__or {
    display: none;
  }
}

.file-list {
  list-style: none;
  margin: 1rem 0 0;
  padding: 0;
}

.file-list:empty {
  margin: 0;
}

.file-list li {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.6rem 0;
  border-top: 1px solid var(--color-rule);
  font-size: 0.9rem;
}

.file-list__name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.file-list__size {
  flex-shrink: 0;
  color: var(--color-ink-soft);
  font-family: var(--font-sans);
  font-size: 0.78rem;
}

.file-list__remove {
  flex-shrink: 0;
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  font-family: var(--font-sans);
  font-size: 0.78rem;
  text-decoration: underline;
  cursor: pointer;
  color: var(--color-ink-soft);
}

.form-consent {
  display: flex;
  align-items: flex-start;
  gap: 0.65rem;
  margin-bottom: 1.25rem;
  font-size: 0.92rem;
  line-height: 1.55;
  cursor: pointer;
}

.form-consent input[type="checkbox"] {
  flex-shrink: 0;
  width: 1.1rem;
  height: 1.1rem;
  margin-top: 0.2rem;
  accent-color: var(--color-accent);
}

.form-consent__note {
  display: block;
  margin-top: 0.35rem;
  color: var(--color-ink-soft);
  font-size: 0.85rem;
}

/* Cloudflare's own widget (an iframe) — only margin is ours to set. */
.cf-turnstile {
  margin: 1.75rem 0 0.5rem;
}

.submit-form__actions {
  margin-top: 1.25rem;
}

.submit-button {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  padding: 1.15rem 1rem;
  background: none;
  border-top: 1px solid var(--color-accent);
  border-bottom: 1px solid var(--color-accent);
  border-left: none;
  border-right: none;
  cursor: pointer;
  transition: opacity 0.15s ease;
}

.submit-button:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.submit-button__label {
  background: var(--color-fluoro);
  color: var(--color-fluoro-ink);
  font-family: var(--font-sans);
  font-size: 0.92rem;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  padding: 0.2em 0.5em;
  box-decoration-break: clone;
  -webkit-box-decoration-break: clone;
}

.submit-success {
  max-width: var(--measure);
  margin: 0 auto;
  padding: 2.5rem 0;
  text-align: left;
}

.submit-success h2 {
  margin: 0 0 1rem;
  font-family: var(--font-sans);
  font-size: 1.2rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.submit-success p {
  margin: 0 0 0.75rem;
  line-height: 1.7;
}

.submit-success__id {
  margin-top: 1.5rem;
  font-family: var(--font-sans);
  font-size: 0.85rem;
  color: var(--color-ink-soft);
}

.submit-success__id span#submission-id {
  color: var(--color-ink);
  font-weight: 600;
}
