/* ============================================================================
   mariopodolan.work — no-box, typography-first design system
   Canonical reference: design-language.md (locked direction, 2026-08-17/20)
   Hard rules: no cards, panels, chips, pills, tables-as-UI, tabs, frames,
   borders, container radius, backdrop blur, decorative shadows, <hr>.
   Structure = whitespace, full-bleed imagery, gradients/vignettes, opacity,
   scale, typographic rhythm. Accent is ink, never fill.
   Sole exception: .source-document (the CV) reproduces the supplied ODT.
   ========================================================================== */

/* --------------------------------------------------------------------------
   Fonts — self-hosted, font-display: swap
   -------------------------------------------------------------------------- */

/* JetBrains Mono — latin */
@font-face {
  font-family: "JetBrains Mono";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/assets/fonts/jetbrains-mono/latin-400-normal.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "JetBrains Mono";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("/assets/fonts/jetbrains-mono/latin-500-normal.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "JetBrains Mono";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("/assets/fonts/jetbrains-mono/latin-600-normal.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "JetBrains Mono";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("/assets/fonts/jetbrains-mono/latin-700-normal.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "JetBrains Mono";
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url("/assets/fonts/jetbrains-mono/latin-400-italic.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

/* JetBrains Mono — latin-ext (SK/DE diacritics) */
@font-face {
  font-family: "JetBrains Mono";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/assets/fonts/jetbrains-mono/latin-ext-400-normal.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "JetBrains Mono";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("/assets/fonts/jetbrains-mono/latin-ext-500-normal.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "JetBrains Mono";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("/assets/fonts/jetbrains-mono/latin-ext-600-normal.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "JetBrains Mono";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("/assets/fonts/jetbrains-mono/latin-ext-700-normal.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "JetBrains Mono";
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url("/assets/fonts/jetbrains-mono/latin-ext-400-italic.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* JetBrains Mono "symbols" subset (client 2026-09-11, "align the arrows with
   the mono font"): the shipped latin subsets already contain ↑/↓ (U+2191,
   U+2193 are inside their unicode-range), but NOT ←/→ (U+2190, U+2192) and
   no house glyph — so every ← on the site rendered in a random system
   fallback font that floats off the type's midline and differs per OS.
   These four tiny files (≈0.7 KB each, subset with pyftsubset from the SAME
   upstream v2.211 TTFs as the latin subsets) carry exactly the three missing
   glyphs, so the arrows and the home icon come from the site's own family on
   every platform. Editing a control glyph later = re-subsetting this file,
   no OS dependence. The latin faces keep ↑/↓; no codepoint overlaps. */
@font-face {
  font-family: "JetBrains Mono";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/assets/fonts/jetbrains-mono/symbols-400-normal.woff2") format("woff2");
  unicode-range: U+2190, U+2192, U+2302;
}
@font-face {
  font-family: "JetBrains Mono";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("/assets/fonts/jetbrains-mono/symbols-500-normal.woff2") format("woff2");
  unicode-range: U+2190, U+2192, U+2302;
}
@font-face {
  font-family: "JetBrains Mono";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("/assets/fonts/jetbrains-mono/symbols-600-normal.woff2") format("woff2");
  unicode-range: U+2190, U+2192, U+2302;
}
@font-face {
  font-family: "JetBrains Mono";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("/assets/fonts/jetbrains-mono/symbols-700-normal.woff2") format("woff2");
  unicode-range: U+2190, U+2192, U+2302;
}

/* Cantarell — used ONLY inside the CV .source-document region (the supplied
   ODT's own typeface; document fidelity takes precedence over the global
   family there). Full Latin Extended coverage. */
@font-face {
  font-family: "Cantarell";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/assets/fonts/cantarell/cantarell-regular.woff2") format("woff2");
}
@font-face {
  font-family: "Cantarell";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("/assets/fonts/cantarell/cantarell-bold.woff2") format("woff2");
}

/* Font candidates (client review, fixes.md "Free flowing"):
   A = IBM Plex Mono (blockier mono, default), B = Space Grotesk (blocky
   grotesk), C = Space Mono. Activate with <body data-font="a|b|c">.
   The approved family replaces JetBrains Mono; the others are removed. */
@font-face { font-family: "IBM Plex Mono"; font-style: normal; font-weight: 400; font-display: swap; src: url("/assets/fonts/ibm-plex-mono/latin-400-normal.woff2") format("woff2"); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+20AC, U+2122, U+2212, U+FEFF, U+FFFD; }
@font-face { font-family: "IBM Plex Mono"; font-style: normal; font-weight: 400; font-display: swap; src: url("/assets/fonts/ibm-plex-mono/latin-ext-400-normal.woff2") format("woff2"); unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; }
@font-face { font-family: "IBM Plex Mono"; font-style: normal; font-weight: 500; font-display: swap; src: url("/assets/fonts/ibm-plex-mono/latin-500-normal.woff2") format("woff2"); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+20AC, U+2122, U+2212, U+FEFF, U+FFFD; }
@font-face { font-family: "IBM Plex Mono"; font-style: normal; font-weight: 500; font-display: swap; src: url("/assets/fonts/ibm-plex-mono/latin-ext-500-normal.woff2") format("woff2"); unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; }
@font-face { font-family: "IBM Plex Mono"; font-style: normal; font-weight: 600; font-display: swap; src: url("/assets/fonts/ibm-plex-mono/latin-600-normal.woff2") format("woff2"); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+20AC, U+2122, U+2212, U+FEFF, U+FFFD; }
@font-face { font-family: "IBM Plex Mono"; font-style: normal; font-weight: 600; font-display: swap; src: url("/assets/fonts/ibm-plex-mono/latin-ext-600-normal.woff2") format("woff2"); unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; }
@font-face { font-family: "IBM Plex Mono"; font-style: normal; font-weight: 700; font-display: swap; src: url("/assets/fonts/ibm-plex-mono/latin-700-normal.woff2") format("woff2"); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+20AC, U+2122, U+2212, U+FEFF, U+FFFD; }
@font-face { font-family: "IBM Plex Mono"; font-style: normal; font-weight: 700; font-display: swap; src: url("/assets/fonts/ibm-plex-mono/latin-ext-700-normal.woff2") format("woff2"); unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; }
@font-face { font-family: "IBM Plex Mono"; font-style: italic; font-weight: 400; font-display: swap; src: url("/assets/fonts/ibm-plex-mono/latin-400-italic.woff2") format("woff2"); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+20AC, U+2122, U+2212, U+FEFF, U+FFFD; }
@font-face { font-family: "IBM Plex Mono"; font-style: italic; font-weight: 400; font-display: swap; src: url("/assets/fonts/ibm-plex-mono/latin-ext-400-italic.woff2") format("woff2"); unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; }

@font-face { font-family: "Space Grotesk"; font-style: normal; font-weight: 400; font-display: swap; src: url("/assets/fonts/space-grotesk/latin-400-normal.woff2") format("woff2"); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+20AC, U+2122, U+2212, U+FEFF, U+FFFD; }
@font-face { font-family: "Space Grotesk"; font-style: normal; font-weight: 400; font-display: swap; src: url("/assets/fonts/space-grotesk/latin-ext-400-normal.woff2") format("woff2"); unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; }
@font-face { font-family: "Space Grotesk"; font-style: normal; font-weight: 500; font-display: swap; src: url("/assets/fonts/space-grotesk/latin-500-normal.woff2") format("woff2"); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+20AC, U+2122, U+2212, U+FEFF, U+FFFD; }
@font-face { font-family: "Space Grotesk"; font-style: normal; font-weight: 500; font-display: swap; src: url("/assets/fonts/space-grotesk/latin-ext-500-normal.woff2") format("woff2"); unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; }
@font-face { font-family: "Space Grotesk"; font-style: normal; font-weight: 600; font-display: swap; src: url("/assets/fonts/space-grotesk/latin-600-normal.woff2") format("woff2"); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+20AC, U+2122, U+2212, U+FEFF, U+FFFD; }
@font-face { font-family: "Space Grotesk"; font-style: normal; font-weight: 600; font-display: swap; src: url("/assets/fonts/space-grotesk/latin-ext-600-normal.woff2") format("woff2"); unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; }
@font-face { font-family: "Space Grotesk"; font-style: normal; font-weight: 700; font-display: swap; src: url("/assets/fonts/space-grotesk/latin-700-normal.woff2") format("woff2"); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+20AC, U+2122, U+2212, U+FEFF, U+FFFD; }
@font-face { font-family: "Space Grotesk"; font-style: normal; font-weight: 700; font-display: swap; src: url("/assets/fonts/space-grotesk/latin-ext-700-normal.woff2") format("woff2"); unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; }

@font-face { font-family: "Space Mono"; font-style: normal; font-weight: 400; font-display: swap; src: url("/assets/fonts/space-mono/latin-400-normal.woff2") format("woff2"); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+20AC, U+2122, U+2212, U+FEFF, U+FFFD; }
@font-face { font-family: "Space Mono"; font-style: normal; font-weight: 400; font-display: swap; src: url("/assets/fonts/space-mono/latin-ext-400-normal.woff2") format("woff2"); unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; }
@font-face { font-family: "Space Mono"; font-style: normal; font-weight: 700; font-display: swap; src: url("/assets/fonts/space-mono/latin-700-normal.woff2") format("woff2"); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+20AC, U+2122, U+2212, U+FEFF, U+FFFD; }
@font-face { font-family: "Space Mono"; font-style: normal; font-weight: 700; font-display: swap; src: url("/assets/fonts/space-mono/latin-ext-700-normal.woff2") format("woff2"); unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; }
@font-face { font-family: "Space Mono"; font-style: italic; font-weight: 400; font-display: swap; src: url("/assets/fonts/space-mono/latin-400-italic.woff2") format("woff2"); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+20AC, U+2122, U+2212, U+FEFF, U+FFFD; }
@font-face { font-family: "Space Mono"; font-style: italic; font-weight: 400; font-display: swap; src: url("/assets/fonts/space-mono/latin-ext-400-italic.woff2") format("woff2"); unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; }

/* Cutive Mono — self-hosted for landing typewriter display */
@font-face { font-family: "Cutive Mono"; font-style: normal; font-weight: 400; font-display: swap; src: url("/assets/fonts/cutive-mono/latin-400-normal.woff2") format("woff2"); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+20AC, U+2122, U+2212, U+FEFF, U+FFFD; }
@font-face { font-family: "Cutive Mono"; font-style: normal; font-weight: 400; font-display: swap; src: url("/assets/fonts/cutive-mono/latin-ext-400-normal.woff2") format("woff2"); unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; }

/* --------------------------------------------------------------------------
   Tokens (design-language.md §5–§8, handover baseline)
   -------------------------------------------------------------------------- */

:root {
  --base-bg: #07080a;
  --base-bg-soft: #0c0e11;

  --text-primary: #ffffff;
  --text-secondary: rgba(255, 255, 255, 0.78);
  --text-faint: rgba(255, 255, 255, 0.58);
  --text-on-photo: #ffffff;

  --accent: #b1c3bc;
  --accent-soft: rgba(177, 195, 188, 0.55);
  --accent-deep: #8aa39a;
  --focus-ring: #d4e1db;
  --focus-glow: rgba(212, 225, 219, 0.42);

  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 24px;
  --space-6: 32px;
  --space-7: 48px;
  --space-8: 64px;
  --space-9: 96px;
  --space-10: 128px;
  --gutter: clamp(20px, 5vw, 40px);

  --measure-landing: 640px;
  --measure-prose: 70ch;
  --measure-prose-px: 780px;
  --measure-photo: 960px;
  --measure-video: 960px;
  --measure-full: 1440px;

  --font-display: clamp(2rem, 5.6vw, 3.2rem);
  --font-h1: clamp(2.1rem, 5vw, 3.4rem);
  --font-h2: clamp(1.4rem, 2.4vw, 1.9rem);
  --font-h3: clamp(1.05rem, 1.4vw, 1.2rem);
  --font-body: clamp(1rem, 0.9vw + 0.7rem, 1.0625rem);
  --font-meta: 0.8125rem;
  --font-label: 0.72rem;

  --lh-tight: 1.05;
  --lh-snug: 1.22;
  --lh-body: 1.7;
  --lh-loose: 1.85;

  --track-display: 0.02em;
  --track-h1: 0.04em;
  --track-h2: 0.06em;
  --track-body: 0.01em;
  --track-label: 0.18em;
  --track-nav: 0.12em;

  --ease-out: cubic-bezier(0.22, 0.61, 0.36, 1);
  --dur-fast: 140ms;
  --dur-med: 240ms;
  --dur-slow: 520ms;

  /* Vignettes — gradients are the allowed "surface" treatment */
  --veil-photo: radial-gradient(
    ellipse at 50% 42%,
    rgba(7, 8, 10, 0.18) 0%,
    rgba(7, 8, 10, 0.46) 55%,
    rgba(7, 8, 10, 0.82) 100%
  );
  --veil-caption: linear-gradient(
    180deg,
    rgba(7, 8, 10, 0) 40%,
    rgba(7, 8, 10, 0.72) 100%
  );
  --veil-field: radial-gradient(
    ellipse at 50% 38%,
    rgba(12, 14, 17, 0) 0%,
    rgba(7, 8, 10, 0.55) 100%
  );

  --font-mono: "IBM Plex Mono", "JetBrains Mono", "SFMono-Regular",
    "DejaVu Sans Mono", "Cascadia Code", "Source Code Pro", ui-monospace,
    Menlo, Consolas, "Liberation Mono", monospace;
  --font-grotesk: "Space Grotesk", "IBM Plex Mono", "JetBrains Mono",
    ui-monospace, monospace;
  --font-space-mono: "Space Mono", "IBM Plex Mono", "JetBrains Mono",
    ui-monospace, monospace;
  --font-typewriter: "Cutive Mono", "IBM Plex Mono", "JetBrains Mono",
    ui-monospace, monospace;
}

/* Font candidates (fixes.md): <body data-font="a|b|c">.
   a = IBM Plex Mono (default), b = Space Grotesk, c = Space Mono. */
body[data-font="b"] {
  --font-mono: var(--font-grotesk);
}

body[data-font="c"] {
  --font-mono: var(--font-space-mono);
}

/* --------------------------------------------------------------------------
   Base
   -------------------------------------------------------------------------- */

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

html {
  color-scheme: dark;
  scroll-behavior: smooth;
}

body {
  background: var(--base-bg);
  color: var(--text-primary);
  font-family: var(--font-mono);
  font-size: var(--font-body);
  letter-spacing: var(--track-body);
  line-height: var(--lh-body);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
}

img,
video,
iframe {
  display: block;
  max-width: 100%;
}

a {
  color: inherit;
}

button {
  color: inherit;
  font: inherit;
  letter-spacing: inherit;
  background: none;
  border: none;
  cursor: pointer;
}

::selection {
  background: var(--accent-soft);
  color: var(--base-bg);
}

/* --------------------------------------------------------------------------
   Atmosphere — Unified atmospheric backdrop across pages (fixes_v2.md).
   Fixed, high-contrast, darkened derivative behind all content.
   -------------------------------------------------------------------------- */

.atmosphere,
.about-tower {
  position: fixed;
  inset: 0;
  z-index: -1;
  overflow: hidden;
  background: var(--base-bg);
}

.atmosphere img,
.about-tower img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 20%;
  /* 2026-09-10 (client): the default atmosphere read too dim — a brighter
     photograph (greenery, daylight) stayed legible but a darker one went
     muddy. Raised from 0.3 to the client's chosen 0.4, with the veil below
     lightened by ~4 points, so the default is a few percent less dim and the
     colour in a splash reads on the black-and-white page. The landing splash
     at "/" is a separate treatment and is deliberately NOT touched. */
  opacity: 0.4;
}

.atmosphere::after,
.about-tower::after {
  position: absolute;
  inset: 0;
  content: "";
  background:
    radial-gradient(ellipse at 50% 24%, rgba(7, 8, 10, 0.06) 0%, rgba(7, 8, 10, 0.62) 78%),
    linear-gradient(180deg, rgba(7, 8, 10, 0.46) 0%, rgba(7, 8, 10, 0.24) 40%, rgba(7, 8, 10, 0.78) 100%);
}

/* --------------------------------------------------------------------------
   Motion — page fade on internal navigation and scroll reveals (fixes.md).
   All of it is gated behind [data-motion="on"] (set by JS) and disabled
   under prefers-reduced-motion.
   -------------------------------------------------------------------------- */

[data-motion="on"] body {
  opacity: 0;
  transition: opacity var(--dur-slow) var(--ease-out);
}

[data-motion="on"] body.is-ready {
  opacity: 1;
}

[data-motion="on"] body.is-leaving {
  opacity: 0;
  transition-duration: var(--dur-fast);
}

[data-reveal] {
  opacity: 0;
  transform: translateY(26px);
  transition:
    opacity var(--dur-slow) var(--ease-out),
    transform var(--dur-slow) var(--ease-out);
}

[data-reveal].is-revealed {
  opacity: 1;
  transform: none;
}

[data-reveal="fade"] {
  transform: none;
}

/* --------------------------------------------------------------------------
   Utilities
   -------------------------------------------------------------------------- */

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

.skip-link {
  position: absolute;
  top: var(--space-3);
  left: var(--space-3);
  z-index: 100;
  padding: var(--space-2) var(--space-3);
  background: var(--base-bg);
  color: var(--text-primary);
  transform: translateY(-300%);
  transition: transform var(--dur-fast) var(--ease-out);
}

.skip-link:focus-visible {
  transform: translateY(0);
  outline: 1px solid var(--focus-ring);
}

/* The one interaction gesture — center-out 1px underline (§9.2) */
.link-underline {
  position: relative;
  text-decoration: none;
}

.link-underline::after {
  position: absolute;
  bottom: -0.15em;
  left: 50%;
  width: 0;
  height: 1px;
  background: var(--accent);
  content: "";
  transform: translateX(-50%);
  transition: width var(--dur-med) var(--ease-out);
}

.link-underline:hover::after,
.link-underline:focus-visible::after,
.link-underline[aria-current="page"]::after,
.link-underline[aria-pressed="true"]::after {
  width: 100%;
}

.eyebrow {
  color: var(--accent);
  font-size: var(--font-label);
  font-weight: 700;
  letter-spacing: var(--track-label);
  text-transform: uppercase;
}

/* --------------------------------------------------------------------------
   Top chrome — plain text only; no pill, no background, no border
   -------------------------------------------------------------------------- */

.site-top {
  position: absolute;
  top: 0;
  right: 0;
  left: 0;
  z-index: 20;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-4) var(--gutter);
}

.site-top__brand {
  font-size: var(--font-meta);
  font-weight: 600;
  letter-spacing: var(--track-nav);
}

/* --- Header "one level back" control (client 2026-09-11, round 6) ----------
   The brand name was a link that nothing marked as clickable (client: bad
   UX). It is replaced by ONE explicit control on every content page: the
   back arrow plus the name of the level it returns to. Plain text, no box,
   header metrics (client: "leave the font type as is" — same size, weight and
   tracking the name had). It lives IN the header, so it scrolls away with the
   top menu instead of being pinned over the page at all times.

   ROUND 6 (client interview 2026-09-11): the ⌂ home glyph is SCRATCHED — it
   was "far too finicky and small to click on". There is no home control and
   every control goes back exactly one level, so this slot holds a single
   link: {rotated ↑} {level above}.

   THE ARROW (client: "the left pointing back arrow is a little too short…
   I want both the arrows to look the same, canonical is the scroll to the
   top arrow"). This is a font fact, not a styling slip: JetBrains Mono draws
   U+2190 at 530 units long but U+2191 at 740, with an equally sized head — so
   the left arrow can never match the up arrow no matter what size it is set
   to (measured with fontTools on the shipped subsets). The fix is to stop
   using U+2190: the control renders the SAME U+2191 the bottom-right .to-top
   control uses, rotated a quarter turn. Identical glyph, identical file,
   identical 17px/400 metrics — only the direction differs. Weight 400 is
   pinned because .site-top__back sets 600 for the label. */
.site-top__back {
  display: inline-flex;
  align-items: baseline;
  font-size: var(--font-meta);
  font-weight: 600;
  letter-spacing: var(--track-nav);
  /* one unbreakable link; the row may wrap under the language group */
  flex-wrap: wrap;
}

.site-top__back a {
  color: var(--text-secondary);
  text-decoration: none;
  white-space: nowrap;
  transition: color var(--dur-fast) var(--ease-out);
}

/* Invisible >= 44px hit field (design-language §9.1 "invisible targets", the
   same rule the language control and .to-top satisfy). The link's own box is
   only ~22px tall — the 13px meta line — which is under every touch-target
   guidance, and the affordance the client complained about ("far too finicky
   and small to click on") was exactly this size class. The field is a
   pseudo-element overlay rather than min-height/padding ON PURPOSE: adding
   box height would move the label off the language control's baseline, which
   was signed off in round 5. An ::before overlay changes no layout at all —
   the anchor keeps its 22px line box — while pointer events on it are
   attributed to the anchor. The longhand is free: .link-underline uses
   ::after for the underline. (.link-underline already sets position:
   relative, so no new positioning context is created.) The field is 44px
   tall and at least 44px wide, centred on the link. */
.site-top__back a::before {
  position: absolute;
  top: 50%;
  left: 0;
  width: 100%;
  min-width: 44px;
  height: 44px;
  content: "";
  transform: translateY(-50%);
}

.site-top__back a:hover,
.site-top__back a:focus-visible {
  color: var(--text-primary);
}

.site-top__arrow {
  display: inline-block;
  font-size: var(--font-body);
  font-weight: 400;
  line-height: 1;
  transform: rotate(-90deg);
}

/* Landing shows only the language control (handover) */
.site-top--landing {
  justify-content: flex-end;
}

.lang {
  display: flex;
  align-items: center;
  font-size: var(--font-meta);
  font-weight: 500;
  letter-spacing: var(--track-nav);
}

.lang__button {
  /* invisible >= 44px hit area */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  padding: var(--space-2) var(--space-1);
  color: var(--text-secondary);
  text-transform: uppercase;
}

.lang__button[aria-pressed="true"] {
  color: var(--text-primary);
}

.lang__sep {
  color: var(--text-faint);
  user-select: none;
}

/* --------------------------------------------------------------------------
   Landing
   -------------------------------------------------------------------------- */

.landing {
  position: relative;
  display: grid;
  min-height: 100svh;
  place-items: start center;
  overflow: hidden;
}

.landing__media,
.landing__media img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

.landing__media img {
  object-fit: cover;
}

.landing__veil {
  position: absolute;
  inset: 0;
  background:
    linear-gradient(
      180deg,
      rgba(7, 8, 10, 0.42) 0%,
      rgba(7, 8, 10, 0.08) 34%,
      rgba(7, 8, 10, 0.2) 62%,
      rgba(7, 8, 10, 0.66) 100%
    ),
    var(--veil-photo);
}

.landing__stack {
  position: relative;
  z-index: 1;
  display: grid;
  justify-items: center;
  gap: var(--space-6);
  width: min(100% - 2 * var(--gutter), var(--measure-landing));
  padding-top: 24svh;
  text-align: center;
}

.landing__name {
  min-height: calc(var(--font-display) * var(--lh-tight));
  color: var(--text-on-photo);
  font-family: var(--font-typewriter);
  font-size: var(--font-display);
  font-weight: 400; /* Cutive Mono ships 400 only — no faux-bold synthesis */
  letter-spacing: var(--track-display);
  line-height: var(--lh-tight);
}

/* typing caret (fixes.md) */
.landing__name .caret {
  display: inline-block;
  width: 1px;
  height: 0.9em;
  margin-left: 0.06em;
  background: var(--text-on-photo);
  vertical-align: -0.08em;
  animation: caret-blink 1.05s steps(1) infinite;
}

@keyframes caret-blink {
  0%, 55% { opacity: 1; }
  56%, 100% { opacity: 0; }
}

.landing__nav {
  display: grid;
  justify-items: center;
  gap: var(--space-3);
}

.landing__nav a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  padding: var(--space-1) var(--space-4);
  color: var(--text-on-photo);
  font-size: var(--font-h3);
  font-weight: 500;
  letter-spacing: var(--track-nav);
}

[data-motion="on"] .landing__nav a {
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.65s var(--ease-out);
}

[data-motion="on"] .landing__nav.is-visible a {
  opacity: 1;
  pointer-events: auto;
}

[data-motion="on"] .landing__nav.is-visible a:nth-child(1) { transition-delay: 0.1s; }
[data-motion="on"] .landing__nav.is-visible a:nth-child(2) { transition-delay: 0.35s; }
[data-motion="on"] .landing__nav.is-visible a:nth-child(3) { transition-delay: 0.6s; }
[data-motion="on"] .landing__nav.is-visible a:nth-child(4) { transition-delay: 0.85s; }

/* Instant (non-animated) nav reveal: no transition, no stagger.
   Used for in-session returns (alreadyTyped) and bfcache restore. */
[data-motion="on"] .landing__nav.is-instant a {
  transition: none;
  opacity: 1;
  pointer-events: auto;
}

/* bfcache restore: snap body to visible with no transition to avoid fade flash. */
[data-motion="on"] body.is-restored {
  transition: none;
  opacity: 1;
}

/* --------------------------------------------------------------------------
   Subpage shared
   -------------------------------------------------------------------------- */

.page {
  padding-bottom: var(--space-10);
}

.page-head {
  max-width: var(--measure-prose);
  margin: 0 auto;
  padding: calc(44px + var(--space-8)) var(--gutter) 0;
}

.page-head h1 {
  margin-top: var(--space-3);
  font-size: var(--font-h1);
  font-weight: 600;
  letter-spacing: var(--track-h1);
  line-height: var(--lh-snug);
}

.page-head .lede {
  margin-top: var(--space-4);
  color: var(--text-secondary);
  max-width: 60ch;
}

.page-head .note {
  margin-top: var(--space-4);
  color: var(--text-faint);
  font-size: var(--font-meta);
}

.prose {
  max-width: var(--measure-prose);
  margin: 0 auto;
  padding: 0 var(--gutter);
}

.prose p + p {
  margin-top: var(--space-4);
}

.section-head {
  max-width: var(--measure-photo);
  margin: 0 auto;
  padding: var(--space-9) var(--gutter) var(--space-6);
}

.section-head h2 {
  margin-top: var(--space-3);
  font-size: var(--font-h2);
  font-weight: 600;
  letter-spacing: var(--track-h2);
  line-height: var(--lh-snug);
}

.section-head .meta {
  margin-top: var(--space-2);
  color: var(--text-faint);
  font-size: var(--font-meta);
}

/* Plain-text link lists (contact, downloads) — §9.9 */
.link-list {
  display: grid;
  gap: var(--space-3);
  list-style: none;
}

.link-list a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  color: var(--text-secondary);
}

.link-list a:hover,
.link-list a:focus-visible {
  color: var(--text-primary);
}

.link-list .label {
  display: block;
  color: var(--text-faint);
  font-size: var(--font-label);
  font-weight: 700;
  letter-spacing: var(--track-label);
  text-transform: uppercase;
}

.link-list__text {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  color: var(--text-secondary);
}

/* --------------------------------------------------------------------------
   Figures & image groups (photography) — §9.5, §9.6
   Rebuilt per fixes.md: images keep their natural aspect ratio (no forced
   crops), groups are loosely staggered, and everything breathes.
   -------------------------------------------------------------------------- */

.figure-full {
  position: relative;
  margin: 0 auto;
  width: 100%;
}

.figure-full img {
  width: 100%;
  height: auto;
}

.figure-full--bleed {
  max-width: none;
}

.figure-full__veil {
  position: absolute;
  inset: 0;
  background: var(--veil-caption);
  pointer-events: none;
}

.figure-full figcaption {
  position: absolute;
  bottom: var(--space-6);
  left: var(--gutter);
  z-index: 1;
  color: var(--text-on-photo);
}

.figure-full figcaption .cap-label {
  display: block;
  font-size: var(--font-label);
  font-weight: 700;
  letter-spacing: var(--track-label);
  text-transform: uppercase;
}

.figure-full figcaption .cap-quote {
  display: block;
  margin-top: var(--space-2);
  color: var(--text-secondary);
  font-size: var(--font-h3);
  font-style: italic;
  font-weight: 400;
}

/* Structured photo groups matching portfolio_me (fixes_v2.md) */
.group {
  display: grid;
  max-width: var(--measure-photo);
  margin: 0 auto;
  padding: 0 var(--gutter);
  gap: var(--space-6);
}

.group img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.group a {
  display: block;
}

/* duo: two large stacked photos (ABOVE and BELOW) */
.group--duo {
  display: grid;
  grid-template-columns: 1fr;
  max-width: var(--measure-photo);
  margin: 0 auto;
  padding: 0 var(--gutter);
  gap: var(--space-7);
}

.group--duo img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 4 / 3;
  object-fit: cover;
}

.group--duo a {
  display: block;
}

/* trio-vvh: two vertical on top, one horizontal below */
.group--trio-vvh {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  max-width: var(--measure-photo);
  margin: 0 auto;
  padding: 0 var(--gutter);
  gap: var(--space-6);
}

.group--trio-vvh a {
  display: block;
}

.group--trio-vvh a:nth-child(1) img,
.group--trio-vvh a:nth-child(2) img {
  display: block;
  width: 100%;
  height: 100%;
  aspect-ratio: 2 / 3;
  object-fit: cover;
}

.group--trio-vvh a:nth-child(3) {
  grid-column: 1 / -1;
  width: 100%;
}

.group--trio-vvh a:nth-child(3) img {
  display: block;
  width: 100%;
  height: 100%;
  aspect-ratio: 3 / 2;
  object-fit: cover;
}

/* trio-hvv: one wide on top, two vertical below */
.group--trio-hvv {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  max-width: var(--measure-photo);
  margin: 0 auto;
  padding: 0 var(--gutter);
  gap: var(--space-6);
}

.group--trio-hvv a {
  display: block;
}

.group--trio-hvv a:nth-child(1) {
  grid-column: 1 / -1;
  width: 100%;
}

.group--trio-hvv a:nth-child(1) img {
  display: block;
  width: 100%;
  height: 100%;
  aspect-ratio: 3 / 2;
  object-fit: cover;
}

.group--trio-hvv a:nth-child(2) img,
.group--trio-hvv a:nth-child(3) img {
  display: block;
  width: 100%;
  height: 100%;
  aspect-ratio: 2 / 3;
  object-fit: cover;
}

/* quad: four vertical photos in a 2x2 grid */
.group--quad {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  max-width: var(--measure-photo);
  margin: 0 auto;
  padding: 0 var(--gutter);
  gap: var(--space-6);
}

.group--quad a {
  display: block;
}

.group--quad img {
  display: block;
  width: 100%;
  height: 100%;
  aspect-ratio: 2 / 3;
  object-fit: cover;
}

/* --------------------------------------------------------------------------
   Grid slots that carry caption/story (SPEC 2026-09-10 §4).
   With both flags off a grid still emits the flat direct-child imageLink()
   anchors, which is what every nth-child rule above keys off — those rules are
   untouched. A grid that opts in wraps each filled slot in
   <figure class="group__item">; the rules below re-express the same
   compositions for that shape (same specificity, later in the file, so they
   win only where the wrapped anchors no longer match as direct children).
   -------------------------------------------------------------------------- */

.group > figure.group__item {
  display: block;
  margin: 0;        /* reset the UA figure margin */
  min-width: 0;     /* long captions must not blow the grid track open */
}

.group__item figcaption {
  margin-top: var(--space-3);
  color: var(--text-secondary);
}

.group__item figcaption .cap-label {
  display: block;
  font-size: var(--font-label);
  font-weight: 700;
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--text-primary);
}

.group__item figcaption .cap-quote {
  display: block;
  margin-top: var(--space-2);
  font-size: var(--font-h3);
  font-style: italic;
  font-weight: 400;
  color: var(--text-secondary);
}

/* trio-vvh wrapped: two vertical slots on top, horizontal slot spanning below */
.group--trio-vvh > figure:nth-child(1) img,
.group--trio-vvh > figure:nth-child(2) img {
  display: block;
  width: 100%;
  height: 100%;
  aspect-ratio: 2 / 3;
  object-fit: cover;
}

.group--trio-vvh > figure:nth-child(3) {
  grid-column: 1 / -1;
  width: 100%;
}

.group--trio-vvh > figure:nth-child(3) img {
  display: block;
  width: 100%;
  height: 100%;
  aspect-ratio: 3 / 2;
  object-fit: cover;
}

/* trio-hvv wrapped: wide slot spanning on top, two vertical slots below */
.group--trio-hvv > figure:nth-child(1) {
  grid-column: 1 / -1;
  width: 100%;
}

.group--trio-hvv > figure:nth-child(1) img {
  display: block;
  width: 100%;
  height: 100%;
  aspect-ratio: 3 / 2;
  object-fit: cover;
}

.group--trio-hvv > figure:nth-child(2) img,
.group--trio-hvv > figure:nth-child(3) img {
  display: block;
  width: 100%;
  height: 100%;
  aspect-ratio: 2 / 3;
  object-fit: cover;
}

/* duo and quad need no per-slot figure rule: their image sizing (.group--duo
   img / .group--quad img) already applies at any depth. */

/* Chapter defining solo photos (as wide as normal grid photos, measure-photo) */
.photo-chapter {
  position: relative;
  max-width: var(--measure-photo);
  width: 100%;
  margin: var(--space-9) auto;
  padding: 0 var(--gutter);
}

.photo-chapter img {
  display: block;
  width: 100%;
  height: auto;
  object-fit: cover;
}

/* Vertical chapter solo photos (01, 07, 27) */
.photo-chapter--vertical {
  max-width: var(--measure-photo);
  width: 100%;
}

.photo-chapter--vertical img {
  aspect-ratio: 2 / 3;
}

/* Horizontal chapter solo photos (14, 17).
   The chapter box is border-box and already carries one --gutter of padding per
   side, so the cap must carry those gutters back: content width = min(100% -
   2 * gutter, 1140px). The old `min(100% - 2 * var(--gutter), 1140px)` box
   subtracted the gutter a second time and left a horizontal chapter NARROWER
   than a vvh's horizontal photo on mobile (310 vs 350 at 390px — round 3 fix).
   Desktop intent is unchanged: 1140px of content at 1280px, not 1060. */
.photo-chapter--horizontal {
  max-width: calc(1140px + 2 * var(--gutter));
  width: min(100%, calc(1140px + 2 * var(--gutter)));
}

.photo-chapter--horizontal img {
  aspect-ratio: 3 / 2;
}

.photo-chapter figcaption {
  margin-top: var(--space-4);
  color: var(--text-secondary);
}

.photo-chapter figcaption .cap-label {
  display: block;
  font-size: var(--font-label);
  font-weight: 700;
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--text-primary);
}

