
        /* =========================================================
           FONTS — self-hosted variable woff2 (OFL: /fonts/OFL-*.txt), split like Google's latin / latin-ext (latin-ext declared first, so the latin file wins where the ranges overlap, e.g. œ),
           so a French or English page downloads only the latin files. Axes as before: Bodoni Moda opsz 6–96 wght 400–600,
           Geist wght 300–600, Geist Mono wght 400–500. The family names stay exact (the title card waits on "Bodoni Moda").
           Fallback faces: Times New Roman / Arial scaled to Bodoni Moda / Geist (advance width of the bio lead, FR + EN;
           ascent, descent and line gap from the web font), so the swap does not move a line.
           ========================================================= */
        @font-face { font-family: 'Bodoni Moda'; font-style: normal; font-weight: 400 600; font-display: swap; src: url('/fonts/bodoni-moda-v2005-latin-ext.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: 'Bodoni Moda'; font-style: normal; font-weight: 400 600; font-display: swap; src: url('/fonts/bodoni-moda-v2005-latin.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: 'Bodoni Moda'; font-style: italic; font-weight: 400 600; font-display: swap; src: url('/fonts/bodoni-moda-italic-v2005-latin-ext.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: 'Bodoni Moda'; font-style: italic; font-weight: 400 600; font-display: swap; src: url('/fonts/bodoni-moda-italic-v2005-latin.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: 'Geist'; font-style: normal; font-weight: 300 600; font-display: swap; src: url('/fonts/geist-1.9.0-latin-ext.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: 'Geist'; font-style: normal; font-weight: 300 600; font-display: swap; src: url('/fonts/geist-1.9.0-latin.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: 'Geist Mono'; font-style: normal; font-weight: 400 500; font-display: swap; src: url('/fonts/geist-mono-1.9.0-latin-ext.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: 'Geist Mono'; font-style: normal; font-weight: 400 500; font-display: swap; src: url('/fonts/geist-mono-1.9.0-latin.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: 'Bodoni Moda Fallback'; font-style: normal; src: local('Times New Roman'), local('TimesNewRomanPSMT'); size-adjust: 113.82%; ascent-override: 98.84%; descent-override: 35.14%; line-gap-override: 0%; }
        @font-face { font-family: 'Bodoni Moda Fallback'; font-style: italic; src: local('Times New Roman Italic'), local('TimesNewRomanPS-ItalicMT'); size-adjust: 106.17%; ascent-override: 105.96%; descent-override: 37.67%; line-gap-override: 0%; }
        @font-face { font-family: 'Geist Fallback'; font-style: normal; src: local('Arial'), local('ArialMT'), local('Helvetica'); size-adjust: 104.5%; ascent-override: 96.18%; descent-override: 28.23%; line-gap-override: 0%; }

        /* =========================================================
           TOKENS — « Le portrait en profondeur » (v4, 2026-10-21)
           One gradient only — Mallory's R→N: 呂色 Ro-iro #0D0D0D → 乳白色 Nyūhaku-iro #F3F3F3, the library's 8 stops, exact
           (a black-to-white band in the middle of its run: Ro-iro until 25%, Nyūhaku from 75%).
           Its dark end is the ground of every chapter (each chapter shows a window of it, hand to hand at 42.86% = #4C4C4C);
           its middle is the light — the pool behind the receding print, the glow behind the player, the last field under the footer —
           never brighter than #646464 where text can pass (Nyūhaku stays ≥ 5.3:1);
           its bright end is the text and the string: Nyūhaku everywhere, hierarchy by size and weight, never grey.
           ========================================================= */
        :root {
            --roiro: #0D0D0D;       /* 呂色 Ro-iro */
            --nyuhaku: #F3F3F3;     /* 乳白色 Nyūhaku-iro */
            --rn:  #0D0D0D 25%, #181818 32.14%, #353535 39.29%, #646464 46.43%, #9C9C9C 53.57%, #CBCBCB 60.71%, #E8E8E8 67.86%, #F3F3F3 75%;
            --rnr: #F3F3F3 25%, #E8E8E8 32.14%, #CBCBCB 39.29%, #9C9C9C 46.43%, #646464 53.57%, #353535 60.71%, #181818 67.86%, #0D0D0D 75%;
            --rn-hand: color-mix(in srgb, #353535 50%, #646464);   /* the gradient at 42.86% (#4C4C4C): where the chapters hand over */
            --rn-pool: #646464 0%, #353535 33.33%, #181818 66.67%, #0D0D0D 100%;   /* its lit half as a pool of light, no brighter than #646464 */
            --tone: #353535;        /* an R→N stop, multiplied into the photograph */

            --text: var(--nyuhaku);
            --text-2: var(--nyuhaku);
            --accent: var(--nyuhaku);   /* the string is the gradient's light end */
            --ink: var(--roiro);
            --ph: #181818;          /* image placeholder */
            --line: color-mix(in srgb, var(--nyuhaku) 14%, transparent);
            --line-strong: color-mix(in srgb, var(--nyuhaku) 55%, transparent);

            --font-display: 'Bodoni Moda', 'Bodoni Moda Fallback', 'Bodoni 72', Didot, 'Times New Roman', serif;
            --font-text: 'Geist', 'Geist Fallback', 'Helvetica Neue', Arial, sans-serif;
            --font-label: 'Geist Mono', ui-monospace, 'SF Mono', Menlo, monospace;
            /* One hierarchy: display / lead / title / body / label */
            --t-display-2: clamp(3.25rem, 8vw, 8.5rem);
            --t-display-3: clamp(2rem, 3.1vw, 3rem);
            --t-lead: clamp(1.5rem, 2.3vw, 2.375rem);
            --t-quote: clamp(1.25rem, 1.5vw, 1.75rem);
            --t-title: clamp(1.375rem, 0.6vw + 1.1rem, 1.625rem);
            --t-body: 1.125rem;     /* 18 px (17 px on phones) */
            --t-body-s: 1rem;
            --t-label: 0.75rem;
            --measure: 33em;        /* ≈ 62–68 characters of Geist */

            --s-1: 4px; --s-2: 8px; --s-3: 12px; --s-4: 16px; --s-5: 24px; --s-6: 32px; --s-7: 48px; --s-8: 64px; --s-9: 96px;
            --gutter: clamp(16px, 4vw, 64px);
            --gap: clamp(16px, 2vw, 32px);
            --maxw: 1440px;
            --section: clamp(88px, 9vw, 152px);
            --title-gap: clamp(40px, 5vw, 88px);
            --nav-h: 72px;
            --control: 44px;
            --hair: 1px;
            --string: 2px;

            /* MOTION — one system for the whole page.
               micro: hover, focus, state · component: strings, nav, panel items · reveal: blocks fade up 14px (720) ·
               reveal-l: chapter-title lines rise, prints settle in their frames (1000) · scene: curtain, sheets.
               Exits ≈ 65 % of their entrances. */
            --t-micro: 240ms;
            --t-xfade: 350ms;
            --t-exit: 380ms;
            --t-comp: 580ms;
            --t-reveal: 720ms;
            --t-reveal-l: 1000ms;   /* chapter-title line rises, print settles */
            --t-scene: 950ms;
            --t-zoom: 1200ms;       /* hover zoom inside a frame */
            --t-cut-out: 180ms; --t-cut-in: 420ms;   /* long anchor jumps: dissolve, jump, dissolve */
            --stagger: 40ms;
            --e-out: cubic-bezier(0.22, 1, 0.36, 1);     /* micro + component */
            --e-expo: cubic-bezier(0.16, 1, 0.3, 1);     /* reveals, settles */
            --e-scene: cubic-bezier(0.76, 0, 0.24, 1);   /* curtain, sheets (in-out) */
            --e-exit: cubic-bezier(0.4, 0, 0.7, 0.2);    /* exits accelerate away */

            /* The intro, one continuous gesture (first visit ≈ 2.9 s, later visits within 7 days ≤ 1 s) */
            --i-lift: var(--t-scene);  /* the curtain */
            --i-settle: 1150ms;        /* photograph + cut-out scale 1.06 → 1 */
            --i-name-d: 200ms; --i-name-s: 34ms; --i-name: 900ms;    /* the name rises letter by letter */
            --i-cut-d: 480ms; --i-cut: 700ms; --i-rise: 1100ms; --i-rise-y: 12px;   /* Roman arrives last */
            --i-ui-d: 650ms; --i-ui: 700ms;                           /* quote, role, navigation */
            --i-sign-d: 700ms;         /* the signature starts as the curtain clears the photograph */

            /* The opening stage: how long the planes take to separate, how far the Écouter sheet overlaps it, and the sheet's top padding */
            --sep: 100vh;
            --overlap: 36vh;
            --sheet-top: clamp(72px, 7vw, 120px);
        }
        @supports (height: 1svh) { :root { --sep: 100svh; --overlap: 36svh; } }
        .intro-fast { --i-sign-d: 250ms; --i-lift: 650ms; --i-settle: 800ms; --i-name-d: 60ms; --i-name-s: 16ms; --i-name: 600ms; --i-cut-d: 200ms; --i-cut: 320ms; --i-rise: 600ms; --i-ui-d: 250ms; --i-ui: 500ms; }

        /* =========================================================
           BASE
           ========================================================= */
        *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
        html { background: var(--roiro); -webkit-text-size-adjust: 100%; text-size-adjust: 100%; scroll-behavior: smooth; overflow-x: clip; }
        html.lenis, html.lenis body { height: auto; }
        html.lenis { scroll-behavior: auto; }
        .lenis.lenis-stopped { overflow: clip; }
        .lenis.lenis-smooth iframe { pointer-events: none; }
        body {
            background: var(--roiro); color: var(--text);
            font-family: var(--font-text); font-size: var(--t-body); line-height: 1.7; font-weight: 400;
            -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-rendering: optimizeLegibility;
            overflow-x: clip;
        }
        img, picture, svg, iframe { display: block; max-width: 100%; }
        a { color: inherit; text-decoration: none; }
        button { font: inherit; color: inherit; background: none; border: 0; border-radius: 0; cursor: pointer; }
        ol, ul { list-style: none; }
        ::selection { background: var(--accent); color: var(--ink); }
        /* Focus: a light ring, ≥ 3:1 on every ground (the blue string is not, on the lighter ends) */
        :focus-visible { outline: var(--string) solid var(--text); outline-offset: 4px; }
        .sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; }
        .skip-link { position: fixed; top: var(--s-3); left: var(--s-3); z-index: 6000; padding: var(--s-3) var(--s-4); background: var(--text); color: var(--ink); transform: translateY(-160%); transition: transform var(--t-micro) var(--e-out); }
        .skip-link:focus { transform: none; }
        :root:not([lang="fr"]) [data-lang="fr"],
        :root:not([lang="en"]) [data-lang="en"] { display: none !important; }

        .wrap { width: 100%; max-width: calc(var(--maxw) + var(--gutter) * 2); margin-inline: auto; padding-inline: var(--gutter); }
        .grid12 { display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); column-gap: var(--gap); }
        .mono { font-family: var(--font-label); font-size: var(--t-label); line-height: 1.4; letter-spacing: 0.14em; text-transform: uppercase; font-weight: 400; }
        .h2 { font-family: var(--font-display); font-weight: 400; font-size: var(--t-display-2); line-height: 0.92; letter-spacing: -0.025em; text-wrap: balance; }
        .lead { font-family: var(--font-display); font-style: italic; font-weight: 400; font-size: var(--t-lead); line-height: 1.18; letter-spacing: -0.01em; text-wrap: pretty; }

        /* Chapter titles: the word rises from its baseline as one line, inside a mask (room kept for accents and descenders) */
        .h2 .ln { display: inline-block; vertical-align: top; overflow: hidden; overflow: clip; padding: 0.14em 0.04em 0.22em; margin: -0.14em -0.04em -0.22em; }
        .h2 .ln-in { display: inline-block; }
        .js .h2 .ln-in { transform: translate3d(0, 0.9em, 0); }
        .ch-head.in .h2 .ln-in { transform: none; transition: transform var(--t-reveal-l) var(--e-expo); }

        /* will-change is granted only while its scene is near (the clock toggles these classes) */
        .wc-hero .hp, .wc-hero .hero-ui { will-change: transform, opacity; }
        .wc-hero .hp-dim { will-change: opacity; }
        .wc-bio .tunnel, .wc-bio .tn { will-change: transform, opacity; }
        .wc-bio .tn img { will-change: transform; }
        .wc-vid .vc-thumb, .wc-vid .player { will-change: transform; }
        .wc-cv .cv-group:nth-child(even) { will-change: transform; }
        .wc-gal .g-in { will-change: transform; }
        .wc-con .coda-cut { will-change: transform; }
        .wc-foot .foot-in { will-change: transform; }

        /* Reveal */
        .js .rv { opacity: 0; transform: translate3d(0, 14px, 0); }
        .rv.in { opacity: 1; transform: none; transition: opacity var(--t-reveal) var(--e-out) var(--d, 0ms), transform var(--t-reveal) var(--e-expo) var(--d, 0ms); }

        /* Photographs: blurred placeholder, then the decoded image */
        .ph { position: relative; overflow: hidden; background: var(--ph); }
        .ph::before { content: ""; position: absolute; inset: -6%; background: var(--lqip) center / cover no-repeat; filter: blur(16px); transform: scale(1.04); }
        .ph picture, .ph img { position: absolute; inset: 0; width: 100%; height: 100%; }
        .ph img { object-fit: cover; object-position: var(--pos, 50% 50%); }
        .js .ph img { opacity: 0; transition: opacity var(--t-comp) var(--e-out); }
        .js .ph img.is-loaded { opacity: 1; }

        /* Chapter index: 01, 02, 02.2… beside the title, like the navigation */
        .ch-head { display: flex; align-items: flex-start; gap: clamp(12px, 1.6vw, 28px); }
        .ch-idx { padding-top: 0.9em; color: var(--text-2); font-variant-numeric: tabular-nums; }
        .ch-idx::after { content: ""; display: block; width: 100%; min-width: 24px; height: var(--string); margin-top: 10px; background: var(--accent); transform: scaleX(0); transform-origin: left; transition: transform var(--t-comp) var(--e-out) 200ms; }
        .ch-head.in .ch-idx::after { transform: scaleX(1); }

        /* =========================================================
           COMPONENTS
           ========================================================= */
        .link { position: relative; display: inline-flex; align-items: center; gap: var(--s-3); min-height: var(--control); transition: color var(--t-micro) var(--e-out); }
        .link::before, .link::after { content: ""; position: absolute; left: 0; right: 0; bottom: calc(50% - 0.95em); height: var(--hair); }
        .link::before { background: var(--line-strong); }
        .link::after { background: var(--accent); transform: scaleX(0); transform-origin: right; transition: transform var(--t-comp) var(--e-out); }
        .link:hover::after, .link:focus-visible::after { transform: scaleX(1); transform-origin: left; }
        .link .arrow { display: inline-block; transition: transform var(--t-comp) var(--e-out); }
        .link:hover .arrow, .link:focus-visible .arrow { transform: translate3d(0, 3px, 0); }

        /* Round media control — play, previous, next */
        .round { display: grid; place-items: center; width: 56px; height: 56px; border-radius: 50%; border: var(--hair) solid color-mix(in srgb, var(--text) 60%, transparent); color: var(--text); transition: background-color var(--t-micro), border-color var(--t-micro), color var(--t-micro); }
        .round:hover, .round:focus-visible, a:hover > .round, a:focus-visible > .round { background: var(--accent); border-color: var(--accent); color: var(--ink); }

        /* Chip — the Arpeggione movements; the selected one is filled (Sumikon on the blue) */
        .chip { position: relative; min-width: var(--control); height: var(--control); display: grid; place-items: center; border: var(--hair) solid color-mix(in srgb, var(--text) 40%, transparent); font-family: var(--font-display); font-size: 1.125rem; transition: border-color var(--t-micro), color var(--t-micro), background-color var(--t-micro); }
        .chip:hover, .chip:focus-visible { border-color: var(--text); }
        .chip.is-active { border-color: var(--accent); box-shadow: inset 0 calc(-1 * var(--string)) 0 var(--accent); }

        /* Primary button */
        .btn { position: relative; display: inline-flex; align-items: center; gap: var(--s-5); min-height: 52px; padding: 0 var(--s-6); background: var(--text); color: var(--ink); transition: background-color var(--t-micro) var(--e-out), transform var(--t-micro) var(--e-out); }
        .btn:hover { background: var(--text-2); }
        .btn:active { transform: scale(0.98); }
        .btn[disabled] { opacity: 0.6; cursor: progress; }
        .btn .sb-arrow { transition: opacity var(--t-micro), transform var(--t-micro) var(--e-out); }
        .btn:hover .sb-arrow { transform: translate3d(4px, 0, 0); }
        .sb-spin, .sb-check { position: absolute; right: 30px; top: 50%; width: 18px; height: 18px; margin-top: -9px; opacity: 0; transition: opacity var(--t-micro); }
        .sb-spin { border: var(--hair) solid color-mix(in srgb, var(--ink) 30%, transparent); border-top-color: var(--ink); border-radius: 50%; }
        .btn.is-loading .sb-spin { opacity: 1; animation: spin 0.8s linear infinite; }
        .btn.is-loading .sb-arrow, .btn.is-success .sb-arrow { opacity: 0; }
        .sb-check path { stroke-dasharray: 24; stroke-dashoffset: 24; }
        .btn.is-success .sb-check { opacity: 1; }
        .btn.is-success .sb-check path { stroke-dashoffset: 0; transition: stroke-dashoffset var(--t-comp) var(--e-out) 80ms; }
        @keyframes spin { to { transform: rotate(360deg); } }

        /* Field — underline at rest, the blue string on focus, a heavier light rule + message on error */
        .fg { position: relative; }
        .fg input, .fg textarea {
            width: 100%; background: transparent; border: 0; border-radius: 0;
            box-shadow: inset 0 calc(-1 * var(--hair)) 0 var(--line-strong);
            padding: 26px 0 10px; font: inherit; font-size: 1rem; line-height: 1.5; color: var(--text);
            transition: box-shadow var(--t-micro) var(--e-out);
        }
        .fg input:focus, .fg textarea:focus { outline: none; box-shadow: inset 0 calc(-1 * var(--string)) 0 var(--text); }
        .fg textarea { resize: vertical; min-height: 132px; display: block; }
        .fg label { position: absolute; left: 0; top: 30px; pointer-events: none; transform-origin: left top; font-family: var(--font-label); font-size: var(--t-label); line-height: 1.4; letter-spacing: 0.14em; text-transform: uppercase; color: var(--text-2); transition: transform var(--t-micro) var(--e-out), color var(--t-micro); }
        .fg :is(input, textarea):focus + label, .fg :is(input, textarea):not(:placeholder-shown) + label { transform: translate3d(0, -24px, 0) scale(0.9); color: var(--text); }
        .fg.is-invalid :is(input, textarea) { box-shadow: inset 0 -3px 0 var(--text); }
        .fg.is-invalid label { color: var(--text); }
        .fg-error { margin-top: var(--s-2); font-size: var(--t-body-s); line-height: 1.5; color: var(--text); }
        .fg-error::before { content: "— "; }
        .form-status { font-size: var(--t-body-s); line-height: 1.5; color: var(--text); }

        /* Language switch */
        .lang-switch { display: flex; align-items: center; }
        .lang-switch a { position: relative; display: inline-flex; align-items: center; justify-content: center; min-width: var(--control); min-height: var(--control); color: var(--text-2); transition: color var(--t-micro); }
        .lang-switch a::after { content: ""; position: absolute; left: 14px; right: 14px; bottom: 11px; height: var(--hair); background: var(--accent); transform: scaleX(0); transition: transform var(--t-comp) var(--e-out); }
        .lang-switch a:hover, .lang-switch a[aria-current] { color: var(--text); }
        .lang-switch a[aria-current]::after { transform: scaleX(1); }
        /* FR / EN are two pages: the browser cross-fades between them (root opacity cross-fade only, no named groups) */
        @view-transition { navigation: auto; }

        /* =========================================================
           NAVIGATION — the name, then a flat numbered index (01 Écouter · 02 Biographie · 03 Galerie · 04 Contact);
           phones: Contact · + (the panel) · FR/EN
           ========================================================= */
        .top {
            position: fixed; inset: 0 0 auto; z-index: 1000; height: var(--nav-h);
            display: flex; align-items: center; justify-content: space-between; padding-inline: var(--gutter);
            color: var(--text);
            transition: transform var(--t-comp) var(--e-out), background-color var(--t-micro);
        }
        .top::after { content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: var(--hair); background: var(--line); opacity: 0; transition: opacity var(--t-micro); }
        .top.scrolled { background: color-mix(in srgb, var(--roiro) 86%, transparent); }
        .top.scrolled::after { opacity: 1; }
        .top.is-hidden { transform: translate3d(0, -100%, 0); }
        .js .top { opacity: 0; }
        .is-ready .top { opacity: 1; transition: transform var(--t-comp) var(--e-out), background-color var(--t-micro), opacity var(--i-ui) var(--e-out) var(--i-ui-d); }
        .panel-open .top, .panel-open .top.scrolled { background: transparent; }
        .panel-open .top::after { opacity: 0; }
        .top-name { position: relative; z-index: 2; display: inline-flex; align-items: center; min-height: var(--control); font-family: var(--font-display); font-size: 1.375rem; line-height: 1; letter-spacing: -0.01em; transition: opacity var(--t-comp) var(--e-out), visibility 0s; }
        /* On the portrait the giant name is the masthead; the small one returns with the first sheet */
        .top.on-hero .top-name { opacity: 0; visibility: hidden; transition: opacity var(--t-comp) var(--e-out), visibility 0s linear var(--t-comp); }
        .panel-open .top.on-hero .top-name { opacity: 1; visibility: visible; transition: opacity var(--t-comp) var(--e-out), visibility 0s; }
        .top-nav { position: relative; z-index: 2; display: flex; align-items: center; gap: clamp(8px, 1.6vw, 28px); }
        .nl-list { display: flex; align-items: center; gap: clamp(12px, 2.4vw, 44px); }
        .nl { position: relative; display: inline-flex; align-items: center; gap: 10px; min-height: var(--control); font-size: 0.8125rem; font-weight: 500; letter-spacing: 0.1em; text-transform: uppercase; transition: color var(--t-micro); }
        .nl-n { font-family: var(--font-label); font-size: 0.6875rem; font-weight: 400; letter-spacing: 0.06em; color: var(--text-2); font-variant-numeric: tabular-nums; }
        .nl::after { content: ""; position: absolute; left: 0; right: 0; bottom: 8px; height: var(--hair); background: var(--accent); transform: scaleX(0); transform-origin: right; transition: transform var(--t-comp) var(--e-out); }
        .nl:hover::after, .nl:focus-visible::after, .nl.is-active::after { transform: scaleX(1); transform-origin: left; }
        .nl-mob { display: none; }   /* the + is phone-only */
        .nl-more { gap: 12px; }
        .plus { position: relative; width: 14px; height: 14px; }
        .plus i { position: absolute; left: 0; top: 50%; width: 14px; height: var(--hair); margin-top: -0.5px; background: currentColor; transition: transform var(--t-comp) var(--e-out); }
        .plus i + i { transform: rotate(90deg); }
        .nl-more[aria-expanded="true"] .plus i { transform: rotate(45deg); }
        .nl-more[aria-expanded="true"] .plus i + i { transform: rotate(-45deg); }

        /* The phone panel — a lacquer-to-navy sheet that drops from the top: 01 Écouter · 02 Biographie · 03 Galerie · 04 Contact */
        .panel { position: fixed; inset: 0 0 auto; z-index: 1; background: linear-gradient(180deg, var(--rn)) 0 0 / 100% 233.34% no-repeat, var(--roiro); color: var(--text); padding: calc(var(--nav-h) + var(--s-7)) 0 var(--s-8); transform: translate3d(0, -101%, 0); visibility: hidden; transition: transform var(--t-exit) var(--e-exit), visibility 0s linear var(--t-exit); }
        .panel.open { transform: none; visibility: visible; transition: transform var(--t-comp) var(--e-out), visibility 0s; }
        .panel-in { display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); column-gap: var(--gap); align-items: end; }
        .pl-list { grid-column: 1 / 9; }
        .pl { display: flex; align-items: baseline; gap: clamp(16px, 2vw, 32px); min-height: var(--control); padding-block: 0.08em; font-family: var(--font-display); font-size: clamp(2.5rem, 6.2vw, 6.25rem); line-height: 1.02; letter-spacing: -0.02em; transition: color var(--t-micro); }
        .pl-n { width: 3.6em; flex: none; font-family: var(--font-label); font-size: 0.75rem; letter-spacing: 0.1em; color: var(--text-2); }
        .pl-t { position: relative; }
        .pl-t::after { content: ""; position: absolute; left: 0; right: 0; bottom: 0.06em; height: var(--string); background: var(--accent); transform: scaleX(0); transform-origin: right; transition: transform var(--t-comp) var(--e-out); }
        .pl:hover .pl-t::after, .pl:focus-visible .pl-t::after, .pl.is-active .pl-t::after { transform: scaleX(1); transform-origin: left; }
        .pl-list li { opacity: 0; transform: translate3d(0, 28px, 0); transition: opacity var(--t-micro) var(--e-out), transform var(--t-comp) var(--e-out); }
        .panel.open .pl-list li { opacity: 1; transform: none; transition: opacity var(--t-comp) var(--e-out), transform var(--t-reveal) var(--e-expo); transition-delay: calc(var(--i, 0) * var(--stagger) + 160ms); }
        .pl-side { grid-column: 9 / 13; display: grid; gap: var(--s-2); justify-items: start; padding-bottom: 1.2rem; }
        .scrim { position: fixed; inset: 0; z-index: 980; background: var(--roiro); opacity: 0; visibility: hidden; transition: opacity var(--t-exit) var(--e-out), visibility 0s linear var(--t-exit); }
        .scrim.open { opacity: 0.62; visibility: visible; transition: opacity var(--t-comp) var(--e-out), visibility 0s; }

        /* =========================================================
           TITLE CARD — the name for a moment, then the curtain lifts onto the portrait
           ========================================================= */
        .veil { display: none; }
        .js .veil { position: fixed; inset: 0; z-index: 5000; display: grid; place-items: center; background: linear-gradient(180deg, var(--rn)) 0 0 / 100% 233.34% no-repeat, var(--roiro); color: var(--text); transition: transform var(--i-lift) var(--e-scene); animation: veil-failsafe 0.01s linear 4.5s forwards; touch-action: none; }
        .js .veil.is-hidden { transform: translate3d(0, -100%, 0); pointer-events: none; }
        /* the scroll is held from the first paint of the card until the curtain is off (gutter kept, so nothing shifts at release) */
        html.intro:not(.intro-done) { overflow: hidden; scrollbar-gutter: stable; }
        .js.intro-skip .veil { display: none; }
        /* the name lifts with the curtain (a touch faster), and is gone as the curtain's edge passes */
        .veil-stage { display: flex; flex-direction: column; align-items: center; gap: var(--s-5); transition: transform var(--i-lift) var(--e-scene), opacity calc(var(--i-lift) * 0.5) var(--e-exit) calc(var(--i-lift) * 0.3); }
        .veil.is-hidden .veil-stage { transform: translate3d(0, -9vh, 0); opacity: 0; }
        .veil-string { width: var(--hair); height: 64px; background: var(--accent); transform-origin: top; transform: scaleY(0); animation: grow-y 520ms var(--e-out) 60ms forwards; }
        .veil-name { display: flex; gap: 0.26em; font-family: var(--font-display); font-size: clamp(2.4rem, 5.4vw, 4.25rem); line-height: 1; letter-spacing: -0.015em; }
        .veil-word { display: inline-flex; overflow: hidden; padding: 0.08em 0.02em 0.14em; margin: -0.08em -0.02em -0.14em; }
        /* the card's letters rise once Bodoni has arrived (≤ 0.7 s), so the font swap never happens while they move;
           the last letter is still by 16 + 9 × 16 + 400 = 560 ms, the HOLD in the script, before the opening is painted behind the card */
        .vc { display: inline-block; transform: translate3d(0, 118%, 0); }
        .fonts-in .vc { animation: rise 400ms var(--e-expo) forwards; animation-delay: calc(16ms + var(--i) * 16ms); }
        .veil-sub { padding-left: 0.42em; font-family: var(--font-label); font-size: var(--t-label); line-height: 1; letter-spacing: 0.42em; text-transform: uppercase; color: var(--text-2); opacity: 0; animation: fade-in 420ms var(--e-out) 200ms forwards; }
        /* later visits: the card is already composed and lifts at once */
        .intro-fast .vc, .intro-fast .veil-string, .intro-fast .veil-sub { animation: none; transform: none; opacity: 1; }
        @keyframes rise { to { transform: none; } }
        @keyframes grow-y { to { transform: scaleY(1); } }
        @keyframes fade-in { to { opacity: 1; } }
        /* hidden for the whole active phase: a 'to' keyframe alone leaves visibility visible at 99.99…% progress, and the transparent card would still take every click */
        @keyframes veil-failsafe { from, to { visibility: hidden; opacity: 0; } }

        /* =========================================================
           OPENING — three physical planes in one perspective:
           the photograph (back) · the name (middle) · Mallory's cut-out of Roman (front).
           The ground is R→N's lit half as a pool of light: #646464 behind Roman, Ro-iro at the edges; the print recedes into it.
           ========================================================= */
        .stage { position: relative; height: calc(100vh + var(--sep)); background: var(--roiro); }
        @supports (height: 1svh) { .stage { height: calc(100svh + var(--sep)); } }
        .hero {
            --fx: 48%; --fy: 34%; --shift: 0%;
            position: sticky; top: 0; height: 100vh; min-height: 520px; overflow: hidden; isolation: isolate;
            background: radial-gradient(ellipse 70% 76% at var(--po-x, 48%) var(--po-y, 60%), var(--rn-pool));
            perspective: 1100px; perspective-origin: var(--po-x, 48%) var(--po-y, 60%);
        }
        @supports (height: 1svh) { .hero { height: 100svh; } }
        .hp { position: absolute; inset: 0; }
        .hp-in { position: absolute; inset: 0; }
        .hp-photo { z-index: 0; }
        .hp-name { z-index: 1; pointer-events: none; }
        .hp-cut { z-index: 2; pointer-events: none; }
        .hp-pic { position: absolute; left: 0; right: 0; top: var(--shift); height: 100%; }
        .hp-pic picture, .hp-pic img { position: absolute; inset: 0; width: 100%; height: 100%; }
        .hp-pic img { object-fit: cover; object-position: var(--fx) var(--fy); }
        .hp-photo .hp-pic { background: var(--ph) var(--lqip-wide) var(--fx) var(--fy) / cover no-repeat; }
        /* The photograph is toned into the night (static, an R→N stop multiplied), so the lit cut-out stands in front of it */
        .hp-tone { position: absolute; inset: 0; background: var(--tone); mix-blend-mode: multiply; opacity: 0.55; }
        .hp-grade {
            position: absolute; inset: 0;
            background:
                linear-gradient(180deg, color-mix(in srgb, var(--roiro) 74%, transparent) 0%, transparent calc(var(--shift) + 24%)),
                radial-gradient(125% 95% at var(--fx) 42%, transparent 40%, color-mix(in srgb, var(--roiro) 60%, transparent) 100%);
        }
        .hp-dim { position: absolute; inset: 0; background: var(--roiro); opacity: 0; }

        .hero-name {
            position: absolute; left: 0; right: 0; top: var(--name-top, 15%);
            text-align: center; white-space: nowrap; color: var(--text);
            font-family: var(--font-display); font-weight: 400; font-size: var(--ns, 16.5vw); line-height: 0.84; letter-spacing: -0.03em;
        }
        .hn-row { display: inline-block; }
        .hn-line { display: inline-block; overflow: hidden; vertical-align: top; padding: 0.08em 0.04em 0.06em; margin: -0.08em -0.04em -0.06em; }
        .hn-line + .hn-line { margin-left: calc(0.24em - 0.04em); }
        .hn-w { display: inline-block; }
        .hn-strut { display: inline-block; width: 0; height: 0; }
        .hero-name .ch { display: inline-block; }
        .js .hero-name .ch { transform: translate3d(0, 110%, 0); }
        .is-ready .hero-name .ch { transform: none; transition: transform var(--i-name) var(--e-expo) calc(var(--i-name-d) + var(--i) * var(--i-name-s)); }

        /* Intro: the planes assemble back to front while the curtain lifts — the photograph (already decoded, never faded:
           no exposure change) settles from 1.06; the name rises; Roman's cut-out arrives last with a small rise.
           Photograph and cut-out share the same scale curve, so they stay registered. */
        .js .hp-photo .hp-in, .js .hp-cut .hp-in { scale: 1.06; }
        /* the photograph is painted once, behind the still card (during the hold), never while the card's name is moving */
        .js .hp-photo .hp-in { opacity: 0; }
        .intro-prime .hp-photo .hp-in, .is-ready .hp-photo .hp-in { opacity: 1; }
        .is-ready .hp-photo .hp-in, .is-ready .hp-cut .hp-in { scale: 1; transition: scale var(--i-settle) var(--e-expo); }
        .js .hp-cut .hp-in { opacity: 0; translate: 0 var(--i-rise-y); }
        .is-ready .hp-cut .hp-in { opacity: 1; translate: 0 0; transition: scale var(--i-settle) var(--e-expo), opacity var(--i-cut) var(--e-out) var(--i-cut-d), translate var(--i-rise) var(--e-expo) var(--i-cut-d); }

        /* Interface plane: the sentence on the photograph, the role, the cue */
        .hero-ui { position: absolute; inset: 0; z-index: 3; pointer-events: none; }
        .hero-ui::before { content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 46%; background: linear-gradient(0deg, color-mix(in srgb, var(--roiro) 84%, transparent) 0%, color-mix(in srgb, var(--roiro) 40%, transparent) 46%, transparent 100%); }
        .hero-quote { position: absolute; left: var(--gutter); bottom: clamp(32px, 7vh, 84px); max-width: min(23em, 34vw); font-family: var(--font-display); font-style: italic; font-size: var(--t-quote); line-height: 1.28; letter-spacing: -0.005em; color: var(--text); text-wrap: pretty; pointer-events: auto; }
        .hero-quote::before { content: ""; display: block; width: 56px; height: var(--string); margin-bottom: 20px; background: var(--accent); transform-origin: left; }
        .hero-quote blockquote, .hero-quote p { margin: 0; }
        /* Roman's signature, written on the print as the curtain lifts (src/signature.py: the pen's path reveals the traced ink).
           The one animation on the page that is not transform/opacity: stroke-dashoffset on a small SVG mask, played once. */
        .hero-sign { position: absolute; right: var(--gutter); bottom: calc(clamp(32px, 7vh, 84px) + 44px); width: clamp(200px, 18vw, 320px); color: var(--text); opacity: 0.94; }
        .hero-sign svg { display: block; width: 100%; height: auto; overflow: visible; }
        .sign .sg-p { stroke-dasharray: 1 1; stroke-dashoffset: 1; }
        .is-ready .sign .sg-p { stroke-dashoffset: 0; transition: stroke-dashoffset var(--st) cubic-bezier(0.37, 0, 0.63, 1) calc(var(--i-sign-d) + var(--sd)); }
        .intro-skip .sign .sg-p { transition: none; }
        .hero-quote-by { margin-top: 0.85em; font-family: var(--font-text); font-style: normal; font-size: var(--t-label); line-height: 1.4; letter-spacing: 0.14em; text-transform: uppercase; color: var(--text-2); }
        .hero-side { position: absolute; right: var(--gutter); bottom: clamp(32px, 7vh, 84px); display: flex; align-items: flex-end; align-items: last baseline; gap: var(--s-5); color: var(--text); }
        .hero-side .hero-cue { align-self: flex-end; }
        /* Écouter ↓ — beside the role, on its baseline; the string draws on hover / focus only */
        .hero-listen { position: relative; display: inline-flex; align-items: flex-end; min-height: var(--control); pointer-events: auto; font-size: 0.8125rem; font-weight: 500; letter-spacing: 0.1em; text-transform: uppercase; line-height: 1.4; color: var(--text); }
        .hero-listen::after { content: ""; position: absolute; left: 0; right: 0; bottom: -4px; height: var(--hair); background: var(--accent); transform: scaleX(0); transform-origin: right; transition: transform var(--t-comp) var(--e-out); }
        .hero-listen:hover::after, .hero-listen:focus-visible::after { transform: scaleX(1); transform-origin: left; }
        .hero-listen .arrow { display: inline-block; margin-left: 0.5em; transition: transform var(--t-comp) var(--e-out); }
        .hero-listen:hover .arrow, .hero-listen:focus-visible .arrow { transform: translate3d(0, 3px, 0); }
        .hero-cue { position: relative; width: var(--hair); height: 64px; overflow: hidden; background: color-mix(in srgb, var(--text) 24%, transparent); }
        .hero-cue::after { content: ""; position: absolute; inset: 0; background: var(--text); transform: translate3d(0, -100%, 0); animation: cue 2.4s var(--e-scene) infinite; }
        .hero-off .hero-cue::after { animation-play-state: paused; }   /* the only infinite animation rests once the opening has gone */
        @keyframes cue { 0% { transform: translate3d(0, -100%, 0); } 50% { transform: none; } 100% { transform: translate3d(0, 100%, 0); } }
        .js .hero-quote, .js .hero-side { opacity: 0; transform: translate3d(0, 12px, 0); }
        .is-ready .hero-quote, .is-ready .hero-side { opacity: 1; transform: none; transition: opacity var(--i-ui) var(--e-out) var(--i-ui-d), transform var(--t-reveal) var(--e-expo) var(--i-ui-d); }
        .js .hero-quote::before { transform: scaleX(0); }
        .is-ready .hero-quote::before { transform: scaleX(1); transition: transform var(--t-reveal) var(--e-expo) calc(var(--i-ui-d) + 120ms); }

        /* Portrait screens: the vertical photograph, lowered so the name stands above his head on two lines */
        @media (max-aspect-ratio: 999/1000) {
            .hero { --fx: 52%; --fy: 30%; --shift: 13%; }
            .hp-photo .hp-pic { background-image: var(--lqip); }
            .hn-line { display: block; }
            .hn-line + .hn-line { margin-left: -0.04em; }
            .hero-name { line-height: 0.86; }
            .hero-quote { right: var(--gutter); max-width: 24em; bottom: calc(var(--s-7) + 28px + env(safe-area-inset-bottom)); }
            .hero-side { left: var(--gutter); right: auto; bottom: calc(var(--s-5) + env(safe-area-inset-bottom)); }
            .hero-sign { width: min(46vw, 280px); bottom: calc(var(--s-7) + 28px + env(safe-area-inset-bottom) + var(--quote-h, 150px) + 14px); }
            .hero-cue { display: none; }
        }

        /* =========================================================
           02 — BIOGRAPHY (Ro-iro): it follows the Écouter sheet on its own colour;
           the photographs travel through depth while the text is read
           ========================================================= */
        .bio { position: relative; z-index: 2; padding-block: var(--section); background: var(--roiro); }
        /* the lead spans columns 1–8 and stays above the prints */
        .bio-lead { position: relative; z-index: 3; max-width: calc((100% - 11 * var(--gap)) / 12 * 8 + 7 * var(--gap)); margin-top: var(--title-gap); color: var(--text); }
        .bio-body { display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); column-gap: var(--gap); margin-top: var(--title-gap); }
        .bio-text { grid-column: 1 / 7; grid-row: 1; position: relative; z-index: 2; max-width: var(--measure); padding-bottom: 8vh; }
        .bio-text p { position: relative; color: var(--text-2); hyphens: manual; text-wrap: pretty; transition: color var(--t-comp) var(--e-out); }
        .bio-text p.rv.in { transition: opacity var(--t-reveal) var(--e-out), transform var(--t-reveal) var(--e-expo), color var(--t-comp) var(--e-out); }
        .bio-text [data-lang] > p ~ p { margin-top: 1.5em; }
        .bio-text p::before { content: ""; position: absolute; left: calc(-1 * var(--s-6)); top: 0.45em; bottom: 0.45em; width: var(--string); background: var(--accent); transform: scaleY(0); transform-origin: top; transition: transform var(--t-comp) var(--e-out); }
        .bio-text p.is-active { color: var(--text); }
        .bio-text p.is-active::before { transform: scaleY(1); }
        /* the photographs start at the first paragraph: only the top edge is cut (static), the sides and the bottom stay open.
           The track keeps the height of the stack above the front print (a waiting print rises 7% of a print per step, three steps,
           and the frame turns a few degrees: 26% of a print, a print being 5/4 of its width: 1.25 × 0.26 = 0.325 of the tunnel width),
           so once the figure is held the cut is above every print */
        .bio-track { grid-column: 7 / 13; grid-row: 1; box-sizing: content-box; padding-top: calc(min(100%, 57vh) * 0.325); clip-path: inset(0 -100vw -100vh -100vw); }
        @supports (height: 1svh) { .bio-track { padding-top: calc(min(100%, 57svh) * 0.325); } }
        .bio-fig { position: sticky; top: calc(14vh + 24px); perspective: 1600px; perspective-origin: 56% 46%; }
        @supports (height: 1svh) { .bio-fig { top: calc(14svh + 24px); } }
        .tunnel { position: relative; width: min(100%, 57vh); aspect-ratio: 4 / 5; margin-left: auto; transform-style: preserve-3d; }
        @supports (height: 1svh) { .tunnel { width: min(100%, 57svh); } }
        .tn { position: absolute; inset: 0; backface-visibility: hidden; }
        .bio-dl { margin-top: var(--s-7); }
        /* Phones: one print before each paragraph instead of the tunnel; it settles like the gallery prints */
        .bio-inl { display: none; }
        .js .bio-inl img { scale: 1.08; transition: opacity var(--t-comp) var(--e-out), scale var(--t-reveal-l, var(--t-reveal)) var(--e-expo); }
        .bio-inl.in img.is-loaded { scale: 1; }

        /* =========================================================
           01 — ÉCOUTER / LISTEN: the sheet that slides over the opening; a pool of light behind the player,
           the sixteen videos on a horizontal strip.
           The page never pins: the chapter passes in about one screen, the strip scrolls sideways natively
           (trackpad, touch, Shift + wheel, the arrows, ← / →); the thumbnails turn a little as they travel.
           ========================================================= */
        .videos { position: relative; z-index: 2; margin-top: calc(-1 * var(--overlap)); padding-block: var(--sheet-top) clamp(64px, 6vw, 104px); background: radial-gradient(62vw 64vh at 34% 50%, var(--rn-pool)), var(--roiro); }
        @supports (height: 1svh) { .videos { background: radial-gradient(62vw 64svh at 34% 50%, var(--rn-pool)), var(--roiro); } }
        /* the sheet arrives with a soft edge: the opening dissolves into it instead of being cut */
        .videos::before { content: ""; position: absolute; left: 0; right: 0; bottom: 100%; height: clamp(80px, 16vh, 180px); background: linear-gradient(180deg, color-mix(in srgb, var(--roiro) 0%, transparent) 0%, color-mix(in srgb, var(--roiro) 55%, transparent) 55%, var(--roiro) 100%); pointer-events: none; }
        .v-stage { display: grid; grid-template-columns: minmax(0, 8fr) minmax(0, 4fr); grid-template-rows: auto 1fr; grid-template-areas: 'player head' 'player side'; column-gap: clamp(24px, 3vw, 56px); }
        .v-head { grid-area: head; }
        .v-head .h2 { font-size: clamp(3rem, 6.4vw, 7rem); }
        .player-col { grid-area: player; perspective: 1400px; }
        .player { position: relative; aspect-ratio: 16 / 9; background: var(--ph); overflow: hidden; transform-origin: 50% 0; }
        .player-facade { position: absolute; inset: 0; display: block; }
        .player-facade img { width: 100%; height: 100%; object-fit: cover; transition: transform var(--t-zoom) var(--e-out); }
        .player-facade:hover img { transform: scale(1.025); }
        .player-peek { position: absolute; inset: 0; opacity: 0; transition: opacity var(--t-xfade) var(--e-out); }
        .player-peek.is-on { opacity: 1; }
        .play { position: absolute; left: 50%; top: 50%; width: 88px; height: 88px; margin: -44px 0 0 -44px; background: color-mix(in srgb, var(--roiro) 40%, transparent); }
        .play svg { margin-left: 4px; }
        .player iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }
        .v-side { grid-area: side; display: flex; flex-direction: column; justify-content: space-between; gap: var(--s-6); padding-left: clamp(0px, 1.5vw, 24px); }
        .player-caption { display: grid; align-content: start; gap: var(--s-2); min-height: 8.5em; margin-top: var(--s-6); padding-top: var(--s-5); border-top: var(--hair) solid var(--line-strong); }
        .pc-composer { font-family: var(--font-display); font-size: clamp(1.75rem, 2.5vw, 2.625rem); line-height: 1.05; letter-spacing: -0.015em; text-wrap: balance; }
        .pc-work { font-size: var(--t-body); line-height: 1.45; }
        .strip-ctrl { display: flex; align-items: center; gap: var(--s-4); }
        .strip-idx { min-width: 6.5em; text-align: center; font-variant-numeric: tabular-nums; }
        .strip-idx b { font-weight: 500; }
        .round[disabled] { opacity: 0.35; cursor: default; }
        .round[disabled]:hover { background: transparent; border-color: color-mix(in srgb, var(--text) 60%, transparent); color: var(--text); }
        .strip-wrap { position: relative; margin-top: clamp(36px, 4vw, 64px); -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 clamp(12px, 2vw, 32px), #000 calc(100% - clamp(32px, 5vw, 88px)), transparent 100%); mask-image: linear-gradient(90deg, transparent 0, #000 clamp(12px, 2vw, 32px), #000 calc(100% - clamp(32px, 5vw, 88px)), transparent 100%); }
        .js .strip-wrap.rv { transform: none; }
        .js .vc-thumb img { scale: 1.08; }
        .strip-wrap.in .vc-thumb img { scale: 1; transition: scale var(--t-reveal) var(--e-expo) calc(var(--j, 0) * var(--stagger)), transform var(--t-zoom) var(--e-out); }
        .strip {
            --edge: max(var(--gutter), calc((100% - var(--maxw)) / 2 + var(--gutter)));
            position: relative; display: flex; gap: clamp(16px, 2vw, 28px); overflow-x: auto; overflow-y: hidden;
            overscroll-behavior-x: contain; scroll-snap-type: x mandatory; scroll-padding-inline: var(--edge);
            padding: var(--s-2) var(--edge) var(--s-5); scrollbar-width: none; -webkit-overflow-scrolling: touch;
        }
        .strip::-webkit-scrollbar { display: none; }
        .strip::after { content: ""; flex: 0 0 1px; }
        .vcard { position: relative; flex: none; width: clamp(264px, 23vw, 340px); scroll-snap-align: start; transition: transform var(--t-micro) var(--e-out); }
        @media (hover: hover) and (pointer: fine) { .vcard:hover { transform: translate3d(0, -4px, 0); } }
        .vcard::before { content: ""; position: absolute; left: 0; top: -6px; width: var(--s-8); height: var(--string); background: var(--accent); transform: scaleX(0); transform-origin: left; transition: transform var(--t-comp) var(--e-out); }
        .vcard.is-active::before { transform: scaleX(1); }
        .vc-link { display: block; }
        .vc-thumb { position: relative; display: block; aspect-ratio: 16 / 9; overflow: hidden; background: var(--ph); }
        .vc-thumb img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; transition: transform var(--t-zoom) var(--e-out); }
        @media (hover: hover) and (pointer: fine) { .vc-link:hover .vc-thumb img { transform: scale(1.04); } }
        .vc-meta { display: flex; align-items: flex-start; gap: var(--s-3); padding-top: var(--s-4); }
        .t-text { display: grid; gap: var(--s-1); min-width: 0; }
        .t-play { flex: none; display: grid; place-items: center; width: 36px; height: 36px; margin-top: 2px; border-radius: 50%; border: var(--hair) solid color-mix(in srgb, var(--text) 60%, transparent); color: var(--text); transition: background-color var(--t-micro), border-color var(--t-micro), color var(--t-micro); }
        .t-play svg { margin-left: 2px; }
        .vc-link:hover .t-play, .vc-link:focus-visible .t-play, .vcard.is-active .t-play { background: var(--accent); border-color: var(--accent); color: var(--ink); }
        .t-composer { font-family: var(--font-display); font-size: var(--t-title); line-height: 1.12; }
        .t-work { font-size: var(--t-body-s); line-height: 1.45; }
        .mvts { display: flex; gap: var(--s-2); margin: var(--s-3) 0 0 calc(36px + var(--s-3)); }

        /* =========================================================
           02.2 — FORMATION / EDUCATION (R→N, Ro-iro → #4C4C4C): an editorial spread, four headings on hairlines;
           the right column floats on a nearer plane
           ========================================================= */
        .cv { position: relative; z-index: 2; padding-block: var(--section) clamp(72px, 6vw, 112px); background: linear-gradient(180deg, var(--rn)) 0 0 / 100% 233.34% no-repeat, var(--roiro); }
        .cv-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: clamp(48px, 7vw, 128px); row-gap: clamp(56px, 6vw, 96px); margin-top: var(--title-gap); align-items: start; }
        .cv-group { position: relative; padding-top: var(--s-5); border-top: var(--hair) solid var(--line-strong); }
        .cv-group:nth-child(2) { margin-top: clamp(48px, 9vw, 150px); }
                .cv-group::before { content: ""; position: absolute; left: 0; top: -1px; width: var(--s-8); height: var(--string); background: var(--accent); transform: scaleX(0); transform-origin: left; }
        .cv-group.in::before { transform: scaleX(1); transition: transform var(--t-reveal) var(--e-expo) 120ms; }
        .cv-group h3 { font-family: var(--font-display); font-weight: 400; font-size: var(--t-display-3); line-height: 1.02; letter-spacing: -0.02em; margin-bottom: var(--s-5); text-wrap: balance; }
        .cv-group li { display: grid; grid-template-columns: 1.25rem minmax(0, 1fr); column-gap: var(--s-4); padding-block: var(--s-3); border-top: var(--hair) solid var(--line); }
        .cv-group li:first-child { border-top: 0; padding-top: 0; }
        /* a dot instead of the year (Mallory, 2026-11-02): square like every mark on the page, centred on the first line */
        .cv-when { width: 5px; height: 5px; margin-top: calc(0.85rem - 2.5px); background: var(--text-2); }
        .cv-what { font-size: 1.0625rem; line-height: 1.6; text-wrap: pretty; }
        .js .cv-group li { opacity: 0; transform: translate3d(0, 10px, 0); }
        .cv-group.in li { opacity: 1; transform: none; transition: opacity var(--t-comp) var(--e-out), transform var(--t-reveal) var(--e-expo); transition-delay: calc(120ms + var(--j, 0) * var(--stagger)); }
        .cv-dl { margin-top: var(--s-8); }

        /* =========================================================
           03 — GALLERY (R→N, #4C4C4C → Ro-iro): an exhibition hang.
           One ratio system (3:2 and 2:3), each photograph cropped on its subject; rows of equal height with
           three scales (large · portrait · small). The frames stay on the grid; the prints drift inside them.
           ========================================================= */
        .gal { position: relative; z-index: 2; padding-block: clamp(56px, 5vw, 96px) var(--section); background: linear-gradient(180deg, var(--rnr)) 0 100% / 100% 233.34% no-repeat, var(--roiro); }
        .gal > .wrap { position: relative; }
        .gal-note { max-width: 24em; margin: clamp(56px, 6vw, 104px) auto 0; text-align: center; font-family: var(--font-display); font-style: italic; font-size: var(--t-quote); line-height: 1.32; letter-spacing: -0.005em; color: var(--text); text-wrap: balance; }
        .gal-note::before { content: ""; display: block; width: 56px; height: var(--string); margin: 0 auto 20px; background: var(--accent); }
        .gal-head { margin-bottom: var(--title-gap); }
        .hang { display: grid; row-gap: clamp(20px, 2.4vw, 36px); }
        .hang-row { display: flex; gap: clamp(12px, 1.8vw, 28px); align-items: flex-start; }
        .hang-row--s { width: 84%; margin-inline: auto; }
        .gallery-item { position: relative; display: block; flex: var(--fr) 1 0; min-width: 0; aspect-ratio: var(--fr); cursor: zoom-in; }
        .gallery-item .frame-clip { position: absolute; inset: 0; display: block; }
        .g-in { position: absolute; left: 0; right: 0; top: -7%; bottom: -7%; }
        .js .gallery-item.rv { opacity: 1; transform: none; }
        .js .gallery-item img { scale: 1.08; }
        .gallery-item.in img.is-loaded { scale: 1; }
        .js .gallery-item .frame-clip img { transition: opacity var(--t-reveal) var(--e-out), scale var(--t-reveal-l) var(--e-expo) var(--d, 0ms), transform var(--t-zoom) var(--e-out); }
        .gallery-item.is-open .frame-clip { visibility: hidden; }
        .gallery-item:focus-visible { outline-offset: 6px; }
        @media (hover: hover) and (pointer: fine) { .gallery-item:hover img.is-loaded { transform: scale(1.04); } }

        /* Lightbox — the full, uncropped photograph grows out of the clicked print */
        .lightbox { position: fixed; inset: 0; z-index: 2000; visibility: hidden; pointer-events: none; color: var(--text); }
        .lightbox.open { visibility: visible; pointer-events: auto; }
        .lightbox.is-closing { visibility: visible; }
        .lb-backdrop { position: absolute; inset: 0; background: color-mix(in srgb, var(--roiro) 96%, transparent); opacity: 0; transition: opacity var(--t-comp) var(--e-out); }
        .lightbox.open .lb-backdrop { opacity: 1; }
        .lb-frame { position: fixed; left: 0; top: 0; width: 0; height: 0; overflow: hidden; background: var(--ph); opacity: 0; transform-origin: 50% 50%; will-change: transform, opacity; }
        .lb-frame img { width: 100%; height: 100%; object-fit: cover; }
        .lb-ui { opacity: 0; transition: opacity var(--t-micro) var(--e-out); }
        .lightbox.open .lb-ui { opacity: 1; transition-delay: 200ms; }
        .lb-top { position: absolute; top: 0; left: 0; right: 0; display: flex; justify-content: flex-end; padding: var(--s-3) var(--gutter); }
        .icon-btn { width: var(--control); height: var(--control); display: grid; place-items: center; color: var(--text); transition: color var(--t-micro), transform var(--t-comp) var(--e-out); }
        .lightbox-close { margin-right: -10px; }
        .lightbox-close:hover { transform: rotate(90deg); }
        .lightbox-nav { position: absolute; top: 50%; margin-top: -28px; }
        .lightbox-prev { left: var(--gutter); }
        .lightbox-next { right: var(--gutter); }

        /* =========================================================
           04 — CONTACT (R→N, Ro-iro → #4C4C4C) and a coda: the opening in miniature
           ========================================================= */
        .contact { position: relative; z-index: 2; padding-block: var(--section); background: linear-gradient(180deg, var(--rn)) 0 0 / 100% 233.34% no-repeat, var(--roiro); }
        .contact-grid { display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); column-gap: var(--gap); }
        .contact-head { grid-column: 1 / 7; align-self: start; }
        .contact-side { grid-column: 8 / 13; align-self: start; padding-top: var(--s-3); }
        .contact-mail { margin-bottom: var(--s-7); }
        .contact-mail .link { font-family: var(--font-display); font-size: var(--t-title); }
        .form { display: grid; gap: var(--s-6); }
        .form-row { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s-6); }
        .form-honeypot { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }
        .form-foot { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-4) var(--s-6); }
        .contact-social { display: flex; flex-wrap: wrap; gap: 0 var(--s-6); margin-top: var(--s-8); padding-top: var(--s-5); border-top: var(--hair) solid var(--line); }
        /* The coda: a narrow print of the portrait; Roman (Mallory's cut-out) is wider than its frame and steps out of it.
           Sizes in cqw (the coda's width), so the photograph and the cut-out stay registered at any size. */
        .coda { position: relative; container-type: inline-size; width: min(78%, 400px); aspect-ratio: 4 / 5; margin: clamp(56px, 7vw, 112px) 0 0 clamp(0px, 5vw, 80px); }
        .coda-frame { position: absolute; left: 20%; right: 20%; top: 6%; bottom: 18%; overflow: hidden; background: var(--ph); }
        .coda-photo { position: absolute; left: -113.4cqw; top: -28.4cqw; width: 292.7cqw; aspect-ratio: 5504 / 3072; }
        .coda-photo picture, .coda-photo img { position: absolute; inset: 0; width: 100%; height: 100%; }
        .coda-frame::after { content: ""; position: absolute; inset: 0; background: var(--roiro); opacity: 0.58; }
        .coda-cut { position: absolute; left: -93.4cqw; top: -20.9cqw; width: 292.7cqw; aspect-ratio: 5504 / 3072; pointer-events: none;
            -webkit-mask-image: linear-gradient(180deg, #000 74%, transparent 90%); mask-image: linear-gradient(180deg, #000 74%, transparent 90%); }
        .coda-cut img { width: 100%; height: 100%; }

        /* =========================================================
           FOOTER (R→N, #4C4C4C → #646464: the page ends in its light) — lies beneath the page; the last sheet lifts off it
           ========================================================= */
        main { position: relative; z-index: 1; background: var(--roiro); }
        .foot { position: sticky; bottom: 0; z-index: 0; background: linear-gradient(180deg, var(--rn-hand), #646464); color: var(--text-2); overflow: hidden; padding-bottom: env(safe-area-inset-bottom); }
        /* Long anchor jumps cut instead of flying: the page dissolves to Ro-iro, jumps, and dissolves back (opacity only).
           The footer fades with main, so the sticky footer under it never shows mid-page; the nav stays. */
        #main, .foot { transition: opacity var(--t-cut-in) var(--e-out); }
        html.is-cut #main, html.is-cut .foot { opacity: 0; pointer-events: none; transition: opacity var(--t-cut-out) var(--e-exit); }
        .foot-in { }
        .foot-name { display: block; padding: clamp(48px, 8vw, 128px) var(--gutter) 0; font-family: var(--font-display); font-weight: 400; font-size: var(--fns, 16vw); line-height: 0.9; letter-spacing: -0.03em; white-space: nowrap; color: var(--text-2); text-align: center; }
        .foot-row { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--s-2) var(--s-6); padding-block: var(--s-6) var(--s-5); }
        .foot-links { display: flex; flex-wrap: wrap; align-items: center; gap: 0 var(--s-5); }
        .foot-links .link { color: var(--text); }

        /* =========================================================
           RESPONSIVE — 1100 (tablet), 1023 (stacked biography), 860 (narrow), 760 (phone), 699 (hang → pairs)
           ========================================================= */
        @media (max-width: 1100px) {
            /* stacked: the pool of light sits behind the player, the caption and the strip stay on the dark part */
            .videos { background: radial-gradient(72vw 30vh at 50% calc(var(--sheet-top) + 110px + 28vw), var(--rn-pool)), var(--roiro); }
            .v-stage { grid-template-columns: minmax(0, 1fr); grid-template-areas: 'head' 'player' 'side'; grid-template-rows: auto; }
            .v-head { margin-bottom: var(--title-gap); }
            .v-head .h2 { font-size: var(--t-display-2); }
            .v-side { padding-left: 0; margin-top: var(--s-5); flex-direction: row; align-items: flex-end; flex-wrap: wrap; }
            .player-caption { flex: 1 1 22rem; min-height: 0; }
        }
        /* Tablet and phone bars: the labels alone (the indices stay in the chapter heads and the panel) */
        @media (max-width: 1023px) {
            .nl-list .nl-n { display: none; }
        }
        @media (max-width: 1023px) {
            .bio-lead { max-width: 22em; }
            .bio-body { margin-top: var(--s-7); }
            .bio-text { grid-column: 1 / -1; grid-row: 2; padding-bottom: 0; }
            .bio-text p::before { left: calc(-1 * var(--s-3)); }
            .bio-track { grid-column: 1 / -1; grid-row: 1; height: 135vh; padding-top: calc(min(78%, 54vh) * 0.325); margin-bottom: var(--s-7); }
            @supports (height: 1svh) { .bio-track { height: 135svh; padding-top: calc(min(78%, 54svh) * 0.325); } }
            .bio-fig { top: calc(var(--nav-h) + var(--s-5)); }
            .tunnel { width: min(78%, 54vh); margin-inline: auto 4%; }
            @supports (height: 1svh) { .tunnel { width: min(78%, 54svh); } }
        }
        /* Phones: no sticky tunnel; the four prints sit in the reading, one before each paragraph */
        @media (max-width: 760px) {
            .bio-track { display: none; }
            .bio-text { grid-row: 1; }
            .bio-inl { display: block; width: min(78%, 54vh); aspect-ratio: 4 / 5; margin: var(--s-7) 4% var(--s-5) auto; }
            @supports (height: 1svh) { .bio-inl { width: min(78%, 54svh); } }
            .bio-text [data-lang] > .bio-inl:first-child { margin-top: 0; }
        }
        @media (max-width: 860px) {
            .cv-grid { grid-template-columns: 1fr; }
            .cv-group:nth-child(2) { margin-top: 0; }
            .contact-head, .contact-side { grid-column: 1 / -1; }
            .contact-side { margin-top: var(--title-gap); padding-top: 0; }
            .coda { display: none; }
            .pl-list { grid-column: 1 / -1; }
            .pl-side { grid-column: 1 / -1; margin-top: var(--s-7); padding-bottom: 0; }
        }
        @media (max-width: 760px) {
            :root { --nav-h: 64px; --section: 80px; --t-body: 1.0625rem; --i-rise-y: 8px; }
            .top-name { font-size: 1.25rem; }
            .nl-desk { display: none; }
            .nl-mob { display: block; }
            .nl-more { justify-content: center; width: var(--control); margin-right: -12px; }
            .plus, .plus i { width: 18px; }
            .top-nav { gap: 0; }
            .panel { height: 100vh; padding: calc(var(--nav-h) + var(--s-5)) 0 calc(var(--s-6) + env(safe-area-inset-bottom)); overflow-y: auto; }
            @supports (height: 1svh) { .panel { height: 100svh; } }
            .panel-in { min-height: 100%; align-content: space-between; }
            .pl { font-size: clamp(2.25rem, 10.5vw, 3rem); }
            .pl-n { width: 3.4em; }
            .hero-quote { max-width: none; }
            .hero-sign { display: none; }   /* no signature on phones (Mallory, 2026-11-02) */
            .vcard { width: min(78vw, 320px); }
            .strip-ctrl { width: 100%; justify-content: space-between; }
            .play { width: 68px; height: 68px; margin: -34px 0 0 -34px; }
            .cv-group li { grid-template-columns: 1rem minmax(0, 1fr); column-gap: var(--s-3); }
            .form-row { grid-template-columns: 1fr; }
            .lightbox-nav { display: none; }
            .contact-social, .foot-links { gap: 0 var(--s-4); }
            .foot-row { justify-content: flex-start; }
        }
        /* Phones: the hang becomes pairs — large pieces span both columns, portraits and small prints go two by two */
        @media (max-width: 699px) {
            .hang { grid-template-columns: 1fr 1fr; gap: 12px; }
            .hang-row, .hang-row--s { display: contents; }
            .gallery-item { flex: none; width: 100%; }
            .gallery-item.g-wide-m { grid-column: span 2; }
        }

        /* Reduced motion — off by decision (Mallory, 2026-10-21: the full motion plays for everyone, Reduce Motion included).
           Kept behind html.rm, which the script adds only when HONOUR_REDUCED_MOTION is true: nothing travels, things dissolve —
           scroll effects stop, reveals fade in place, the biography prints cross-dissolve (writeCalm); the opening still plays */
        html.rm { scroll-behavior: auto; }
        .rm .rv, .rm .cv-group li { transform: none; }
        .rm .rv.in { transition: opacity var(--t-comp) var(--e-out) var(--d, 0ms); }
        .rm .bio-text p.rv.in { transition: opacity var(--t-comp) var(--e-out), color var(--t-comp) var(--e-out); }
        .rm .cv-group.in li { transition: opacity var(--t-comp) var(--e-out); transition-delay: calc(120ms + var(--j, 0) * var(--stagger)); }
        .rm .hero-cue::after { animation: none; transform: none; }
        .rm .cv-group::before, .rm .ch-idx::after { transition: none; }
        .rm .h2 .ln-in { transform: none; transition: none; }
    