.photo-chapter figcaption .cap-quote {
  display: block;
  margin-top: var(--space-2);
  font-size: var(--font-h3);
  font-style: italic;
  font-weight: 400;
  color: var(--text-secondary);
}

/* Picture — standalone full-bleed photograph (SPEC 2026-09-10 new-blocks §4).
   Native aspect ratio, no crop: the image keeps its real width/height ratio at
   the full layout-viewport width. The parent (#main-content) is already full
   width, so width:100% is edge to edge with no 100vw scrollbar-gutter
   overflow; no cap, no gutter, no veil, no framing. */
.photo-picture {
  width: 100%;
  max-width: none;
}

.photo-picture img {
  display: block;
  width: 100%;
  height: auto;
}

/* Caption in flow under the photo: shrink-to-fit, anchored flush RIGHT by
   default (caption_align), with one gutter of padding so the text itself stays
   on the page gutter. */
.photo-picture figcaption {
  max-width: var(--measure-photo);
  width: fit-content;
  margin: var(--space-4) 0 0 auto;
  padding: 0 var(--gutter);
  color: var(--text-secondary);
}

.photo-picture figcaption .cap-label {
  display: block;
  font-size: var(--font-label);
  font-weight: 700;
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--text-primary);
}

.photo-picture figcaption .cap-quote {
  display: block;
  margin-top: var(--space-2);
  font-size: var(--font-h3);
  font-style: italic;
  font-weight: 400;
  color: var(--text-secondary);
}

/* text_placement: "inside" (SPEC 2026-09-10 round 2 §8) — the same figcaption
   sits over the photograph behind the cover's gradient veil. Gradient only:
   no box, panel or scrim, and the flags still decide whether each part shows.
   The caption keeps the label/story type treatment it has below the photo. */
.photo-picture--inside {
  position: relative;
}

.photo-picture--inside a {
  display: block;
}

.photo-picture__veil {
  position: absolute;
  inset: 0;
  background:
    linear-gradient(180deg, rgba(7, 8, 10, 0.15) 0%, rgba(7, 8, 10, 0) 40%, rgba(7, 8, 10, 0.8) 100%);
  pointer-events: none;
}

.photo-picture--inside figcaption {
  position: absolute;
  right: var(--gutter);
  bottom: var(--space-6);
  left: var(--gutter);
  z-index: 1;
  width: fit-content;
  max-width: var(--measure-prose-px);
  margin: 0 0 0 auto;
  padding: 0;
  color: var(--text-on-photo);
}

/* caption_align (round 3): the caption BLOCK is anchored flush right by
   default; these two modifiers move it flush left or centred, in BOTH
   placements (equal specificity, declared after both base rules, so only the
   auto margins change — the box stays shrink-to-fit, or a full-width block
   would travel without the text appearing to move). The text keeps its natural
   ragged-right reading alignment inside the block. */
.photo-picture--caption-left figcaption {
  margin-right: auto;
  margin-left: 0;
}

.photo-picture--caption-center figcaption {
  margin-right: auto;
  margin-left: auto;
}

/* Dividers: page separator & chapter separator (fixes_v5) */
.story-divider--page {
  position: relative;
  width: min(100% - 2 * var(--gutter), 1200px);
  height: 1px;
  margin: var(--space-6) auto;
  background: linear-gradient(
    90deg,
    transparent 0%,
    rgba(255, 255, 255, 0.08) 20%,
    rgba(255, 255, 255, 0.08) 80%,
    transparent 100%
  );
}

.story-divider--chapter {
  position: relative;
  width: min(100% - 2 * var(--gutter), 1360px);
  height: 1px;
  margin: var(--space-9) auto var(--space-8);
  background: linear-gradient(
    90deg,
    transparent 0%,
    rgba(255, 255, 255, 0.14) 8%,
    rgba(255, 255, 255, 0.14) 92%,
    transparent 100%
  );
}

/* 2-column intro in photography (clean, no backdrop blob, no gradient mask) */
.photo-intro {
  display: grid;
  grid-template-columns: 1fr;
  max-width: var(--measure-photo);
  margin: 0 auto;
  padding: var(--space-7) var(--gutter);
  gap: var(--space-8);
}

.photo-intro__media {
  width: min(100%, 340px);
  margin: 0 auto;
}

.photo-intro__media img {
  display: block;
  width: 100%;
  height: 100%;
  aspect-ratio: 3 / 4;
  object-fit: cover;
  object-position: center;
}

/* Intro caption/story — opt-in per block (SPEC 2026-09-10 §3). Mirrors the
   split/chapter figcaption treatment; it stacks under the portrait inside the
   media column. A captioned portrait must not stretch to the full (stretched)
   column height, or the figcaption would overflow the media column — so the
   image drops back to its own ratio whenever a figcaption follows it. */
.photo-intro__media a:not(:last-child) img {
  height: auto;
}

.photo-intro__media figcaption {
  margin-top: var(--space-4);
  color: var(--text-secondary);
}

.photo-intro__media figcaption .cap-label {
  display: block;
  font-size: var(--font-label);
  font-weight: 700;
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--text-primary);
}

.photo-intro__media figcaption .cap-quote {
  display: block;
  margin-top: var(--space-2);
  font-size: var(--font-h3);
  font-style: italic;
  font-weight: 400;
  color: var(--text-secondary);
}

.photo-intro__content h2 {
  font-size: var(--font-h2);
  font-weight: 600;
  letter-spacing: var(--track-h2);
  line-height: var(--lh-snug);
}

.photo-intro__content p {
  margin-top: var(--space-5);
  color: var(--text-secondary);
}

.photo-intro__content p.meta {
  color: var(--text-faint);
  font-size: var(--font-meta);
}

@media (min-width: 860px) {
  .photo-intro {
    grid-template-columns: 320px 1fr;
    gap: var(--space-9);
    align-items: stretch; /* was: center — portrait now spans full text-column height */
  }

  .photo-intro__media {
    width: 100%;
    height: 100%;
    margin: 0;
  }

  .photo-intro__media img {
    aspect-ratio: auto; /* release 3/4 so height:100% governs under stretch */
    height: 100%;
    object-fit: cover;
  }
}

.section-head {
  max-width: var(--measure-photo);
  margin: var(--space-9) auto var(--space-5);
  padding: 0 var(--gutter);
}

.section-head .section-title {
  font-size: var(--font-h2);
  font-weight: 600;
  letter-spacing: var(--track-h2);
}

/* Eyebrow-only marker block (SPEC 2026-09-10 round 2 §6): a section-head
   without its heading, so it takes the same box as .section-head (measure,
   gutter) but no pause padding. Its own top margin stays 0 like every other
   #main-content block, so the uniform --space-8 rhythm above it is preserved
   (the <p> is not matched by the #main-content > section/header/figure/div
   rhythm rule); below it hugs the content it introduces. It carries no
   data-section-head, so the section rail never counts it. */
.photo-eyebrow {
  max-width: var(--measure-photo);
  margin: 0 auto var(--space-5);
  padding: 0 var(--gutter);
}

/* Photography cover hero (contained large hero with soft edges) */
.photo-cover {
  position: relative;
  display: grid;
  place-items: end start;
  max-width: 1400px;
  width: min(100% - 2 * var(--gutter), 1400px);
  min-height: 88svh;
  max-height: 92svh;
  margin: calc(44px + var(--space-5)) auto var(--space-8);
  overflow: hidden;
}

.photo-cover img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.photo-cover__veil {
  position: absolute;
  inset: 0;
  background:
    linear-gradient(180deg, rgba(7, 8, 10, 0.15) 0%, rgba(7, 8, 10, 0) 40%, rgba(7, 8, 10, 0.8) 100%);
  pointer-events: none;
}

.photo-cover__body {
  position: relative;
  z-index: 1;
  padding: var(--space-7) var(--space-7);
  color: var(--text-on-photo);
}

.photo-cover__title {
  margin: 0;
  font-size: var(--font-h1);
  font-weight: 600;
  letter-spacing: var(--track-h1);
  line-height: var(--lh-tight);
}

/* Cover caption/story — opt-in per block (SPEC 2026-09-10 §3). The hero is a
   bottom-aligned grid, so the caption gets its own auto row beneath the title
   block while the title keeps the remaining height. Without a figcaption the
   single in-flow item still sits flush at the bottom exactly as before. */
.photo-cover {
  grid-template-rows: 1fr auto;
}

.photo-cover figcaption {
  position: relative;
  z-index: 1;
  padding: 0 var(--space-7) var(--space-7);
  color: var(--text-on-photo);
}

.photo-cover figcaption .cap-label {
  display: block;
  font-size: var(--font-label);
  font-weight: 700;
  letter-spacing: var(--track-label);
  text-transform: uppercase;
}

.photo-cover figcaption .cap-quote {
  display: block;
  margin-top: var(--space-2);
  color: var(--text-secondary);
  font-size: var(--font-h3);
  font-style: italic;
  font-weight: 400;
}

/* Opt-in viewport-wide hero (SPEC 2026-09-10 new-blocks §3). Only the box
   changes: no gutter, no 1400px cap, title/veil/crop untouched. #main-content
   is already full width, so 100% reaches both viewport edges; a 100vw box
   would overflow the layout viewport by the scrollbar gutter and either
   scroll or clip the photo's right edge. */
.photo-cover--full-bleed {
  width: 100%;
  max-width: none;
}

/* Text interludes (e.g. Eventy · Protesty) */
.interlude {
  max-width: var(--measure-prose);
  margin: 0 auto;
  padding: var(--space-9) var(--gutter) var(--space-7);
}

.interlude h2 {
  margin-top: var(--space-3);
  font-size: var(--font-h2);
  font-weight: 600;
  letter-spacing: var(--track-h2);
  line-height: var(--lh-snug);
}

.interlude p {
  margin-top: var(--space-5);
  color: var(--text-secondary);
}

/* Prose — sustained reading block (SPEC 2026-09-10 new-blocks §2).
   Reuses the about page's .prose type treatment: body copy left-aligned at a
   measure with ordinary paragraph rhythm. Deliberately NOT .interlude — that
   is a centred pause with pause padding, this is running text. */
.photo-prose {
  max-width: var(--measure-prose-px);
  margin: 0 auto;
  padding: 0 var(--gutter);
}

.photo-prose--wide {
  max-width: var(--measure-photo);
}

.photo-prose--full {
  max-width: var(--measure-full);
}

.photo-prose h2 {
  margin-top: var(--space-3);
  margin-bottom: var(--space-5);
  font-size: var(--font-h2);
  font-weight: 600;
  letter-spacing: var(--track-h2);
  line-height: var(--lh-snug);
}

.photo-prose p + p {
  margin-top: var(--space-4);
}

/* --------------------------------------------------------------------------
   Lightbox — no-box: dark field, floating image, glyph controls (§9.8)
   -------------------------------------------------------------------------- */

.lightbox {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: grid;
  place-items: center;
  background: rgba(4, 5, 6, 0.96);
}

.lightbox[hidden] {
  display: none;
}

.lightbox__image {
  max-width: 92vw;
  max-height: 86svh;
  width: auto;
  height: auto;
  object-fit: contain;
}

.lightbox__caption {
  position: absolute;
  right: var(--gutter);
  bottom: var(--space-5);
  left: var(--gutter);
  color: var(--text-secondary);
  font-size: var(--font-meta);
  text-align: center;
}

.lightbox__control {
  position: absolute;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  padding: var(--space-2);
  color: var(--text-secondary);
  font-size: 1.6rem;
  line-height: 1;
}

.lightbox__control:hover,
.lightbox__control:focus-visible {
  color: var(--text-primary);
}

.lightbox__control:focus-visible {
  outline: 1px solid var(--focus-ring);
}

.lightbox__control--close {
  top: var(--space-3);
  right: var(--gutter);
}

.lightbox__control--prev {
  top: 50%;
  left: var(--gutter);
  transform: translateY(-50%);
}

.lightbox__control--next {
  top: 50%;
  right: var(--gutter);
  transform: translateY(-50%);
}

body.has-modal {
  overflow: hidden;
}

/* --------------------------------------------------------------------------
   Video — Borderless, free-flowing presentation with soft thumbnail fade &
   blur-on-play facade (fixes_v2.md).
   -------------------------------------------------------------------------- */

.clip {
  position: relative;
  display: grid;
  gap: var(--space-6);
  padding: var(--space-9) var(--gutter);
  max-width: var(--measure-video);
  margin: 0 auto;
}

.clip__head {
  width: 100%;
}

.clip__head h2 {
  margin-top: var(--space-3);
  font-size: var(--font-h2);
  font-weight: 600;
  letter-spacing: var(--track-h2);
}

.clip__stage {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  background: transparent;
}

.clip__stage iframe {
  position: absolute;
  inset: 0;
  z-index: 1;
  width: 100%;
  height: 100%;
  border: 0;
}

.clip__facade {
  position: absolute;
  inset: -5%;
  z-index: 2;
  display: grid;
  width: 110%;
  height: 110%;
  align-content: center;
  justify-items: center;
  gap: var(--space-3);
  padding: 0;
  color: var(--text-on-photo);
  text-align: center;
  transition:
    filter var(--dur-normal) var(--ease-out),
    opacity var(--dur-normal) var(--ease-out);
}

.clip__stage.is-playing .clip__facade {
  filter: blur(24px);
  opacity: 0;
  pointer-events: none;
}

.clip__facade img {
  position: absolute;
  inset: 0;
  z-index: -1;
  width: 100%;
  height: 100%;
  object-fit: cover;
  -webkit-mask-image: radial-gradient(ellipse at 50% 50%, black 50%, rgba(0, 0, 0, 0.7) 72%, transparent 100%);
  mask-image: radial-gradient(ellipse at 50% 50%, black 50%, rgba(0, 0, 0, 0.7) 72%, transparent 100%);
}

.clip__facade .play {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  padding: var(--space-2) var(--space-4);
  color: var(--text-on-photo);
  font-size: var(--font-h3);
  font-weight: 500;
  letter-spacing: var(--track-nav);
}

.clip__facade .pending {
  max-width: 46ch;
  padding: 0 var(--space-4);
  color: var(--text-secondary);
  font-size: var(--font-meta);
}

.clip__facade .hint {
  color: var(--text-secondary);
  font-size: var(--font-meta);
}

.clip__body {
  width: 100%;
  color: var(--text-secondary);
}

.clip__body p + p {
  margin-top: var(--space-3);
}

.clip__links {
  width: 100%;
}

/* --------------------------------------------------------------------------
   About — 2-column layout + mask-softened portrait (fixes_v2.md)
   -------------------------------------------------------------------------- */

.about-grid {
  display: grid;
  grid-template-columns: 1fr;
  /* Decoupled from --measure-photo (960): media col + gap + prose + gutter
     padding. The +2*--gutter term self-balances border-box padding so the bio
     column resolves to exactly --measure-prose-px (780) at the cap. */
  max-width: calc(320px + var(--space-9) + var(--measure-prose-px) + 2 * var(--gutter));
  margin: 0 auto;
  padding: 0 var(--gutter);
  gap: var(--space-8);
}

.about-portrait {
  position: relative;
  width: min(100%, 340px);
  margin: var(--space-6) auto 0;
}

/* Simple rounded rectangle backdrop slightly larger than the picture, blurring into background (fixes_v5) */
.about-portrait::before {
  content: "";
  position: absolute;
  inset: -14px;
  border-radius: 20px;
  z-index: 0;
  background: #07080a;
  box-shadow: 0 0 32px 16px #07080a;
  pointer-events: none;
  filter: blur(14px);
}

.about-portrait picture {
  position: relative;
  z-index: 1;
  display: block;
}

.about-portrait img {
  display: block;
  width: 100%;
  height: auto;
}

.about-bio {
  padding-top: var(--space-6);
}

.about-bio h2 {
  margin-top: var(--space-3);
  margin-bottom: var(--space-5);
  font-size: var(--font-h2);
  font-weight: 600;
  letter-spacing: var(--track-h2);
}

.about-bio p + p,
.about-bio ul + p,
.about-bio p + ul {
  margin-top: var(--space-4);
}

.about-bio ul {
  margin-top: var(--space-4);
  padding-left: 1.2em;
  color: var(--text-secondary);
}

.about-bio li + li {
  margin-top: var(--space-2);
}

.about-contact {
  padding-top: var(--space-8);
}

.about-contact h2 {
  margin-top: var(--space-3);
  margin-bottom: var(--space-5);
  font-size: var(--font-h2);
  font-weight: 600;
  letter-spacing: var(--track-h2);
}

@media (min-width: 860px) {
  .about-grid {
    grid-template-columns: 320px 1fr;
    gap: var(--space-9);
    align-items: start;
    padding-top: var(--space-6);
  }

  .about-grid__media {
    position: sticky;
    top: calc(44px + var(--space-8));
  }

  .about-portrait {
    width: 100%;
    margin: 0;
  }

  .about-bio {
    padding-top: 0;
  }
}

/* --------------------------------------------------------------------------
   CV document viewer — .source-document scoped exception (§9.10)
   Reproduces the supplied German ODT (V4, 20.8.2026): white paper page,
   personal-data side column, larger experience area, source boundaries and
   source typography. The general no-box rules do NOT apply inside this region.
   -------------------------------------------------------------------------- */

.cv-stage {
  padding: var(--space-7) var(--gutter) var(--space-8);
}

.cv-scroll {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}

.cv-scale {
  margin: 0 auto;
  width: max-content;
  transform-origin: top center;
}

.source-document .cv-paper {
  width: 210mm;
  padding: 7.4mm 7.4mm 10.2mm;
  background: #ffffff;
  color: #404040;
  font-family: "Cantarell", "Corbel", "Calibri", "Carlito", sans-serif;
  font-size: 9pt;
  line-height: 1.32;
  letter-spacing: 0;
  /* paper lift — part of the intentional document surface, not UI chrome */
  box-shadow: 0 3mm 14mm rgba(0, 0, 0, 0.5);
}

.cv-table {
  width: 100%;
  border-collapse: collapse;
  table-layout: fixed;
}

.cv-table td {
  border: 1px solid #004f6d;
  padding: 2.2mm 2.8mm;
  vertical-align: top;
}

.cv-table .cv-photo-cell {
  width: 32.5%;
  padding: 0;
  vertical-align: top;
  border-bottom: 1px solid #004f6d;
}

.cv-photo {
  display: block;
  width: 100%;
  height: auto;
}

.cv-title-cell {
  width: 67.5%;
  padding: 3mm 4mm;
  vertical-align: middle;
}

.cv-title-cell h1 {
  margin: 0;
  color: #0f6fc6;
  font-size: 26pt;
  font-weight: 700;
  line-height: 1.15;
}

.cv-table .cv-side {
  width: 32.5%;
  padding: 2.4mm 3mm;
  text-align: right;
  vertical-align: top;
}

.cv-table .cv-main {
  width: 67.5%;
  padding: 2.4mm 3.8mm;
  vertical-align: top;
}

.cv-table .cv-main--ausbildung {
  padding-top: 2.4mm;
}

/* side column */
.cv-side h2 {
  margin: 2.4mm 0 1mm;
  color: #0f6fc6;
  font-size: 13.5pt;
  font-weight: 700;
  line-height: 1.2;
  text-align: right;
}

.cv-side h2:first-child {
  margin-top: 0.6mm;
}

.cv-side p {
  margin: 0;
  font-size: 8.8pt;
  line-height: 1.32;
  text-align: right;
}

.cv-side .cv-sig {
  margin-top: 7mm;
  text-align: right;
}

.cv-side .cv-sig img {
  display: block;
  width: 52mm;
  height: auto;
  margin: 0 0 -2.6mm auto;
}

.cv-side .cv-sig .dashes {
  color: #404040;
  font-size: 8pt;
  letter-spacing: 0.02em;
  white-space: nowrap;
  overflow: hidden;
  text-align: right;
}

.cv-side .cv-sig .date {
  margin-top: 0.6mm;
  font-size: 8.5pt;
  text-align: right;
}

/* main column */
.cv-main h2 {
  margin: 0 0 1.2mm;
  color: #0f6fc6;
  font-size: 13.5pt;
  font-weight: 700;
  line-height: 1.2;
  text-align: left;
}

.cv-main h3 {
  margin: 2.4mm 0 0.4mm;
  color: #0076a3;
  font-size: 10.5pt;
  font-weight: 700;
  line-height: 1.25;
  text-align: left;
}

.cv-main h3:first-of-type {
  margin-top: 0;
}

.cv-main p {
  margin: 0 0 0.4mm 0;
  font-size: 8.8pt;
  line-height: 1.32;
  text-align: left;
}

.cv-main p.cv-entry {
  font-weight: 700;
  margin-top: 1.2mm;
}

.cv-main p.cv-plain {
  text-indent: 0;
  margin-left: 0;
  text-align: left;
}

.cv-main .cv-gap {
  margin-bottom: 1.8mm;
}

/* Ausbildung entries */
.cv-main--ausbildung p {
  margin-left: 0;
  font-size: 9pt;
  text-align: left;
  text-indent: 0;
  margin-bottom: 0.8mm;
}

/* downloads below the viewer — plain text, never above */
.cv-downloads {
  max-width: var(--measure-prose);
  margin: 0 auto;
  padding: 0 var(--gutter);
}

.cv-downloads h2 {
  margin-top: 0;
  margin-bottom: var(--space-4);
  font-size: var(--font-h3);
  font-weight: 600;
  letter-spacing: var(--track-h2);
}

/* --------------------------------------------------------------------------
   Responsive
   -------------------------------------------------------------------------- */

@media (max-width: 760px) {
  .group,
  .group--duo,
  .group--trio-vvh,
  .group--trio-hvv,
  .group--quad {
    grid-template-columns: 1fr;
    gap: var(--space-6);
  }

  .group a:nth-child(n) {
    grid-column: auto;
    width: auto;
    margin-top: 0;
    margin-right: auto;
    margin-left: auto;
  }

  /* Wrapped slots (SPEC 2026-09-10 §4) mirror that reset for the figure shape. */
  .group > figure.group__item {
    grid-column: auto;
    width: auto;
  }

  .figure-full figcaption {
    bottom: var(--space-4);
  }

  .clip {
    min-height: 0;
    padding-top: var(--space-8);
    padding-bottom: var(--space-8);
  }
}

/* --------------------------------------------------------------------------
   Reduced motion — underline growth and transitions off
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

  *,
  *::before,
  *::after {
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
  }

  [data-reveal] {
    opacity: 1;
    transform: none;
  }

  [data-motion="on"] body {
    opacity: 1;
  }
}

/* --------------------------------------------------------------------------
   Print — the CV paper prints clean; site chrome is screen-only
   -------------------------------------------------------------------------- */

@media print {
  body {
    background: #ffffff;
  }

  .site-top,
  .skip-link,
  .cv-downloads,
  .page-head {
    display: none !important;
  }

  .cv-stage {
    padding: 0;
    background: none;
  }

  .cv-scroll {
    overflow: visible;
  }

  .cv-scale {
    transform: none !important;
    width: auto;
    height: auto !important;
  }

  .source-document .cv-paper {
    width: auto;
    box-shadow: none;
  }
}

/* ------------------------------------------------------------------------
   Photo landing carousel (/photo/) — two-up panels, blur at rest.
   Approved design study 2026-08-27 (style-snapshots/2026-08-27-carousel-
   inpanel-approved). Blur = filter on <img>, never backdrop-filter (§4).
   ------------------------------------------------------------------------ */

.photo-carousel {
  position: relative;
  --unblur-delay: 100ms;   /* hover-in: ignore quick mouse passes */
  --unblur-dur: 650ms;     /* hover-in: slow dissolve to sharp */
  --unblur-release: 280ms; /* hover-out: prompt, no delay */
}

.photo-carousel__track {
  display: flex;
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x mandatory;
  height: 100svh;
  outline: none;
}

.photo-carousel__slide {
  position: relative;
  flex: 0 0 50%;
  height: 100%;
  scroll-snap-align: start;
  display: grid;
  align-items: end;
  overflow: hidden;
  text-decoration: none;
  /* Issue fix 2026-08-27 (dark streak across the panel boundary during fast
     hover passes): paint containment clips ALL descendant painting — incl.
     the animated blur layer's expanded filter-effect bounds — to this slide,
     and isolation gives the slide its own stacking context so a neighbour's
     transitioning media can never composite over it. */
  contain: paint;
  isolation: isolate;
}

.photo-carousel__media {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: blur(14px) saturate(0.95) brightness(0.88);
  transform: scale(1.10);
  opacity: 0.82;
  transition:
    filter var(--unblur-release) var(--ease-out),
    transform var(--unblur-release) var(--ease-out),
    opacity var(--unblur-release) var(--ease-out);
  /* Keep the blurred layer permanently promoted: without this every hover
     cycle promotes/demotes a full-screen layer and re-rasters it, which
     under rapid passes leaves stale tiles (the streak/jank source). */
  will-change: filter, transform, opacity;
}

.photo-carousel__slide:hover .photo-carousel__media:not(.photo-carousel__media--preview),
.photo-carousel__slide:focus-visible .photo-carousel__media:not(.photo-carousel__media--preview) {
  transition:
    filter var(--unblur-dur) var(--ease-out) var(--unblur-delay),
    transform var(--unblur-dur) var(--ease-out) var(--unblur-delay),
    opacity var(--unblur-dur) var(--ease-out) var(--unblur-delay);
  filter: blur(0) saturate(1) brightness(0.95);
  transform: scale(1);
  opacity: 1;
}

.photo-carousel__veil {
  position: absolute;
  inset: 0;
  background: linear-gradient(
    180deg,
    rgba(7, 8, 10, 0.22) 0%,
    rgba(7, 8, 10, 0.08) 45%,
    rgba(7, 8, 10, 0.65) 100%
  );
  pointer-events: none;
}

.photo-carousel__body {
  position: relative;
  z-index: 2;
  padding: var(--space-8) var(--space-6) calc(var(--space-9) + var(--space-7));
  text-align: center;
  pointer-events: none;
}

.photo-carousel__eyebrow {
  color: var(--accent);
  font-size: var(--font-label);
  font-weight: 700;
  letter-spacing: var(--track-label);
  text-transform: uppercase;
}

.slide-draft-badge {
  display: inline-block;
  margin-left: var(--space-2);
  padding: 1px 6px;
  border: 1px solid rgba(217, 138, 122, 0.6);
  color: #d98a7a;
  font-size: 0.65rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  border-radius: 2px;
  vertical-align: middle;
}

.photo-carousel__heading {
  margin-top: var(--space-4);
  font-size: var(--font-h2);
  font-weight: 600;
  letter-spacing: var(--track-h2);
  line-height: var(--lh-snug);
}

.photo-carousel__meta {
  margin-top: var(--space-4);
  color: var(--text-secondary);
  font-size: var(--font-meta);
}

/* Card foot line when the collection has an "intro metadata" line
   (client 2026-09-10): the count is technical, a sentence is not — same
   place, quieter ink and a comfortable measure so it reads as a short
   story rather than a caption. */
.photo-carousel__meta--intro {
  max-width: 34ch;
  margin-right: auto;
  margin-left: auto;
  font-size: var(--font-body);
  line-height: 1.6;
  text-wrap: balance;
}

.photo-carousel__nav {
  position: fixed;
  right: 0;
  bottom: var(--space-5);
  left: 0;
  z-index: 10;
  display: grid;
  justify-items: center;
  gap: var(--space-2);
  pointer-events: none;
}

.photo-carousel__nav-row {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  pointer-events: auto;
}

.photo-carousel__arrow {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  background: transparent;
  border: none;
  color: var(--text-faint);
  font-size: 1.6rem;
  line-height: 1;
  cursor: pointer;
  pointer-events: auto;
  transition: color var(--dur-fast) var(--ease-out);
}

.photo-carousel__arrow:hover {
  color: var(--text-primary);
}

.photo-carousel__arrow:focus-visible {
  outline: 1px solid var(--focus-ring);
}

.photo-carousel__dots {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  pointer-events: auto;
}

.photo-carousel__dot {
  width: 8px;
  height: 8px;
  padding: 0;
  border-radius: 50%;
  background: var(--text-faint);
  opacity: 0.55;
  transition: opacity var(--dur-fast) var(--ease-out),
              background var(--dur-fast) var(--ease-out);
}

.photo-carousel__dot[aria-selected="true"],
.photo-carousel__dot--active {
  background: var(--accent);
  opacity: 1;
}

.photo-carousel__dot:focus-visible {
  outline: 1px solid var(--focus-ring);
  opacity: 1;
}

.photo-carousel__counter {
  color: var(--text-faint);
  font-size: var(--font-meta);
  letter-spacing: var(--track-nav);
}

/* In-panel subcollection list (photo-combined approved pattern) */
.clist {
  position: absolute;
  top: 50%;
  right: var(--space-6);
  z-index: 3;
  display: grid;
  transform: translateY(-50%);
  text-align: right;
  --clist-stagger: 90ms;
}

.clist__item {
  min-height: 44px;
  display: flex;
  width: 100%;
  align-items: center;
  justify-content: flex-end;
  position: relative;
  padding: var(--space-2) 0;
  color: var(--text-faint);
  font-size: var(--font-meta);
  letter-spacing: var(--track-nav);
  opacity: 0.72;
  text-decoration: none;
  transition: opacity var(--dur-med) var(--ease-out),
              color var(--dur-med) var(--ease-out);
  animation: clist-in var(--dur-med) var(--ease-out) backwards;
  animation-delay: calc(var(--clist-i, 0) * var(--clist-stagger));
}

@keyframes clist-in {
  from {
    opacity: 0;
    transform: translateY(-8px);
  }
}

.clist--entering .clist__item {
  pointer-events: none;
}

.clist__item:hover,
.clist__item:focus-visible {
  color: var(--text-primary);
  opacity: 1;
}

.clist__item::after {
  position: absolute;
  bottom: 0.35em;
  left: 50%;
  width: 0;
  height: 1px;
  background: var(--accent);
  content: "";
  transform: translateX(-50%);
  transition: width var(--dur-med) var(--ease-out);
}

.clist__item:hover::after,
.clist__item:focus-visible::after {
  width: 100%;
}

.photo-carousel__slide[data-collection] {
  cursor: pointer;
}

.photo-carousel__slide .photo-carousel__media--preview {
  opacity: 0;
  filter: blur(0) saturate(1) brightness(0.92);
  transform: scale(1);
  transition: opacity var(--dur-med) var(--ease-out);
}

.photo-carousel__slide.slide--preview .photo-carousel__media--preview {
  opacity: 1;
  transition: opacity var(--dur-slow) var(--ease-out);
}

/* Admin Add slide: reads like sibling carousel panels (photo + veil + text),
   distinguished by its content. The dashed border + grey background were a
   §4 violation (border-as-framing) from when the tile was a placeholder; both
   removed. The shared .photo-carousel__slide:hover/:focus-visible blur-sharpen
   on .photo-carousel__media is the non-filled hover/keyboard affordance. */
.photo-carousel__slide--add {
  cursor: pointer;
}

@media (max-width: 720px) {
  .photo-carousel__slide {
    flex-basis: 85%;
  }
}

/* Issue fix 2026-08-27 (mobile: in-view panel stayed blurred): on touch
   there is no :hover to lift the rest blur, so setupPhotoCarousel() tags
   the panel(s) currently in view — same left-panel index the dots already
   compute — with .photo-carousel__slide--in-view. In-view panels render
   sharp; off-screen panels keep the blurred rest state. No unblur delay:
   crossing panels by scroll is deliberate, unlike quick mouse passes.
   NOTE: placed before the prefers-reduced-motion block on purpose, so its
   transition:none stays the last word for reduced-motion users. */
@media (hover: none) {
  .photo-carousel__slide--in-view .photo-carousel__media:not(.photo-carousel__media--preview) {
    transition:
      filter var(--unblur-dur) var(--ease-out),
      transform var(--unblur-dur) var(--ease-out),
      opacity var(--unblur-dur) var(--ease-out);
    filter: blur(0) saturate(1) brightness(0.92);
    transform: scale(1);
    opacity: 1;
  }
}

/* RETIRED 2026-09-11 (client interview): both dedicated back-link blocks are
   gone. The sub-collections view's in-carousel back link and the fixed
   top-left collection-back control were the two "back" patterns; the vision
   changed to ONE pattern — the header control .site-top__back (see the
   top-chrome section), which replaces the brand name on photo pages and
   scrolls with the top menu. Nothing here styled anything else, so the
   blocks were deleted rather than left as dead weight. The approved
   typographic recipe survives in .site-top__back: secondary ink, colour-only
   hover, arrow-in-text, no box. */

@media (prefers-reduced-motion: reduce) {
  .photo-carousel__media,
  .photo-carousel__slide--in-view .photo-carousel__media,
  .photo-carousel__dot {
    transition: none;
  }
}

/* --------------------------------------------------------------------------
   Collection hero & streams (editorial & random presentations)
   -------------------------------------------------------------------------- */

.collection-hero {
  max-width: var(--measure-prose-px);
  margin: 0 auto;
  padding: calc(var(--space-10) + var(--space-6)) var(--gutter) var(--space-8);
}

.collection-hero__eyebrow {
  color: var(--accent);
  font-size: var(--font-label);
  font-weight: 700;
  letter-spacing: var(--track-label);
  text-transform: uppercase;
}

.collection-hero__title {
  margin-top: var(--space-4);
  font-size: var(--font-h1);
  font-weight: 600;
  letter-spacing: var(--track-h1);
  line-height: var(--lh-tight);
}

.collection-hero__subtitle {
  margin-top: var(--space-3);
  color: var(--text-secondary);
  font-size: var(--font-h3);
}

.collection-hero__intro {
  margin-top: var(--space-6);
  color: var(--text-secondary);
  line-height: var(--lh-loose);
}

.collection-hero__meta {
  margin-top: var(--space-5);
  color: var(--text-faint);
  font-size: var(--font-meta);
}

/* Stream rhythm: asymmetric sizes, whitespace as the only separator.
   wide  = horizontal image across the photo measure
   tall  = vertical image on a narrower centered column
   lead  = opening vertical image, larger presence                */

.stream {
  display: grid;
  gap: var(--space-10);
  padding: 0 0 var(--space-9);
}

.stream__item {
  margin: 0;
}

.stream__item a {
  display: block;
}

.stream__item img {
  display: block;
  width: 100%;
  height: auto;
}

.stream__item--wide a {
  max-width: var(--measure-photo);
  margin: 0 auto;
  padding: 0 var(--gutter);
}

.stream__item--tall a {
  max-width: 640px;
  margin: 0 auto;
  padding: 0 var(--gutter);
}

.stream__item--lead a {
  max-width: 720px;
  margin: 0 auto;
  padding: 0 var(--gutter);
}

/* Caption + story sit under the image, inside the prose measure */
.stream__item figcaption {
  max-width: var(--measure-prose-px);
  margin: 0 auto;
  padding: var(--space-4) var(--gutter) 0;
}

.stream__cap {
  display: block;
  color: var(--text-faint);
  font-size: var(--font-label);
  font-weight: 700;
  letter-spacing: var(--track-label);
  text-transform: uppercase;
}

.stream__story {
  display: block;
  max-width: var(--measure-prose);
  margin-top: var(--space-4);
  color: var(--text-secondary);
  line-height: var(--lh-loose);
}

/* Random presentation layout: sequential alternating flow (Row-by-Row, no column split) */
.stream--random {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-8) var(--space-6);
  max-width: var(--measure-full);
  margin: 0 auto;
  padding: 0 var(--gutter) var(--space-9);
  align-items: start;
}

.stream--random .stream__item {
  margin: 0;
}

.stream--random .stream__item a {
  max-width: 100%;
  padding: 0;
}

.stream--random .stream__item figcaption {
  padding: var(--space-3) 0 0;
}

.stream--random .stream__item--wide {
  grid-column: 1 / -1;
  max-width: var(--measure-photo);
  margin: 0 auto;
  width: 100%;
}

.stream--random .stream__item:nth-child(4n+1) {
  padding-top: var(--space-6);
}

.stream--random .stream__item:nth-child(4n+3) {
  padding-top: var(--space-4);
}

@media (max-width: 720px) {
  .stream--random {
    grid-template-columns: 1fr;
    gap: var(--space-8);
  }
  .stream--random .stream__item:nth-child(4n+1),
  .stream--random .stream__item:nth-child(4n+3) {
    padding-top: 0;
  }
}

/* Side-by-side split template (Image on one side, prose text on the other).
   The photograph commands the viewport — "one large photography at a time" —
    so the image column takes roughly two-thirds of the row and the row is
    capped at --measure-full (1440px, the locked upper bound for wide
    imagery — the client's "one large photography at a time" intent;
    --measure-photo stays 960px for the client-approved v10 static
    portfolio surfaces). Vertical rhythm between rows is --space-10
    (128px), the only separator (no dividers, §4/§9.4). */
.stream--side-by-side {
  display: grid;
  gap: var(--space-10);
  max-width: var(--measure-full);
  margin: 0 auto;
  padding: 0 var(--gutter) var(--space-9);
}

.stream-split-row {
  display: grid;
  grid-template-columns: minmax(0, 1.9fr) minmax(280px, 1fr);
  gap: var(--space-8);
  align-items: center;
  max-width: var(--measure-full);
  margin: 0 auto;
  width: 100%;
}

.stream-split-row.is-reversed {
  direction: rtl;
}

.stream-split-row.is-reversed > * {
  direction: ltr;
}

.stream-split-row__media img {
  display: block;
  width: 100%;
  height: auto;
  object-fit: cover;
}

.stream-split-row__content {
  padding: var(--space-4);
  max-width: var(--measure-prose);
}

.stream-split-row__cap {
  display: block;
  color: var(--accent);
  font-size: var(--font-label);
  font-weight: 700;
  letter-spacing: var(--track-label);
  text-transform: uppercase;
}

.stream-split-row__title {
  margin-top: var(--space-3);
  font-size: var(--font-h2);
  font-weight: 600;
  line-height: var(--lh-snug);
}

.stream-split-row__story {
  margin-top: var(--space-4);
  color: var(--text-secondary);
  line-height: var(--lh-loose);
}

@media (max-width: 768px) {
  .stream-split-row {
    grid-template-columns: 1fr;
    gap: var(--space-6);
  }
  .stream-split-row.is-reversed {
    direction: ltr;
  }
}

.collection-foot {
  display: grid;
  justify-items: center;
  padding: var(--space-8) var(--gutter) var(--space-9);
}

.collection-foot a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  color: var(--text-secondary);
}

/* --------------------------------------------------------------------------
   In-place editing & admin bar (no-box, typography-first)
   -------------------------------------------------------------------------- */

.admin-bar {
  position: fixed;
  bottom: 0;
  left: 0;
  /* Span the full VISUAL viewport including the scrollbar gutter. With
     right: 0 the layout box stops short of the scrollbar column and a strip
     of sharp page content stayed visible beside the bar ("slight unblurred
     gap" when in admin mode — client, 2026-09-06). left:0 + width:100vw
     covers it; right is intentionally not set (over-constrained). */
  width: 100vw;
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-4);
  padding: var(--space-3) var(--gutter);
  /* Client decision 2026-09-06: opaque no-box field. (A frosted translucent
     treatment was tried the same day and reverted at the client's word —
     the plain opaque bar is the approved look.) */
  background: rgb(7, 8, 10);
  font-size: var(--font-label);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
}

.admin-bar__status {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  color: var(--accent);
  font-weight: 700;
}

.admin-bar__status::before {
  content: "●";
  color: var(--accent);
  font-size: 0.8em;
}

.admin-bar__controls {
  display: inline-flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-4);
}

.admin-bar button,
.admin-bar a {
  background: transparent;
  border: none;
  color: var(--text-secondary);
  font-family: inherit;
  font-size: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  cursor: pointer;
  padding: var(--space-2) var(--space-3);
  transition: color var(--dur-fast) var(--ease-out);
}

.admin-bar button:hover,
.admin-bar a:hover,
.admin-bar button.is-active {
  color: var(--text-primary);
}

.admin-bar button.is-active {
  color: var(--accent);
  font-weight: 700;
}

.admin-bar__msg {
  color: var(--text-faint);
  font-size: var(--font-meta);
  text-transform: none;
  letter-spacing: normal;
}

/* ---- Mobile admin bar (locked spec 2026-09-10, handout item 3) ----
   Phone widths collapse the bar to ONE ~44px row: ☰ · Save · edit toggle.
   Everything else is relocated into the sheet by renderAdminBar — the SAME
   nodes and handlers, moved while the sheet is open and moved home on close.
   This block is duplicated in admin/admin.css: the bar is styled by both files
   (public pages vs the dashboard shell) and must not differ between them.
   Opaque surfaces only — the frosted variant was tried and reverted (locked). */
.admin-bar__sheet-toggle {
  display: none; /* desktop: the bar already shows every control */
}

.admin-bar__sheet {
  display: none; /* closed by default — no stray hit-target over the page */
}

.admin-bar__btn-edit__short {
  display: none;
}

@media (max-width: 760px) {
  /* The row: no block padding, 44px controls, so the closed bar is ~44px
     (≈8% even on a 320×568 phone). */
  .admin-bar {
    flex-wrap: nowrap;
    gap: var(--space-2);
    padding: 0 var(--gutter);
    min-height: 44px;
  }

  .admin-bar__sheet-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    min-width: 44px;
    min-height: 44px;
  }

  .admin-bar__controls {
    flex: 1 1 auto;
    flex-wrap: nowrap;
    justify-content: flex-end;
    gap: var(--space-2);
    min-width: 0;
  }

  .admin-bar__btn-save,
  .admin-bar__btn-edit {
    flex: 0 0 auto;
    min-height: 44px;
  }

  .admin-bar__msg {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  /* These leave the row and re-appear inside the sheet (rules below). */
  .admin-bar__left,
  .admin-bar__btn-pres,
  .admin-bar__btn-splash,
  .admin-bar__btn-status,
  .admin-bar__btn-del,
  .admin-bar__btn-logout {
    display: none;
  }

  .admin-bar__btn-edit__full {
    display: none;
  }

  .admin-bar__btn-edit__short {
    display: inline;
  }

  /* The sheet hangs directly above the bar, so the Save row is never covered;
     it is capped to the remaining viewport and scrolls internally. */
  .admin-bar__sheet.is-open {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: var(--space-2);
    position: fixed;
    left: 0;
    bottom: var(--admin-bar-h, 44px);
    width: 100vw;
    max-height: calc(100vh - var(--admin-bar-h, 44px) - var(--space-3));
    overflow-y: auto;
    padding: var(--space-4) var(--gutter);
    background: rgb(7, 8, 10);
    z-index: 999;
  }

  .admin-bar__sheet.is-open .admin-bar__left {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-2);
  }

  .admin-bar__sheet.is-open .admin-bar__btn-pres,
  .admin-bar__sheet.is-open .admin-bar__btn-splash,
  .admin-bar__sheet.is-open .admin-bar__btn-status,
  .admin-bar__sheet.is-open .admin-bar__btn-del,
  .admin-bar__sheet.is-open .admin-bar__btn-logout {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    text-align: left;
  }

  /* Same 44px hit area the project already uses (.lang__button,
     .collection-foot a) for the relocated nav links and language buttons. */
  .admin-bar__sheet.is-open .admin-bar__site-nav a,
  .admin-bar__sheet.is-open .admin-bar__site-nav button {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
  }
}

/* Edit mode affordances */
body.is-editing [contenteditable="true"] {
  outline: none;
  cursor: text;
  border-bottom: 1px dashed var(--accent-soft);
  transition: border-color var(--dur-fast) var(--ease-out);
}

body.is-editing [contenteditable="true"]:hover,
body.is-editing [contenteditable="true"]:focus {
  border-bottom: 1px solid var(--accent);
}

body.is-editing .stream__item {
  position: relative;
  cursor: grab;
}

body.is-editing .stream__item:active {
  cursor: grabbing;
}

body.is-editing .stream__item.is-dragging {
  opacity: 0.35;
}

body.is-editing .stream__item.drag-over {
  outline: 2px dashed var(--accent);
}

.stream__item-remove {
  display: none;
}

body.is-editing .stream__item-remove {
  position: absolute;
  top: var(--space-2);
  right: var(--space-2);
  z-index: 10;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  background: rgba(7, 8, 10, 0.75);
  border: none;
  color: var(--text-primary);
  font-size: 1.2rem;
  line-height: 1;
  cursor: pointer;
  opacity: 0.7;
  transition: opacity var(--dur-fast) var(--ease-out);
}

body.is-editing .stream__item-remove:hover {
  opacity: 1;
  color: #ff6b6b;
}

/* Empty-slot "+" tile */
.stream__item--add {
  display: none;
  margin: 0 auto;
  max-width: var(--measure-photo);
  padding: var(--space-8) var(--gutter);
  text-align: center;
  border: 1px dashed var(--accent-soft);
}

body.is-editing .stream__item--add {
  display: block;
}

.stream__item--add label {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
  color: var(--accent);
  cursor: pointer;
  font-size: var(--font-label);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
}

.stream__item--add .add-glyph {
  font-size: 2rem;
  line-height: 1;
}

.stream__item--add input[type="file"] {
  display: none;
}

/* Admin in-place dialog / modal (no-box, typography-first) */
.admin-modal {
  position: fixed;
  inset: 0;
  /* Same scrollbar-gutter coverage as .admin-bar: inset:0 alone stops at the
     layout viewport edge and leaves a sharp content strip beside the modal
     in admin mode. */
  width: 100vw;
  z-index: 2000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-6) var(--gutter);
  /* M5 §2.4: prior backdrop-filter deviation closed. Opaque no-box
     treatment — solid field, no blur, no framed dialog. */
  background: rgb(7, 8, 10);
}

.admin-modal__dialog {
  width: 100%;
  max-width: 680px;
  max-height: 90vh;
  overflow-y: auto;
  padding: var(--space-7) var(--gutter);
}

.admin-modal__title {
  font-size: var(--font-h2);
  letter-spacing: var(--track-h2);
  margin: 0 0 var(--space-4);
}

.admin-modal__close {
  position: absolute;
  top: var(--space-5);
  right: var(--space-6);
  background: transparent;
  border: none;
  color: var(--text-faint);
  font-size: 1.8rem;
  line-height: 1;
  cursor: pointer;
}

.admin-modal__close:hover {
  color: var(--text-primary);
}

.admin-form-field {
  display: block;
  margin-bottom: var(--space-5);
}

.admin-form-label {
  display: block;
  font-size: var(--font-label);
  font-weight: 700;
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--accent);
  margin-bottom: var(--space-2);
}

.admin-input,
.admin-select,
.admin-textarea {
  display: block;
  width: 100%;
  background: transparent;
  border: none;
  border-bottom: 1px solid rgba(255, 255, 255, 0.22);
  color: var(--text-primary);
  font-family: inherit;
  font-size: var(--font-body);
  padding: var(--space-2) 0;
}

.admin-input:focus,
.admin-select:focus,
.admin-textarea:focus {
  outline: none;
  border-bottom-color: var(--accent);
}

.admin-select option {
  background: var(--base-bg);
  color: var(--text-primary);
}

.admin-form-actions {
  display: flex;
  gap: var(--space-4);
  margin-top: var(--space-7);
}

/* Video admin actions */
.video-admin-controls {
  display: none;
  margin: var(--space-4) 0;
  gap: var(--space-4);
}

body.is-editing .video-admin-controls {
  display: flex;
  align-items: center;
}

.clip__poster-change {
  display: none;
}

body.is-editing .clip__poster-change {
  position: absolute;
  top: var(--space-3);
  left: var(--space-3);
  z-index: 10;
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  background: rgba(7, 8, 10, 0.85);
  color: var(--accent);
  font-size: var(--font-label);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  cursor: pointer;
}

.clip__remove {
  display: none;
}

body.is-editing .clip__remove {
  position: absolute;
  top: var(--space-3);
  right: var(--space-3);
  z-index: 10;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  background: rgba(7, 8, 10, 0.85);
  border: none;
  color: #ff8888;
  font-size: 1.4rem;
  line-height: 1;
  cursor: pointer;
}

.clip__remove:hover {
  color: #ff4444;
}

/* Hero cover change button */
.collection-hero__cover-btn {
  display: none;
  margin-top: var(--space-4);
  color: var(--accent);
  font-size: var(--font-label);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  background: transparent;
  border: none;
  cursor: pointer;
  padding: 0;
}

body.is-editing .collection-hero__cover-btn {
  display: inline-block;
}

/* ==========================================================================
   M5 blocks — public renderer markup emitted by script.js (M5-LOCKED-SPEC
   §§7.4, 9.1, 9.2, 10). All whitespace/typography/accent-ink only; no cards,
   panels, pills, borders, radius, shadows, backdrop-filter (§4, §14.5).
   ========================================================================== */

/* --- Spacer block (§7.4 Spacer) -------------------------------------------
   Canonical dual-level matte story dividers with generous vertical breathing. */
.photo-spacer {
  position: relative;
  border: 0;
  clear: both;
}

.photo-spacer--page {
  width: min(100% - 2 * var(--gutter), 1200px);
  height: 1px;
  margin: var(--space-8) auto;
  background: linear-gradient(
    90deg,
    transparent 0%,
    rgba(255, 255, 255, 0.08) 20%,
    rgba(255, 255, 255, 0.08) 80%,
    transparent 100%
  );
}

.photo-spacer--chapter {
  width: min(100% - 2 * var(--gutter), 1360px);
  height: 1px;
  margin: var(--space-9) auto var(--space-8);
  background: linear-gradient(
    90deg,
    transparent 0%,
    rgba(255, 255, 255, 0.14) 8%,
    rgba(255, 255, 255, 0.14) 92%,
    transparent 100%
  );
}

/* --- Split block (§7.4 Split, §9.2) ---------------------------------------
   Large image column opposite a prose column. Image commands the row
   (~two-thirds), prose stays in --measure-prose. Alternate direction via
   the variant. Stack in reading order <= 768px with no horizontal overflow. */
.photo-split {
  display: grid;
  grid-template-columns: minmax(0, 1.9fr) minmax(0, 1fr);
  gap: var(--space-8);
  align-items: center;
  max-width: var(--measure-full);
  margin: 0 auto;
  padding: 0 var(--gutter) var(--space-10);
}

.photo-split--image-right {
  /* Reverse visual order: media on the right, prose on the left. Uses
     grid-column ordering so source order (media, content) stays stable
     for AT while the visual side flips. */
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.9fr);
}

.photo-split--image-right .photo-split__media {
  grid-column: 2;
  grid-row: 1;
}

.photo-split--image-right .photo-split__content {
  grid-column: 1;
  grid-row: 1;
}

.photo-split__media {
  min-width: 0; /* prevent grid blowout / horizontal overflow */
}

/* Split caption + story: block-level stacking under the image. Without this
   the two spans rendered inline and ran together ("stuck together, no
   spacing" — client report, 2026-08-28 handout item 5). Mirrors the
   .photo-chapter figcaption treatment. */
.photo-split__media figcaption {
  margin-top: var(--space-4);
  color: var(--text-secondary);
}

.photo-split__media figcaption .cap-label {
  display: block;
  font-size: var(--font-label);
  font-weight: 700;
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--text-primary);
}

.photo-split__media figcaption .cap-quote {
  display: block;
  margin-top: var(--space-2);
  font-size: var(--font-h3);
  font-style: italic;
  font-weight: 400;
  color: var(--text-secondary);
}

.photo-split__media img {
  display: block;
  width: 100%;
  height: auto;
}

.photo-split__content {
  max-width: var(--measure-prose);
  padding: var(--space-4) 0;
}

.photo-split__content h2 {
  margin: 0 0 var(--space-4);
  font-size: var(--font-h2);
  font-weight: 600;
  letter-spacing: var(--track-h2);
  line-height: var(--lh-snug);
}

.photo-split__content p + p {
  margin-top: var(--space-4);
  color: var(--text-secondary);
  line-height: var(--lh-loose);
}

.photo-split__content p {
  color: var(--text-secondary);
  line-height: var(--lh-loose);
}

.photo-split__content .meta {
  margin-top: var(--space-5);
  color: var(--text-faint);
  font-size: var(--font-meta);
}

@media (max-width: 768px) {
  .photo-split,
  .photo-split--image-right {
    grid-template-columns: 1fr;
    gap: var(--space-6);
    padding-bottom: var(--space-9);
  }

  .photo-split--image-right .photo-split__media,
  .photo-split--image-right .photo-split__content {
    grid-column: auto;
    grid-row: auto;
  }

  /* Reading order on narrow screens: image first, then prose, regardless
     of variant — matches §9.2 "stack in reading order". */
  .photo-split__media { order: 1; }
  .photo-split__content { order: 2; }
}

/* --- Contact block (§7.4 Contact) -----------------------------------------
   Typography-first closing content. Reuses the existing .link-list language
   (§9.9) for the links. No card/panel/frame. Sits in the prose measure. */
.contact-block {
  max-width: var(--measure-prose-px);
  margin: var(--space-10) auto 0;
  padding: 0 var(--gutter) var(--space-9);
}

.contact-block h2 {
  margin: 0 0 var(--space-5);
  font-size: var(--font-h2);
  font-weight: 600;
  letter-spacing: var(--track-h2);
  line-height: var(--lh-snug);
}

.contact-block p {
  color: var(--text-secondary);
  line-height: var(--lh-loose);
}

.contact-block p + p {
  margin-top: var(--space-4);
}

.contact-block p + .link-list,
.contact-block .link-list + p {
  margin-top: var(--space-6);
}

/* --- Section rail (§10) ----------------------------------------------------
   Plain-text right-edge rail. Heading entries appear only when a blocks page
   has >= 3 section-head blocks (script.js gates generation). No box, no
   border, no shadow. Current section = accent ink + full underline.

   2026-09-11 (client interview): the rail no longer holds the scroll-to-top
   arrow — that control moved to the bottom-right corner (.to-top below), and
   the rail exists only for section headings.

   2026-09-11 ROUND 6 (client finding 1): the rail had been re-anchored to a
   fixed top offset, which parked the list in the top half of the screen; it
   is once again centred on the VIEWPORT (`top: 50%` + `translateY(-50%)`,
   the pre-round-5 geometry). Only the right edge changed from the old
   `--space-5` to `--gutter`, so the rail lines up with the bottom-right
   arrow. Centred, it is nowhere near .site-top, so the z-index note that
   justified the top anchor no longer applies; 22 is kept only to stay above
   the header in the mid-scroll cases the header can still reach. Hidden
   <= 900px, where the heading entries would crowd a phone. */
.section-rail {
  position: fixed;
  top: 50%;
  right: var(--gutter);
  z-index: 22;
  display: grid;
  justify-items: end;
  gap: var(--space-3);
  max-width: 40vw;
  transform: translateY(-50%);
  text-align: right;
  pointer-events: none;
}

.section-rail__link {
  position: relative;
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: var(--space-1) 0;
  color: var(--text-faint);
  font-size: var(--font-meta);
  letter-spacing: var(--track-nav);
  text-decoration: none;
  text-transform: uppercase;
  pointer-events: auto;
  transition: color var(--dur-med) var(--ease-out);
}

.section-rail__link:hover,
.section-rail__link:focus-visible {
  color: var(--text-primary);
}

.section-rail__link::after {
  position: absolute;
  bottom: 0.1em;
  right: 0;
  left: auto;
  width: 0;
  height: 1px;
  background: var(--accent);
  content: "";
  transform: none;
  transition: width var(--dur-med) var(--ease-out);
}

.section-rail__link:hover::after,
.section-rail__link:focus-visible::after,
.section-rail__link.is-current::after {
  width: 100%;
}

.section-rail__link.is-current {
  color: var(--accent);
  font-weight: 700;
}

.section-rail__link:focus-visible {
  outline: 1px solid var(--focus-ring);
  outline-offset: 3px;
}

@media (max-width: 900px) {
  /* Heading entries would crowd a phone; without them the rail is empty. */
  .section-rail {
    display: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .section-rail__link,
  .section-rail__link::after,
  .to-top {
    transition: none;
  }
}

/* --- Scroll to top (client 2026-09-11) --------------------------------------
   The arrow moved from the top-right corner to the BOTTOM-right corner: a
   "return to top" control is used after scrolling down, so it belongs at the
   bottom (client: "the scroll up arrow should be at the bottom"). Plain text
   glyph, no box, no background: ↑ already renders in the site's own JetBrains
   Mono (the latin subsets carry U+2191).

   The glyph keeps the body size/ink of the retired top-corner arrow; the
   hit area is an invisible >= 44x44px field (the §9.1 invisible-target
   pattern) with the glyph centred in it. Hover is ink-only — at this corner
   there are no heading entries around it to be confused with, so it needs
   no underline gesture to read as a control. While editing, the opaque
   admin bar owns the bottom edge of the page, so the control hides. */
.to-top {
  position: fixed;
  right: var(--gutter);
  bottom: var(--space-4);
  z-index: 22;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  padding: 0;
  background: none;
  border: none;
  color: var(--text-secondary);
  font-size: var(--font-body);
  line-height: 1;
  cursor: pointer;
  transition: color var(--dur-fast) var(--ease-out);
}

.to-top:hover,
.to-top:focus-visible {
  color: var(--text-primary);
}

.to-top:focus-visible {
  outline: 1px solid var(--focus-ring);
  outline-offset: 3px;
}

/* In the admin context the opaque admin bar owns the bottom edge of the
   page (fixed, full width, z-index 1000), so the corner control hides while
   a bar is present — authenticated or ?advanced=1. body.is-editing is NOT
   the signal: it only toggles the inline-editing affordances (the bar is
   there in admin mode either way), and it can be switched off while the bar
   stays. :has() keeps this purely presentational (Chrome 105+, in line with
   the 100svh units the site already uses). */
body:has(.admin-bar) .to-top {
  display: none;
}

/* --- In-panel curated / show-more entries (§5.2) --------------------------
   The curated overview is the first, visually distinct list entry; Show more
   follows. Both are separated from the subcollection list and from each other
   using whitespace, typography scale, and accent ink only — no border, pill,
   filled background, or enclosing panel. script.js emits these as item-level
   modifiers on .clist__item (.clist__item--curated / --more); the container
   aliases below are harmless if ever used. */
.clist__item--curated {
  color: var(--accent);
  font-size: var(--font-h3);
  font-weight: 600;
  letter-spacing: var(--track-h2);
  text-transform: none;
  opacity: 1;
  margin-bottom: var(--space-4);
}

.clist__item--curated::after {
  background: var(--accent);
}

.clist__item--more {
  color: var(--text-secondary);
  font-size: var(--font-meta);
  letter-spacing: var(--track-nav);
  text-transform: uppercase;
  opacity: 0.85;
  margin-bottom: var(--space-6);
}

.clist__item--more::after {
  background: var(--accent);
}

/* --------------------------------------------------------------------------
   In-place Admin & Block Editing Affordances

   NOTE (2026-09-11): five rules in this section asked for
   `font-size: var(--font-small)`, and `--font-small` has never been defined
   anywhere in the repo — so those declarations were inert and every editor
   button silently rendered at the INHERITED BODY size (17px at desktop). The
   brief that reviewed these controls flagged exactly this ("referenced but
   undefined … propose the minimal correct fix rather than inventing a
   literal"). The fix is to compose from the existing scale: `--font-meta`
   (13px), the site's established small size, which is what the other compact
   controls already use (`.clist__item`, `.section-rail__link`, `.lang`).
   No new token was invented. These rules only exist in edit mode.
   -------------------------------------------------------------------------- */

body.is-editing [contenteditable="true"] {
  outline: 1px dashed rgba(196, 74, 44, 0.4);
  outline-offset: 4px;
  border-radius: 2px;
  cursor: text;
  transition: outline-color var(--dur-fast) var(--ease-out);
}

body.is-editing [contenteditable="true"]:focus {
  outline: 2px solid var(--accent);
  background: rgba(196, 74, 44, 0.06);
}

.block-inplace-wrap {
  position: relative;
  transition: outline 150ms ease;
}

body.is-editing .block-inplace-wrap:hover {
  outline: 1px dashed rgba(255, 255, 255, 0.15);
}

.block-inplace-toolbar {
  position: absolute;
  top: var(--space-2);
  right: var(--space-4);
  z-index: 40;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  background: rgba(14, 16, 20, 0.92);
  border: 1px solid rgba(255, 255, 255, 0.15);
  padding: 4px 8px;
  border-radius: 4px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
}

.block-inplace-toolbar button {
  background: none;
  border: 0;
  color: var(--text-secondary);
  font-size: var(--font-meta);
  cursor: pointer;
  padding: 2px 6px;
  border-radius: 2px;
  transition: color var(--dur-fast) var(--ease-out), background var(--dur-fast) var(--ease-out);
}

.block-inplace-toolbar button:hover {
  color: var(--text-primary);
  background: rgba(255, 255, 255, 0.1);
}

.block-inplace-toolbar button.btn-del:hover {
  color: var(--danger, #ff6666);
  background: rgba(255, 70, 70, 0.15);
}

.block-inplace-toolbar .block-type-tag {
  font-size: var(--font-label);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--accent);
  margin-right: var(--space-2);
  font-weight: 700;
}

.block-inplace-insert {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  margin: var(--space-4) auto;
  position: relative;
  z-index: 20;
}

.block-inplace-insert__btn {
  background: rgba(20, 24, 30, 0.9);
  border: 1px dashed rgba(196, 74, 44, 0.6);
  color: var(--accent);
  padding: 6px 16px;
  border-radius: 20px;
  font-size: var(--font-meta);
  letter-spacing: var(--track-nav);
  text-transform: uppercase;
  font-weight: 600;
  cursor: pointer;
  transition: all var(--dur-fast) var(--ease-out);
}

.block-inplace-insert__btn:hover {
  background: var(--accent);
  color: #fff;
  border-style: solid;
}

.block-inplace-palette {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-2);
  background: rgba(14, 16, 20, 0.96);
  border: 1px solid rgba(255, 255, 255, 0.18);
  padding: var(--space-3) var(--space-4);
  border-radius: 6px;
  margin: var(--space-2) auto;
  max-width: 800px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
}

.block-inplace-palette button {
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid rgba(255, 255, 255, 0.1);
  color: var(--text-primary);
  font-size: var(--font-meta);
  padding: 4px 10px;
  border-radius: 3px;
  cursor: pointer;
}

.block-inplace-palette button:hover {
  background: var(--accent);
  color: #fff;
}

/* Empty slots & interactive add affordances */
.empty-slot-box {
  width: 100%;
  min-height: 200px;
  background: rgba(255, 255, 255, 0.03);
  border: 1px dashed rgba(196, 74, 44, 0.5);
  border-radius: 4px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-4);
  margin: var(--space-2) 0;
}

.empty-slot-btn {
  background: rgba(196, 74, 44, 0.15);
  border: 1px solid var(--accent);
  color: var(--accent);
  padding: var(--space-2) var(--space-4);
  border-radius: 4px;
  font-size: var(--font-meta);
  font-weight: 600;
  cursor: pointer;
  transition: all var(--dur-fast) var(--ease-out);
}

.empty-slot-btn:hover {
  background: var(--accent);
  color: #fff;
}

.photo-cover--empty {
  min-height: 350px;
  background: rgba(255, 255, 255, 0.02);
  border: 1px dashed rgba(196, 74, 44, 0.5);
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  margin-bottom: var(--space-6);
  padding: var(--space-6);
}

/* An EMPTY media slot is a legal published value as of 2026-09-10
   (SPEC-2026-09-10-in-use-photo-deletion.md §5): deleting an in-use
   photograph clears the reference instead of leaving the document
   unsaveable. On a published page the slot renders as nothing at all — no
   photograph, no dashed box, no "choose a photo" button. The fill-me control
   appears only while editing, where the owner can put a photo back.
   The block itself, and every word of text it owns, is untouched: an empty
   cover still shows its title, an empty picture still shows its caption.
   NOTE: this block sits AFTER the .empty-slot-box / .photo-cover--empty
   definitions above — equal specificity, so source order is what wins. */
.empty-slot-box,
.empty-slot-btn {
  display: none;
}

/* The empty cover is a real section carrying the page title, so it is not
   hidden — the editor chrome is stripped off it and the title alone remains. */
.photo-cover--empty {
  min-height: 0;
  background: none;
  border: 0;
  margin-bottom: 0;
  padding: 0;
}

body.is-editing .empty-slot-box {
  display: flex;
}

body.is-editing .empty-slot-btn {
  display: inline-block;
}

body.is-editing .photo-cover--empty {
  min-height: 350px;
  background: rgba(255, 255, 255, 0.02);
  border: 1px dashed rgba(196, 74, 44, 0.5);
  margin-bottom: var(--space-6);
  padding: var(--space-6);
}

.photo-cover__media-empty {
  margin-bottom: var(--space-4);
}

.add-paragraph-btn {
  display: inline-block;
  margin-top: var(--space-3);
  background: none;
  border: 1px dashed rgba(255, 255, 255, 0.2);
  color: var(--text-faint);
  font-size: var(--font-meta);
  padding: 4px 12px;
  border-radius: 12px;
  cursor: pointer;
}

.add-paragraph-btn:hover {
  color: var(--accent);
  border-color: var(--accent);
}

.photo-inplace-badge {
  position: absolute;
  bottom: 8px;
  right: 8px;
  z-index: 10;
  background: rgba(0, 0, 0, 0.75);
  border: 1px solid rgba(255, 255, 255, 0.2);
  color: #fff;
  /* Client: F/H controls are "a bit too small" — enlarged for comfortable
     clicking (handout §4, 2026-08-28). */
  font-size: 13px;
  padding: 6px 14px;
  border-radius: 3px;
  cursor: pointer;
}
.photo-inplace-badge:hover {
  background: var(--accent);
}

/* H — per-photo random-showcase override toggles (Foto/Popis/Príbeh),
   rendered under the ⚙ badge on each random stream item in edit mode. */
.random-override-strip {
  position: absolute;
  bottom: 46px;
  right: 8px;
  display: flex;
  gap: 6px;
  z-index: 10;
}
.random-override-btn {
  background: rgba(0, 0, 0, 0.75);
  border: 1px solid rgba(255, 255, 255, 0.2);
  color: #fff;
  font-size: 11px;
  padding: 5px 10px;
  border-radius: 3px;
  cursor: pointer;
}
.random-override-btn:hover {
  border-color: var(--accent);
  color: var(--accent);
}
.random-override-btn.is-off {
  color: rgba(255, 136, 136, 0.9);
  border-color: rgba(255, 136, 136, 0.4);
}

/* Uniform inter-block rhythm (client G): every rendered block separator must
   be --space-8. Block widths stay as-is; margin bottom added only. Spacers
   still add their own big margins; those remain the only bigger gaps.
   .photo-carousel is excluded: it sizes itself to the viewport on /photo/,
   and an extra bottom margin made the page taller than the viewport
   (client regression report 2026-08-28). */
#main-content > section:not(.photo-spacer):not(.photo-carousel),
#main-content > header:not(.photo-spacer),
#main-content > figure:not(.photo-spacer),
#main-content > div:not(.photo-spacer):not(.block-inplace-insert):not(.block-inplace-toolbar):not(.block-inplace-palette):not(.photo-spacer):not(.section-rail):not(.empty-slot-box):not(.landing__veil):not(.landing__stack) {
  margin-bottom: var(--space-8) !important;
}

/* The second half of uniform rhythm: several block classes carry their own
   margin-top (.photo-chapter 96px, .section-head 96px, .photo-cover 76px).
   Adjacent vertical margins collapse, so each gap was max(64px, next
   margin-top) and varied per block type. Zero the top margin on uniform
   children so every gap is exactly --space-8. The cover keeps its top
   margin (header clearance under the absolute .site-top); spacers keep
   theirs (they ARE the bigger gap). */
#main-content > section:not(.photo-spacer):not(.photo-cover):not(.photo-carousel),
#main-content > header:not(.photo-spacer),
#main-content > figure:not(.photo-spacer),
#main-content > div:not(.photo-spacer):not(.block-inplace-insert):not(.block-inplace-toolbar):not(.block-inplace-palette):not(.photo-spacer):not(.section-rail):not(.empty-slot-box):not(.landing__veil):not(.landing__stack) {
  margin-top: 0 !important;
}

/* The veil + stack are excluded above: they are landing fixtures, not
   content blocks. The veil is absolutely positioned (inset: 0) — a 64px
   margin-bottom shrank it from the bottom and left an UNVEILED strip of
   photo exactly the admin bar's height at the viewport bottom (the client's
   "whole unblurred gap where the admin bar should appear", and in admin mode
   the couple-pixel gap above the bar). Found + fixed 2026-09-06. */
body.is-editing .photo-spacer.is-editing-spacer {
  min-height: 48px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-top: 1px dashed rgba(255, 255, 255, 0.15);
  border-bottom: 1px dashed rgba(255, 255, 255, 0.15);
  /* `auto` horizontal centering preserved: `0` here overrode the base
     `margin: ... auto` and made the spacer hug the left edge in the editor
     (client report, 2026-08-28 handout item 3). */
  margin: var(--space-4) auto;
  background: rgba(255, 255, 255, 0.02);
}

.spacer-toggle-btn {
  background: rgba(0, 0, 0, 0.6);
  border: 1px solid rgba(255, 255, 255, 0.2);
  color: var(--text-faint);
  font-size: var(--font-meta);
  padding: 4px 12px;
  border-radius: 12px;
  cursor: pointer;
}

.spacer-toggle-btn:hover {
  color: var(--accent);
  border-color: var(--accent);
}

/* Curated + Show more sit above the subcollection list; the first real
   subcollection entry follows the Show-more's bottom margin. The existing
   .clist__item base already provides the underline gesture + hover ink. */

/* Container-level aliases (brief named .clist--curated / .clist--more);
   if a future emitter wraps these entries in a scoped container, the same
   whitespace/type/accent rules apply to its direct item children. */
.clist--curated > .clist__item,
.clist--more > .clist__item {
  color: var(--accent);
  font-size: var(--font-h3);
  font-weight: 600;
  letter-spacing: var(--track-h2);
  text-transform: none;
  opacity: 1;
}

/* "More coming soon" teaser collections (change #2, 2026-09-06): not
   clickable, name + splash only, explicit work-in-progress text.
   Fix #3 (client 2026-09-06, corrected): the coming-soon text is a
   standalone sign overlaying the middle of the WHOLE card (not a re-flow
   of the text block) — a clear visual "not clickable" marker. The name
   block stays at the bottom as on every other panel. */
.photo-carousel__slide--soon {
  cursor: default;
}

.photo-carousel__coming-soon {
  position: absolute;
  inset: 0;
  z-index: 2;
  display: grid;
  place-items: center;
  margin: 0;
  pointer-events: none;
  color: var(--accent);
  font-size: calc(var(--font-h2) * 1.15);
  font-weight: 600;
  letter-spacing: var(--track-h2);
  line-height: var(--lh-snug);
  text-align: center;
}

.clist__item--soon {
  cursor: default;
  color: var(--text-faint);
}

.clist__item--soon:hover {
  color: var(--text-faint);
}

.clist__item--soon .clist__item-tag {
  margin-left: var(--space-3);
  color: var(--accent);
  font-size: var(--font-label);
  font-weight: 700;
  letter-spacing: var(--track-label);
  text-transform: uppercase;
}

/* Per-paragraph controls in the in-place editor (change #1, 2026-09-06):
   remove + reorder arrows, visible only in edit mode. */
.para-ctl {
  position: absolute;
  top: 4px;
  right: 0;
  display: none;
  gap: 2px;
  z-index: 5;
}

body.is-editing .para-ctl {
  display: inline-flex;
}

.para-ctl button {
  background: rgba(0, 0, 0, 0.6);
  border: none;
  color: var(--text-faint);
  font-size: 11px;
  line-height: 1;
  padding: 3px 6px;
  cursor: pointer;
}

.para-ctl button:hover {
  color: var(--text-primary);
}

.para-ctl button:disabled {
  opacity: 0.3;
  cursor: default;
}

body.is-editing [contenteditable="true"] {
  position: relative;
}

body.is-editing .photo-split__content [contenteditable="true"],
body.is-editing .interlude [contenteditable="true"],
body.is-editing .photo-intro__content [contenteditable="true"],
body.is-editing .contact-block [contenteditable="true"] {
  padding-right: 64px;
}

/* --------------------------------------------------------------------------
   In-place contact-link editor hooks (round 3): the inline href field and its
   transient validation message. Editor-only — both classes are created by
   admin/in-place.js inside body.is-editing on the photography page, where
   admin.css (the only place --danger is declared) is not loaded, hence the
   fallback to the same locked #d98a7a. Type and ink only: no box, panel, pill,
   radius, shadow or framing border. The generic
   `body.is-editing [contenteditable="true"]` dashed underline/outline above is
   what keeps the field obviously editable, so nothing here removes it.
   -------------------------------------------------------------------------- */

.link-url {
  font-family: var(--font-mono);
  font-size: var(--font-meta);
  color: var(--text-faint);
  overflow-wrap: anywhere;
}

.link-url-error {
  color: var(--danger, #d98a7a);
  font-size: var(--font-meta);
}
