  /* =========================================
     STUDIO N°19 — base
     ========================================= */

  /* Butler Stencil — self-hosted */
  @font-face {
    font-family: 'Butler Stencil';
    src: url('../fonts/Butler-Free-Lgt-St.woff2') format('woff2');
    font-weight: 300; font-style: normal; font-display: swap;
  }
  @font-face {
    font-family: 'Butler Stencil';
    src: url('../fonts/Butler-Free-Rmn-St.woff2') format('woff2');
    font-weight: 400; font-style: normal; font-display: swap;
  }
  @font-face {
    font-family: 'Butler Stencil';
    src: url('../fonts/Butler-Free-Med-St.woff2') format('woff2');
    font-weight: 500; font-style: normal; font-display: swap;
  }
  @font-face {
    font-family: 'Butler Stencil';
    src: url('../fonts/Butler-Free-SBd-St.woff2') format('woff2');
    font-weight: 600; font-style: normal; font-display: swap;
  }
  @font-face {
    font-family: 'Butler Stencil';
    src: url('../fonts/Butler-Free-Bd-St.woff2') format('woff2');
    font-weight: 700; font-style: normal; font-display: swap;
  }
  @font-face {
    font-family: 'Butler Stencil';
    src: url('../fonts/Butler-Free-XBd-St.woff2') format('woff2');
    font-weight: 800; font-style: normal; font-display: swap;
  }
  @font-face {
    font-family: 'Butler Stencil';
    src: url('../fonts/Butler-Free-Blk-St.woff2') format('woff2');
    font-weight: 900; font-style: normal; font-display: swap;
  }

  /* ---- Google families, self-hosted (latin subset only) ---------------------
     Downloaded from Google Fonts and served from this origin. Loading them from
     fonts.googleapis.com meant two extra DNS+TLS handshakes and a render-blocking
     stylesheet on a third party before a single glyph could paint. ---- */
  @font-face {
    font-family: 'JetBrains Mono';
    src: url('../fonts/jetbrainsmono-100.woff2') format('woff2');
    font-weight: 100; font-style: normal; font-display: swap;
  }
  @font-face {
    font-family: 'JetBrains Mono';
    src: url('../fonts/jetbrainsmono-200.woff2') format('woff2');
    font-weight: 200; font-style: normal; font-display: swap;
  }
  @font-face {
    font-family: 'JetBrains Mono';
    src: url('../fonts/jetbrainsmono-300.woff2') format('woff2');
    font-weight: 300; font-style: normal; font-display: swap;
  }
  @font-face {
    font-family: 'JetBrains Mono';
    src: url('../fonts/jetbrainsmono-400.woff2') format('woff2');
    font-weight: 400; font-style: normal; font-display: swap;
  }
  @font-face {
    font-family: 'JetBrains Mono';
    src: url('../fonts/jetbrainsmono-500.woff2') format('woff2');
    font-weight: 500; font-style: normal; font-display: swap;
  }
  @font-face {
    font-family: 'JetBrains Mono';
    src: url('../fonts/jetbrainsmono-600.woff2') format('woff2');
    font-weight: 600; font-style: normal; font-display: swap;
  }
  @font-face {
    font-family: 'Manrope';
    src: url('../fonts/manrope-300.woff2') format('woff2');
    font-weight: 300; font-style: normal; font-display: swap;
  }
  @font-face {
    font-family: 'Manrope';
    src: url('../fonts/manrope-400.woff2') format('woff2');
    font-weight: 400; font-style: normal; font-display: swap;
  }
  @font-face {
    font-family: 'Manrope';
    src: url('../fonts/manrope-500.woff2') format('woff2');
    font-weight: 500; font-style: normal; font-display: swap;
  }
  @font-face {
    font-family: 'Manrope';
    src: url('../fonts/manrope-600.woff2') format('woff2');
    font-weight: 600; font-style: normal; font-display: swap;
  }
  @font-face {
    font-family: 'Newsreader';
    src: url('../fonts/newsreader-400-italic.woff2') format('woff2');
    font-weight: 400; font-style: italic; font-display: swap;
  }
  @font-face {
    font-family: 'Newsreader';
    src: url('../fonts/newsreader-500-italic.woff2') format('woff2');
    font-weight: 500; font-style: italic; font-display: swap;
  }
  @font-face {
    font-family: 'Newsreader';
    src: url('../fonts/newsreader-300.woff2') format('woff2');
    font-weight: 300; font-style: normal; font-display: swap;
  }
  @font-face {
    font-family: 'Newsreader';
    src: url('../fonts/newsreader-400.woff2') format('woff2');
    font-weight: 400; font-style: normal; font-display: swap;
  }
  @font-face {
    font-family: 'Newsreader';
    src: url('../fonts/newsreader-500.woff2') format('woff2');
    font-weight: 500; font-style: normal; font-display: swap;
  }
  @font-face {
    font-family: 'Newsreader';
    src: url('../fonts/newsreader-600.woff2') format('woff2');
    font-weight: 600; font-style: normal; font-display: swap;
  }
  @font-face {
    font-family: 'Newsreader';
    src: url('../fonts/newsreader-700.woff2') format('woff2');
    font-weight: 700; font-style: normal; font-display: swap;
  }

  :root {
    --ink:    #1C2B47;
    --char:   #5b6068;
    --mist:   #9aa0a8;
    --line:   #ececec;
    --bg:     #F9F3E6; /* vintage patina cream (warmer than the old off-white) */
    --linen:  #EFEAE0;
    --accent: #C0472E; /* reddish terracotta clay (was gold-orange #E8A33D) */

    --f-serif:   'Newsreader', Georgia, 'Times New Roman', serif;
    --f-stencil: 'Butler Stencil', 'Newsreader', Georgia, serif;
    --f-mono:    'JetBrains Mono', 'SF Mono', Menlo, monospace;
    --f-sans:    'Manrope', system-ui, -apple-system, 'Segoe UI', sans-serif;

    --t-micro: 0.6875rem;
    --t-small: 0.8125rem;
    --t-body:  0.9375rem;

    --s-8:   0.5rem;
    --s-12:  0.75rem;
    --s-16:  1rem;
    --s-24:  1.5rem;
    --s-32:  2rem;
    --s-48:  3rem;
    --s-64:  4rem;
    --s-80:  5rem;
    --s-120: 7.5rem;

    --e-out-3: cubic-bezier(.165,.84,.44,1);
    --e-out-2: cubic-bezier(.25,.46,.45,.94);

    --gutter: clamp(1.25rem, 3vw, 2.5rem);
  }

  * { box-sizing: border-box; margin: 0; padding: 0; }
  /* scroll handled by Lenis; native scroll is the reduced-motion fallback */
  body {
    font-family: var(--f-mono);
    font-size: var(--t-body);
    font-weight: 400;
    line-height: 1.55;
    color: var(--ink);
    background: var(--bg);
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
    max-width: 100%;
    overflow-x: hidden;
    overflow-x: clip;
  }
  img, svg { display: block; max-width: 100%; }

  /* Custom scrollbar */
  html {
    /* iOS Safari "text autosizing" inflates the rendered size of text inside any
       block it judges wide relative to the viewport - a heuristic meant to rescue
       desktop layouts on a phone. .work-services is deliberately full-bleed
       (negative side margins) with white-space:nowrap children, which is exactly
       what trips it, so the services line renders far larger than the size the
       JS fitter computed and the words run off the screen. Desktop Chrome never
       does this, so it is invisible in any non-iOS test. 100% = render text at
       the size we asked for. */
    -webkit-text-size-adjust: 100%;
    text-size-adjust: 100%;
    scrollbar-width: thin;                       /* Firefox */
    scrollbar-color: var(--ink) transparent;
    /* Nothing was clipping horizontally at document level. The marquee tracks
       measure ~4900px wide by design, and although their parents clip them,
       iOS Safari will still let you pan the document sideways to reach them
       unless html itself refuses. `clip` not `hidden`: hidden makes a scroll
       container, and iOS pans scroll containers; clip makes none. */
    overflow-x: hidden;
    overflow-x: clip;
  }
  ::-webkit-scrollbar { width: 11px; height: 11px; }
  ::-webkit-scrollbar-track { background: transparent; }
  ::-webkit-scrollbar-thumb {
    background: var(--ink);
    border: 3px solid var(--bg);                 /* off-white inset → rounded, slim thumb */
    border-radius: 999px;
  }
  ::-webkit-scrollbar-thumb:hover { background: #13213a; }  /* slightly deeper navy */
  a { color: inherit; text-decoration: none; }

  /* Holds the <defs> sprite at the top of <body> (the Nº19 mark, referenced by <use>).
     Must not be display:none — that would stop the referenced geometry from rendering
     in some engines. Take it out of flow at zero size instead. */
  .svg-defs { position: absolute; width: 0; height: 0; overflow: hidden; }

  /* =============================================
     NAV
     ============================================= */
  .nav {
    position: fixed;
    top: 0; left: 0; right: 0;
    z-index: 50;
    padding: var(--s-24) var(--gutter);
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    gap: var(--s-32);
    transition: padding .35s var(--e-out-3),
                background .35s var(--e-out-3),
                border-color .35s var(--e-out-3);
    border-bottom: 1px solid transparent;
  }
  /* ---- TOUCH TARGETS ----------------------------------------------------
     Apple's HIG floor is 44x44pt. Measured on a real iPhone viewport these were
     67x30 (brand), 32x29 (menu), 64x28 (drawer links) and ~55x21 (footer links) —
     every one of them under it, and the menu button is the only way into the nav.

     Each fix below adds hit area WITHOUT moving anything: padding is cancelled by
     an equal negative margin on the two nav controls, and the two dense link lists
     get an invisible ::before that expands the tappable box only. Nothing shifts,
     nothing resizes, the targets just stop being 21px tall.
     (::after is already the underline on both link lists, hence ::before.) -------- */
  .nav__brand {
    display: inline-flex;
    align-items: center;
    justify-self: start;
    padding: 7px 6px;
    margin: -7px -6px;   /* cancels the padding, so the logo does not move */
  }
  .nav__brand img {
    height: 36px;
    width: auto;
    transition: opacity .25s var(--e-out-2);
  }
  /* pointer-gated: on touch this sticks and the logo stays dimmed after a tap */
  @media (hover: hover) and (pointer: fine) {
    .nav__brand:hover img { opacity: 0.65; }
  }

  .nav__location {
    justify-self: center;
  }
  .nav__location-display {
    font-family: var(--f-mono);
    font-size: var(--t-micro);
    font-weight: 400;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: var(--ink);
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
    transition: opacity .35s var(--e-out-2);
  }
  .nav__location-display .sep {
    color: var(--ink);
    margin: 0 0.65em;
  }

  .nav__menu-toggle {
    justify-self: end;
    display: inline-flex;
    align-items: center;
    gap: var(--s-12);
    background: none;
    border: 0;
    padding: 8px;
    margin: -8px;        /* cancels the padding, so the icon does not move */
    cursor: pointer;
    color: var(--ink);
  }
  .nav__menu-word {
    font-family: var(--f-sans);
    font-size: var(--t-micro);
    letter-spacing: 0.16em;
    text-transform: uppercase;
    line-height: 1;
    transition: opacity .25s var(--e-out-2);
  }
  .nav__menu-toggle.is-open .nav__menu-word { opacity: 0; }
  .nav__menu-icon {
    position: relative;
    display: block;
    width: 40px;
    height: 29px;
  }
  .nav__menu-icon span {
    position: absolute;
    left: 0;
    width: 100%;
    height: 1px;
    background: currentColor;
    transition: background .25s var(--e-out-2), transform .35s var(--e-out-3);
  }
  .nav__menu-icon span:nth-child(1) { top: 8px; }
  .nav__menu-icon span:nth-child(2) { top: 20px; }
  .nav__menu-toggle:hover .nav__menu-icon span { background: var(--char); }

  @media (max-width: 880px) {
    .nav {
      grid-template-columns: auto 1fr auto;
      padding: var(--s-16) var(--gutter);
      gap: var(--s-16);
    }
    .nav__brand img { height: 30px; }
    .nav__menu-word { display: none; }
    .nav__menu-icon { width: 32px; }
    /* stack: location on line 1, conditions on line 2 */
    .nav__location-display {
      display: flex;
      flex-direction: column;
      align-items: center;
      line-height: 1.3;
      gap: 1px;
      font-size: 0.6875rem;
      letter-spacing: 0.12em;
    }
    .nav__location-display .sep { margin: 0 0.4em; }
    .nav__loc-sep { display: none; }   /* no divider between the two stacked lines */
    .nav__location-display .time,
    .nav__location-display .time + .sep { display: none; }   /* time stays hidden on mobile */
  }

  /* =============================================
     HERO — logo, golfer, caption, scroll cue
     ============================================= */
  .hero {
    position: sticky;
    top: 0;
    height: calc(var(--vh, 1vh) * 100);   /* locked — iOS-stable */
    min-height: calc(var(--vh, 1vh) * 100);
    padding: var(--gutter);
    display: grid;
    place-items: center;          /* the logo+dog group sits at true viewport centre */
    background: var(--bg);
    isolation: isolate;
  }
  .hero__main { position: relative; z-index: 1; }
  .hero__scroll-row {
    position: absolute;           /* floats at the bottom, out of the centred group */
    left: 0;
    right: 0;
    bottom: clamp(5.5rem, 9vh, 7rem);   /* cleared up to sit above the Newsreader marquee */
    z-index: 1;
  }

  .hero__main {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    gap: var(--s-48);
    transform: scale(0.84);
    transform-origin: center;
  }
  .hero__logomark {
    position: relative;
    width: clamp(138px, 15vw, 196px);
    color: var(--ink);
    display: block;
  }
  .hero__logo {
    width: 100%;
    height: auto;
    display: block;
    color: inherit;
    overflow: visible;
  }
  .hero__logo path {
    fill: currentColor;
    stroke: currentColor;
    stroke-width: 1;
    vector-effect: non-scaling-stroke;
    stroke-linecap: round;
    stroke-linejoin: round;
    fill-opacity: 0;
    stroke-opacity: 1;
    stroke-dasharray: var(--len, 1200);
    stroke-dashoffset: var(--len, 1200);
    transition:
      stroke-dashoffset 0.95s cubic-bezier(.55,.05,.45,1),
      fill-opacity 0.55s var(--e-out-2),
      stroke-opacity 0.55s var(--e-out-2);
  }

  /* SECTION ASSET DRAW-ON — the hero logo's self-drawing effect, applied to the Nº19 mark,
     the dancing dogs and the bag tag. Stroke draws the outline on, then the fill blooms in as
     the stroke fades; it fires when the asset's scene gets .is-revealed. JS sets --len per path
     (its measured length) and --dd/--fd (staggered draw + fill delays). */
  .draw-asset path {
    stroke: currentColor;
    stroke-width: 1;
    vector-effect: non-scaling-stroke;
    stroke-linecap: round;
    stroke-linejoin: round;
    fill-opacity: 0;
    stroke-opacity: 1;
    stroke-dasharray: var(--len, 1200);
    stroke-dashoffset: var(--len, 1200);
    transition:
      stroke-dashoffset 1.05s cubic-bezier(.55,.05,.45,1),
      fill-opacity 0.6s var(--e-out-2),
      stroke-opacity 0.6s var(--e-out-2);
    transition-delay: var(--dd, 0s), var(--fd, .5s), var(--fd, .5s);
  }
  .is-revealed .draw-asset path,
  .draw-asset.is-revealed path {
    stroke-dashoffset: 0;
    fill-opacity: 1;
    stroke-opacity: 0;
  }

  /* CLIP-WIPE REVEAL — mobile only; on desktop the Nº19 mark just cross-fades in (no wipe) */
  @media (max-width: 760px) {
    .mark-inner { clip-path: inset(100% 0 0 0); transition: clip-path 1.15s var(--e-out-3); }
    .section--mark.is-revealed .mark-inner { clip-path: inset(0 0 0 0); }
  }

  @media (prefers-reduced-motion: reduce) {
    .mark-inner { clip-path: none !important; }
  }

  .hero__golfer-wrap {
    position: relative;
    display: block;
    width: clamp(172px, 18.5vw, 242px);
    margin: var(--s-16) auto 0;
    transform: translateX(clamp(-9px, -0.6vw, -4px));
    overflow: hidden;
    line-height: 0;
  }
  .hero__golfer {
    width: 100%;
    aspect-ratio: 163.99 / 200.63;
    display: block;
    background-color: var(--ink);
    -webkit-mask-image: url('../img/dog2.svg');
            mask-image: url('../img/dog2.svg');
    -webkit-mask-size: contain;
            mask-size: contain;
    -webkit-mask-repeat: no-repeat;
            mask-repeat: no-repeat;
    -webkit-mask-position: center;
            mask-position: center;
  }
  .hero__golfer-mask {
    position: absolute;
    inset: 0;
    pointer-events: none;
    overflow: hidden;
    -webkit-mask-image: url('../img/dog2.svg');
            mask-image: url('../img/dog2.svg');
    -webkit-mask-size: 100% 100%;
            mask-size: 100% 100%;
    -webkit-mask-repeat: no-repeat;
            mask-repeat: no-repeat;
    -webkit-mask-position: center;
            mask-position: center;
  }
  .hero__golfer-sheen {
    position: absolute;
    inset: -40%;
    background: linear-gradient(
      100deg,
      transparent 30%,
      rgba(255, 250, 235, 0.4) 50%,
      transparent 70%
    );
    filter: blur(28px);
    transform: translateX(-130%);
    transition: none;
  }
  /* pointer-gated: a tap would fire the sheen sweep and leave it parked at 130% */
  @media (hover: hover) and (pointer: fine) {
    .hero__golfer-wrap:hover .hero__golfer-sheen {
      transform: translateX(130%);
      transition: transform 1.7s cubic-bezier(.42, 0, .58, 1);
    }
  }

  .hero__brand-block {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--s-16);
    text-align: center;
  }
  .hero__scroll-row {
    display: flex;
    justify-content: center;
    align-items: center;
  }
  .hero__scroll {
    display: inline-flex;
    flex-direction: column;
    align-items: center;
    gap: var(--s-16);
    font-family: var(--f-mono);
    font-size: var(--t-micro);
    letter-spacing: 0.24em;
    text-transform: uppercase;
    color: var(--char);
    cursor: pointer;
    transition: color .25s var(--e-out-2);
  }
  .hero__scroll:hover { color: var(--ink); }
  .hero__scroll-line {
    display: block;
    width: 1px;
    height: 36px;
    position: relative;
    overflow: hidden;
    line-height: 0;
  }
  .hero__scroll-line::after {
    content: '';
    position: absolute;
    inset: 0;
    background: var(--ink);
    transform: scaleY(0);
    transform-origin: top;
    animation: scrollTrack 2.4s cubic-bezier(.65, 0, .35, 1) infinite;
  }
  @keyframes scrollTrack {
    0%   { transform: scaleY(0); transform-origin: top; }
    45%  { transform: scaleY(1); transform-origin: top; }
    50%  { transform: scaleY(1); transform-origin: bottom; }
    95%  { transform: scaleY(0); transform-origin: bottom; }
    100% { transform: scaleY(0); transform-origin: top; }
  }
  .hero__scroll-label {
    display: block;
    font-family: var(--f-sans);
    text-transform: uppercase;
    letter-spacing: 0.16em;
  }

  /* HERO MARQUEE — a scrolling strip pinned to the bottom of the first (hero) scene only */
  .hero__marquee {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 2;
    overflow: hidden;
    pointer-events: none;
    padding: var(--s-12) 0;
    /* no panel — the type inverts whatever's behind it (see mix-blend-mode below) */
    mix-blend-mode: difference;
  }
  .hero__marquee-track {
    display: flex;
    align-items: center;
    width: max-content;
    animation: heroMarquee 60s linear infinite;
  }
  .hero__marquee-track .hm-item,
  .hero__marquee-track .hm-sep {
    font-family: var(--f-mono);
    font-size: clamp(0.72rem, 1.1vw, 1.05rem);
    font-weight: 100;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    /* white over a difference blend = the exact opposite colour of the backdrop */
    color: #fff;
    white-space: nowrap;
  }
  .hero__marquee-track .hm-sep { margin: 0 0.6em; color: #fff; }
  @keyframes heroMarquee { from { transform: translateX(0); } to { transform: translateX(-50%); } }
  @media (prefers-reduced-motion: reduce) { .hero__marquee-track { animation: none; } }

  /* MOBILE — legibility and frame budget over the video.
     Two separate problems, same media query.
     1) iOS Safari does not composite mix-blend-mode over a playing <video>,
        the same limitation this file already works around for .scenes__duo
        ("iOS won't blend a layer over a <video>"). The difference blend drops
        out or flickers as the video repaints. Recolouring the type alone is
        not enough - the footage is bright under a cream veil, so ANY single
        colour washes out somewhere along the strip. It needs its own solid
        ground, so the strip becomes a navy bar with cream type.
     2) That bar is also opaque, which means the compositor no longer has to
        blend this layer against every video frame. */
  @media (max-width: 760px) {
    .hero__marquee {
      mix-blend-mode: normal;
      background: var(--ink);
      padding: var(--s-8) 0;
    }
    .hero__marquee-track .hm-item,
    .hero__marquee-track .hm-sep { color: var(--linen); }
  }

  /* hero entrance — logo strokes in, then fills */
  .hero.loaded .hero__logo path {
    stroke-dashoffset: 0;
    fill-opacity: 1;
    stroke-opacity: 0;
  }
  .hero.loaded .hero__logo path:nth-child(1) { transition-delay: 0.10s, 0.50s, 0.50s; }
  .hero.loaded .hero__logo path:nth-child(2) { transition-delay: 0.22s, 0.62s, 0.62s; }
  .hero.loaded .hero__logo path:nth-child(3) { transition-delay: 0.34s, 0.74s, 0.74s; }
  .hero.loaded .hero__logo path:nth-child(4) { transition-delay: 0.46s, 0.86s, 0.86s; }

  @media (max-width: 880px) {
    .hero { padding: var(--gutter); }
    .hero__main { gap: var(--s-32); }
    .hero__logomark { width: clamp(110px, 22vw, 150px); }
    .hero__golfer-wrap { width: clamp(140px, 30vw, 190px); }
  }

  /* =============================================
     SECTION — viewport-locked snap chapter
     ============================================= */
  .section {
    position: sticky;
    top: 0;
    height: calc(var(--vh, 1vh) * 100);   /* locked height — stable across the iOS toolbar */
    min-height: calc(var(--vh, 1vh) * 100);
    background: var(--bg);
    color: var(--ink);
    display: flex;
    flex-direction: column;
  }
  /* studio: a full-viewport Newsreader design statement — copy pushed to every edge,
     only the fixed nav is cleared at the top */
  .studio-statement {
    flex: 1;
    width: 100%;
    display: flex;
    flex-direction: column;
    justify-content: center;          /* one dense block — no gap pinned to the edges */
    gap: clamp(0.4rem, 1.4vh, 1.2rem);
    padding: clamp(5.25rem, 11vh, 7rem) var(--gutter) clamp(1.5rem, 4vh, 2.75rem);
  }
  .studio-statement__p {
    font-family: var(--f-serif);
    font-weight: 400;
    /* width- AND height-aware: the vh term caps the type on short (MacBook) viewports so the
       block can't run off the bottom even before the JS height-fitter refines it */
    font-size: clamp(2.4rem, min(6.7vw, 7.2vh), 7.75rem);
    line-height: 1.06;
    letter-spacing: -0.02em;
    color: var(--ink);
    margin: 0;
    text-align: justify;
    text-justify: inter-word;
  }
  /* per-word ink-in on scroll (same mechanic as the lead, sized for the statement) */
  .studio-statement__p .word {
    display: inline-block;
    color: rgba(28, 43, 71, 0.20);
    transition: color .6s var(--e-out-2);
  }
  .studio-statement__p .word.is-revealed { color: var(--ink); }
  /* terracotta italic accent — one warm focal phrase, matches the brand accent */
  .studio-statement__p .word--accent { font-style: italic; }
  .studio-statement__p .word--accent.is-revealed { color: var(--accent); }

  @media (max-width: 860px) {
    .studio-statement__p { font-size: clamp(2.2rem, 9vw, 4rem); line-height: 1.12; }
  }

  /* Nº19 mark — a pinned cross-fade scene (fades in by opacity, in place, like the others).
     The numeral is full-width at its natural ratio, so it's taller than the scene; the scene
     clips it (overflow:hidden) showing the top, and once the scene is fully faded in, extra
     scroll scrubs the numeral upward (JS translateY) to reveal the rest. */
  /* NUMBER + 19 are ONE connected unit (.mark-inner). On desktop the unit is full-width and
     taller than the scene, so it scrubs upward together (JS translates .mark-inner) to reveal
     the lower half — NUMBER and 19 always move as a single entity. */
  .section--mark {
    display: block;
    overflow: hidden;
    padding-top: clamp(5rem, 10vh, 6.5rem);   /* top of the unit sits below the fixed nav */
  }
  .mark-inner {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: clamp(0.25rem, 1.2vh, 1rem);   /* tight — NUMBER sits on the 19 as one mark */
    will-change: transform;
  }
  .mark-number { display: block; width: 100%; height: auto; }
  .mark-statement__svg {
    display: block;
    width: 100%;
    height: auto;        /* natural ratio → full width, tall */
    fill: var(--ink);
  }
  @media (prefers-reduced-motion: reduce) {
    /* no clip/scrub — the unit sits at natural height and scrolls normally */
    .section--mark { height: auto; overflow: visible; }
    .mark-inner { transform: none !important; }
  }
  /* Mobile: the same unit, centred and sized to fit (no scrub). */
  @media (max-width: 700px) {
    .section--mark {
      display: flex;
      align-items: center;
      justify-content: center;
      padding: clamp(5rem, 10vh, 6.5rem) var(--gutter) clamp(2rem, 6vh, 4rem);
    }
    .mark-inner { width: 100%; gap: clamp(1.25rem, 5vh, 3rem); transform: none !important; }
    .mark-number { width: 88%; }
    .mark-statement__svg { width: 84%; }
  }

  /* DESKTOP: three repeating "Number 19" lockups read as ONE composition that fills the
     scene as large as possible below the nav. The row spans the full content width; JS scales
     the whole group down only if it would run past the available height. Mobile keeps the
     single tall scroll-scrub mark (.mark-inner). */
  .mark-repeat { display: none; }
  @media (min-width: 701px) {
    .section--mark {
      display: flex;
      align-items: center;
      justify-content: center;
      overflow: visible;
      padding: clamp(5.5rem, 11vh, 7rem) var(--gutter) clamp(1.75rem, 4vh, 3rem);   /* top clears the fixed nav */
    }
    .mark-inner { display: none; }
    .mark-repeat {
      display: flex;
      align-items: stretch;
      justify-content: center;
      gap: clamp(2.5rem, 7vw, 8rem);   /* space between the three; outer edges stay flush */
      width: 100%;
      height: 100%;
    }
    .mark-unit {
      flex: 1 1 0;
      min-width: 0;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      gap: clamp(0.35rem, 1vh, 0.9rem);
    }
    /* show only the middle lockup; the side two stay in layout (visibility) so the middle
       keeps its size + centred position */
    .mark-unit:nth-child(1),
    .mark-unit:nth-child(3) { visibility: hidden; }
    .mark-unit__word { width: 100%; height: auto; display: block; }
    /* fill sits on the <svg>, not on a descendant `path`: the mark is a <use>
       reference now, and CSS cannot select into a shadow tree. `fill` inherits. */
    .mark-unit__19   { width: 100%; height: auto; display: block; fill: var(--ink); }
  }

  /* Pennants — a 3×3 navy bunting wall with the wordmark underneath. */
  .section--dogs {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: clamp(1.75rem, 5vh, 3.5rem);
    padding: clamp(5rem, 10vh, 6.5rem) var(--gutter) clamp(2rem, 5vh, 3rem);   /* clear nav */
  }

  /* 3×3 pennant grid — each SVG recoloured navy via a mask (like the hero golfer) */
  .pennant-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: clamp(0.75rem, 2vw, 2rem);
    width: clamp(300px, 60vw, 680px);
    margin: 0 auto;
  }
  .pennant {
    display: block;
    width: 100%;
    aspect-ratio: 836.69 / 393.15;     /* pennant artwork ratio */
    background-color: var(--ink);       /* navy shows through the masked shape */
    -webkit-mask: var(--src) center / contain no-repeat;
            mask: var(--src) center / contain no-repeat;
    opacity: 0;
    transform: translateY(16px) scale(0.96);
    transition: opacity .55s var(--e-out-2), transform .7s var(--e-out-3);
  }
  .section--dogs.is-revealed .pennant { opacity: 1; transform: none; }
  /* staggered pop, left-to-right / top-to-bottom */
  .section--dogs.is-revealed .pennant:nth-child(1) { transition-delay: .04s; }
  .section--dogs.is-revealed .pennant:nth-child(2) { transition-delay: .10s; }
  .section--dogs.is-revealed .pennant:nth-child(3) { transition-delay: .16s; }
  .section--dogs.is-revealed .pennant:nth-child(4) { transition-delay: .22s; }
  .section--dogs.is-revealed .pennant:nth-child(5) { transition-delay: .28s; }
  .section--dogs.is-revealed .pennant:nth-child(6) { transition-delay: .34s; }
  .section--dogs.is-revealed .pennant:nth-child(7) { transition-delay: .40s; }
  .section--dogs.is-revealed .pennant:nth-child(8) { transition-delay: .46s; }
  .section--dogs.is-revealed .pennant:nth-child(9) { transition-delay: .52s; }

  /* wordmark sits under the pennant wall — fades + rises in after the pennants pop */
  .dogs-wordmark {
    display: block;
    width: clamp(180px, 26vw, 420px);
    height: auto;
    opacity: 0;
    transform: translateY(26px);
    transition: opacity .7s var(--e-out-2), transform .9s var(--e-out-3);
    transition-delay: .55s;
  }
  .section--dogs.is-revealed .dogs-wordmark {
    opacity: 1;
    transform: translateY(0);
  }

  /* progress line — fixed at the bottom of the viewport, above the overlapping blocks;
     tracks scroll from the Nº19 section to the bag tag section. */
  .ticker {
    position: fixed;
    left: 0;
    right: 0;
    bottom: var(--s-32);
    z-index: 40;
    display: flex;
    justify-content: center;
    pointer-events: none;
    transition: opacity .5s var(--e-out-2);
  }
  .ticker.is-hero-hidden { opacity: 0; }
  @media (prefers-reduced-motion: reduce) { .ticker { display: none; } }
  .ticker__bar {
    width: clamp(110px, 13vw, 170px);
    height: 2px;
    background: rgba(28, 43, 71, 0.16);
    transition: background .35s var(--e-out-2);
  }
  .ticker__fill {
    display: block;
    height: 100%;
    background: var(--ink);
    transform: scaleX(0);
    transform-origin: left center;
    transition: background .35s var(--e-out-2);
  }
  /* legible over the navy bag tag section */
  .ticker.is-on-dark .ticker__bar  { background: rgba(249, 243, 230, 0.22); }
  .ticker.is-on-dark .ticker__fill { background: var(--bg); }

  /* =============================================
     STACK — pinned stage; screens cross-fade in place
     ============================================= */
  .stack {
    position: relative;
    z-index: 1;             /* rides above the pinned footer */
    background: var(--bg);  /* opaque — hides the footer until the page lifts off it */
  }
  .scenes { position: relative; }   /* reduced-motion fallback: normal sticky stack */

  /* persistent video backdrop — stays put while hero/studio/clients cross-fade over it */
  .scenes__bg {
    position: absolute;
    inset: 0;
    z-index: 0;
    overflow: hidden;
    background: var(--bg);
  }
  /* always-present still behind the video — paints instantly on load (no cream flash) and on
     iOS where the video waits for a gesture; the playing video covers it. */
  .scenes__poster {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    pointer-events: none;
  }
  .scenes__video {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    pointer-events: none;   /* never intercept taps — it's a backdrop, not a player */
    display: block;         /* TEST: duotone video backdrop enabled (was hidden / cream) */
    transform: translateZ(0);   /* keep the video on its own GPU layer (less recompositing) */
    backface-visibility: hidden;
  }
  .scenes__fallback { display: none !important; }   /* and its Low-Power-Mode still */
  /* Kill iOS's "tap to play" overlay. WebKit paints this big start-playback glyph
     over any video it refuses to autoplay (Low Power Mode). Our JS starts the
     video on first gesture; this just removes the ugly button it shows meanwhile. */
  .scenes__video::-webkit-media-controls,
  .scenes__video::-webkit-media-controls-start-playback-button,
  .scenes__video::-webkit-media-controls-play-button,
  .scenes__video::-webkit-media-controls-panel,
  .scenes__video::-webkit-media-controls-overlay-play-button {
    display: none !important;
    -webkit-appearance: none;
    appearance: none;
    opacity: 0 !important;
  }
  /* sharp still fallback, shown only when iOS Low Power Mode blocks video autoplay */
  .scenes__fallback {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    pointer-events: none;
    display: none;
  }
  /* keep the video rendered (opacity 0, not display:none) so a later user gesture can
     still start it on iOS and we can upgrade back to real video; the still covers it meanwhile */
  .scenes__bg.is-fallback .scenes__video { opacity: 0; }
  .scenes__bg.is-fallback .scenes__fallback { display: block; }
  .scenes__veil {
    position: absolute;
    inset: 0;
    pointer-events: none;
    /* ~50% cream scrim with a gentle vertical fade — denser (less transparent) toward the top */
    background: linear-gradient(to bottom,
      rgba(249, 243, 230, 0.55) 0%,
      rgba(249, 243, 230, 0.37) 45%,
      rgba(249, 243, 230, 0.20) 100%);
  }
  /* film grain over the backdrop video — a static tiled SVG-noise texture (no per-frame
     filter, so it doesn't re-composite the playing video every frame like a live filter would) */
  .scenes__grain {
    position: absolute;
    inset: 0;
    z-index: 2;
    pointer-events: none;
    opacity: 0.09;
    background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='3' /%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.5'/%3E%3C/svg%3E");
    background-size: 170px 170px;
  }
  @media (prefers-reduced-motion: reduce) { .scenes__video { display: none; } }

  /* =============================================
     MOBILE FRAME BUDGET
     Everything above sits ON TOP of a playing video, and every video frame
     forces the whole stack to re-composite. On desktop that is free; on a
     phone it is the difference between smooth playback and the video barely
     running. Two layers are worth far less than they cost here.
     ============================================= */
  @media (max-width: 760px) {
    /* A full-viewport tiled noise texture composited over live video, 30-60
       times a second, for an effect that is nearly invisible at phone DPI. */
    .scenes__grain { display: none; }

    /* will-change permanently promotes each scene to its own GPU layer. Four
       full-screen layers, plus video, poster, veil and duotone, exceeds what a
       phone compositor will keep in memory - it then thrashes or falls back to
       software, which shows up as stuttering playback. The cross-fade still
       works exactly the same; the layers just are not pre-promoted. */
    .scenes > .hero,
    .scenes > .section { will-change: auto; }
  }

  .stage-spacer { display: none; }

  @media (prefers-reduced-motion: no-preference) {
    /* .stack sizes to: pinned stage + spacer (cross-fade scroll) + contact + footer */
    /* spacer baseline = (screens − 1) × STAGE × 100vh (4 scenes, STAGE 1.8 → 540vh) for
       the hero→studio→mark→dogs cross-fade; JS may extend it for the mark's reveal.
       Bag tag + contact then rise up and overlap the pinned stage. */
    /* EVERY rule in this block is scoped to html.js on purpose.
       These are the rules that hand control of the layout to JavaScript -
       sections stacked absolutely at opacity 0, waiting to be cross-faded. If
       the script never runs they would leave the page blank. Scoped this way,
       no-JS falls through to the plain stacked document below. */
    html.js .stage-spacer { display: block; height: calc(var(--vh, 1vh) * 540); }   /* locked — 4 scenes (hero→studio→mark→bagtag): (4-1)×1.8×100vh */
    html.js .scenes {
      position: sticky;
      top: 0;
      /* Stage HEIGHT and scroll MATH are deliberately two different things.
         Scroll distance stays on the locked --vh (the spacer below, and vhPx in
         sceneScroll) so cross-fade timing can never drift as the toolbar moves —
         that part was right, and 100dvh here would indeed make the maths and the
         stage disagree every frame.

         But the stage's own height must NOT come from a JS-frozen constant. --vh
         is measured once at load, when the toolbar is SHOWN, so it locks to the
         small viewport. The instant the toolbar retracts the viewport grows past
         it and the stage stops reaching the bottom of the screen — on a 393x664
         iPhone that is 81px of .stage-spacer, which has no background, showing
         cream body through it. That is the cream bar.

         100lvh is the LARGEST the viewport can ever be, and unlike dvh it does
         not move as the toolbar animates. So the stage is >= the visible area in
         both toolbar states and can never leave a gap, while the maths stay
         locked. The calc() line before it is the fallback for browsers without
         lvh; the later declaration wins wherever it is supported. */
      height: calc(var(--vh, 1vh) * 100);
      height: 100lvh;
      overflow: hidden;
      background: var(--bg);
    }
    /* screens stack in the same spot, above the video; JS cross-fades opacity + depth */
    html.js .scenes > .hero,
    html.js .scenes > .section {
      position: absolute;
      inset: 0;
      height: 100%;
      min-height: 0;
      z-index: 1;
      background: transparent;
      transform-origin: 50% 45%;
      will-change: transform, opacity;
      backface-visibility: hidden;
    }
    /* only the hero is visible before JS sets per-scroll opacity */
    html.js .scenes > .section { opacity: 0; }
    /* bag-tag scene is transparent: the navy now comes from the .scenes__duo blend layer
       below, so the video shows through it recoloured navy (a duotone). */
    html.js .scenes > .section--bagtag { background: transparent; }
    /* navy duotone over the video — multiply tints the footage toward brand navy while its
       brighter motion still reads through. Lives in the .scenes stage so the blend hits the
       video; sits below the cross-fading scenes (z-index 1) and is faded in by JS for the
       bag-tag beat only (opacity 0 elsewhere). */
    .scenes__duo {
      position: absolute;
      inset: 0;
      z-index: 1;
      background: var(--ink);
      mix-blend-mode: color;
      opacity: 0;
      pointer-events: none;
      will-change: opacity;
    }
  }
  @media (prefers-reduced-motion: reduce) { .scenes__duo { display: none; } }

  /* Bag tag → Work: an overlap region that rises over the pinned stage, then PINS. The bag
     tag is the base; the work index cross-fades in over it (JS scrubs its opacity), so the
     list is a separate fade-in beat rather than a section connected to the tag by scroll. */
  .tag-stage { position: relative; z-index: 2; }   /* height = hold + contact */
  .tag-hold { height: calc(var(--vh, 1vh) * 70); }   /* bag tag dwells here before work rises */
  /* Bag tag — white tag filling the height on a navy field. */
  .section--bagtag {
    position: absolute;
    inset: 0;
    background: var(--ink);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: clamp(5rem, 10vh, 6.5rem) var(--gutter) clamp(2rem, 5vh, 3rem);  /* clear nav, breathe at base */
  }
  .bagtag-art {
    display: block;
    height: 62vh;       /* viewport-based so it resolves on the inlined SVG; sits fully inside */
    width: auto;        /* portrait, natural ratio */
    max-width: 100%;
  }

  /* =============================================
     WORK INDEX — Swiss/utilitarian, one mono size, hairline rules. Fades in over the bag tag.
     ============================================= */
  .section--index {
    position: relative;
    z-index: 3;          /* rises up and overlaps the pinned bag tag */
    min-height: calc(var(--vh, 1vh) * 100);
    background: var(--bg);
    display: flex;
    flex-direction: column;
    justify-content: center;
    /* heavier bottom padding lifts the list up, leaving cream breathing room before contact.
       (Must be padding, not margin — margin would be transparent and reveal the pinned stage.) */
    padding: clamp(5rem, 10vh, 7rem) var(--gutter) clamp(7rem, 16vh, 11rem);
  }
  /* top-only depth as it rises over the duo — a gradient above the section so the shadow
     can never bleed onto the contact section below it */
  .section--index::before {
    content: "";
    position: absolute;
    left: 0; right: 0; top: 0;
    height: 48px;
    transform: translateY(-100%);
    background: linear-gradient(to top, rgba(28, 43, 71, 0.22), rgba(28, 43, 71, 0));
    pointer-events: none;
  }
  @media (prefers-reduced-motion: reduce) {
    /* no pin — bag tag then work stack and scroll normally */
    .tag-stage { height: auto; }
    .section--bagtag { position: relative; inset: auto; height: 100vh; }
    .section--index { position: relative; z-index: auto; box-shadow: none; min-height: 100vh; }
  }
  /* phones: smaller bag tag, shorter hold */
  @media (max-width: 760px) {
    .bagtag-art { height: 46vh; }
    .tag-hold { height: 70vh; }
    /* iOS won't blend a layer over a <video>, so the desktop mix-blend-mode duotone is invisible
       on phones. Fall back to a semi-transparent navy (plain alpha DOES composite over video on
       iOS): the footage shows through it, recoloured navy. Opacity is still JS-driven for the fade. */
    .scenes__duo {
      mix-blend-mode: normal;
      background: rgba(28, 43, 71, 0.7);
    }
  }
  .index__wrap { width: 100%; margin: 0 auto; }

  /* SERVICES — a large two-line flowing line above the work list */
  .work-services {
    font-family: var(--f-serif);
    font-weight: 400;
    font-size: clamp(2rem, 5.2vw, 7rem);   /* JS scales it up to the largest size that fits 3 lines */
    line-height: 1.05;
    letter-spacing: -0.02em;
    color: var(--ink);
    text-align: left;                      /* ragged right */
    /* span the full screen width — break out of the section's gutter padding */
    margin: 0 calc(-1 * var(--gutter)) clamp(6rem, 13vh, 11rem);
    padding: 0 var(--gutter);
  }
  /* Belt-and-braces against iOS text autosizing. html already carries
     text-size-adjust:100%, but this element is the exact shape Safari's heuristic
     targets — full-bleed via negative margins, with white-space:nowrap children — and
     it is the one place where an inflated render also invalidates the JS fitter's
     measurement, so the size it welds on is computed from wrong numbers. `none` opts
     this subtree out of the heuristic entirely. */
  .work-services {
    -webkit-text-size-adjust: none;
    text-size-adjust: none;
  }
  .work-services .service {
    white-space: nowrap;                   /* keep each service name intact when wrapping */
    transition: color .3s var(--e-out-2), opacity .3s var(--e-out-2);
  }
  /* hover one WORD → clay red + full opacity; the rest fade back. :has() so it only triggers
     when an actual service is hovered, not just the mouse being in the container */
  @media (hover: hover) and (pointer: fine) {
    .work-services:has(.service:hover) .service:not(:hover) { opacity: 0.35; }
    .work-services .service:hover { opacity: 1; color: var(--accent); }
  }

  /* everything in the index is ONE size in the mono face */
  .index__legend,
  .index__row {
    font-family: var(--f-mono);
    font-size: clamp(0.8rem, 1vw, 1rem);
    font-weight: 400;
    letter-spacing: 0.01em;
  }
  /* header row = column titles aligned to the grid below (same mono face + size as the rows) */
  .index__legend {
    display: grid;
    grid-template-columns: 3.5ch minmax(0, 1.4fr) minmax(0, 1.3fr) 5ch;
    gap: 0 clamp(0.6rem, 1.4vw, 1rem);
    padding: 0 2rem 0.7rem 0;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--ink);
    border-bottom: 1px solid rgba(28, 43, 71, 0.3);   /* rule between the titles and the list */
    margin-bottom: clamp(0.75rem, 2.5vh, 1.75rem);    /* gap below the titles before the list */
  }
  .index__legend__year { text-align: right; }   /* match the right-aligned year column */
  .index__list { list-style: none; margin: 0; padding: 0; }
  /* ledger zebra — alternating rows get a soft gray fill, the others none. The rows break out
     of the section gutter to the full viewport width (negative inline margin), with matching
     padding so the text stays aligned with the header columns. */
  .index__entry {
    margin-inline: calc(-1 * var(--gutter));
    padding-inline: var(--gutter);
  }
  .index__entry:nth-child(even) { background: rgba(28, 43, 71, 0.05); }

  /* each project = a clickable row that expands a description panel. Dotted LEADERS run between
     the columns (the ::after fillers below) rather than horizontal separators; on phones we fall
     back to a horizontal dotted rule per entry (see the mobile block). */
  .index__row {
    width: 100%;
    background: none;
    border: 0;
    -webkit-appearance: none;
    appearance: none;
    cursor: pointer;
    color: var(--ink);
    text-align: left;
    display: grid;
    grid-template-columns: 3.5ch minmax(0, 1.4fr) minmax(0, 1.3fr) 5ch;
    grid-template-areas: "num name disc year";
    gap: 0 clamp(0.6rem, 1.4vw, 1rem);
    align-items: baseline;
    padding: 0.8rem 2rem 0.8rem 0;   /* row leading; right room for the +/− toggle */
    text-transform: uppercase;
    letter-spacing: 0.04em;
    position: relative;
  }
  /* all type navy — one mono size; names a touch bolder, discipline not uppercased */
  .index__num  { grid-area: num;  color: var(--ink); font-variant-numeric: tabular-nums; }
  .index__name { grid-area: name; color: var(--ink); font-weight: 500; transition: color .25s var(--e-out-2); }
  .index__disc { grid-area: disc; color: var(--ink); text-transform: none; font-weight: 100; }
  .index__year { grid-area: year; color: var(--ink); text-align: right; font-variant-numeric: tabular-nums; }

  /* +/− toggle, far right of the row */
  .index__toggle {
    position: absolute;
    right: 0;
    top: 50%;
    width: 12px;
    height: 12px;
    transform: translateY(-50%);
    pointer-events: none;
  }
  .index__toggle::before,
  .index__toggle::after {
    content: "";
    position: absolute;
    background: var(--ink);
    transition: transform .35s var(--e-out-3), background .25s var(--e-out-2);
  }
  .index__toggle::before { left: 0; right: 0; top: 50%; height: 1px; transform: translateY(-50%); }   /* — bar */
  .index__toggle::after  { top: 0; bottom: 0; left: 50%; width: 1px; transform: translateX(-50%); }    /* | bar */
  .index__entry.is-open .index__toggle::after { transform: translateX(-50%) scaleY(0); }                 /* + → − */
  .index__entry.is-open .index__toggle::before,
  .index__entry.is-open .index__toggle::after { background: var(--accent); }
  .index__entry.is-open .index__name { color: var(--accent); }
  @media (hover: hover) and (pointer: fine) {
    .index__row:hover .index__name { color: var(--accent); }
    .index__row:hover .index__toggle::before,
    .index__row:hover .index__toggle::after { background: var(--accent); }
  }

  /* expandable description — grid-rows 0fr→1fr animates the auto height */
  .index__panel {
    display: grid;
    grid-template-rows: 0fr;
    transition: grid-template-rows .5s var(--e-out-3);
  }
  .index__entry.is-open .index__panel { grid-template-rows: 1fr; }
  .index__panel-inner { overflow: hidden; min-height: 0; }
  .index__desc {
    font-family: var(--f-mono);
    text-transform: none;
    letter-spacing: 0;
    font-size: clamp(0.85rem, 1vw, 0.95rem);
    line-height: 1.5;
    color: var(--char);
    max-width: 56ch;
    padding: 0.1rem 0 1.3rem;
    padding-left: calc(3.5ch + var(--gutter));   /* align under the name column */
  }
  @media (prefers-reduced-motion: reduce) {
    .index__panel { transition: none; }
  }
  @media (max-width: 600px) {
    /* iPhone: stays one 100vh section (no internal scroll). Each entry is two lines — name
       on line 1, discipline + year on line 2 — sized small so all rows fit and stay centred. */
    .section--index { padding: clamp(4rem, 8.5vh, 5.5rem) var(--gutter) clamp(1.25rem, 3.5vh, 2.25rem); }
    .index__legend {
      display: flex;
      justify-content: space-between;
      font-size: 0.85rem;
      padding: 0 0 0.5rem;
    }
    .index__legend__num, .index__legend__cat { display: none; }   /* category lives on line 2 below */
    .index__row {
      grid-template-columns: 2.2ch 1fr auto;
      grid-template-areas:
        "num name year"
        "num disc disc";
      column-gap: 0.8rem;
      row-gap: 0.1rem;
      align-items: start;
      padding: 0.55rem 1.4rem 0.55rem 0;   /* right room for the toggle */
      font-size: clamp(0.7rem, 3.3vw, 0.82rem);
      letter-spacing: 0.02em;
    }
    .index__name { line-height: 1.2; }
    .index__toggle { top: 0.66rem; bottom: auto; transform: none; width: 11px; height: 11px; }   /* sit on the top (name/year) line */
    .index__desc { padding-left: calc(2.2ch + 0.8rem); }
  }

  /* contact is a normal section after the work index — it scrolls up naturally once the
     pin releases (no rising overlap). Split: video left, studio card right. */
  .section--contact {
    position: relative;
    background: var(--bg);
  }
  .contact__split {
    display: grid;
    grid-template-columns: 1fr 1fr;
    align-items: stretch;
    min-height: calc(var(--vh, 1vh) * 100);
  }
  @media (prefers-reduced-motion: reduce) {
    .tag-hold { display: none; }   /* no pinned overlap — sections just stack */
  }
  .contact__media {
    position: relative;
    overflow: hidden;
    background: var(--ink);
  }
  .contact__media video {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
  }
  /* Kill iOS's "tap to play" overlay on the contact video too (Low Power Mode paints it). */
  .contact__media video::-webkit-media-controls,
  .contact__media video::-webkit-media-controls-start-playback-button,
  .contact__media video::-webkit-media-controls-play-button,
  .contact__media video::-webkit-media-controls-panel,
  .contact__media video::-webkit-media-controls-overlay-play-button {
    display: none !important;
    -webkit-appearance: none;
    appearance: none;
    opacity: 0 !important;
  }
  /* Number 19 lockup — hidden until the video is hovered, then fades up over a soft scrim */
  .contact__media-mark {
    position: absolute;
    inset: 0;
    z-index: 2;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: clamp(0.5rem, 1.5vh, 1.25rem);
    padding: clamp(1rem, 3vh, 2rem);
    background: rgba(28, 43, 71, 0.42);   /* navy scrim — atmospheric; navy mark reads against the lit footage */
    opacity: 0;
    transition: opacity .55s var(--e-out-2);
    pointer-events: none;
  }
  /* the 19 is square, so its height sets its width; the wordmark matches that same width.
     19 height ≈ 50% of the container → 19 + wordmark stack ≈ 60% of the container height */
  /* fill on the <svg> itself — the mark is a <use> reference; CSS can't reach its shadow tree */
  .contact__media-19 { height: calc(var(--vh, 1vh) * 50); width: auto; fill: var(--ink); }
  .contact__media-number { width: calc(var(--vh, 1vh) * 50); height: auto; }   /* number.svg is natively navy */
  @media (hover: hover) and (pointer: fine) {
    .contact__media:hover .contact__media-mark { opacity: 1; }
  }
  .contact__panel {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    justify-content: center;
    text-align: left;
    gap: var(--s-24);
    padding: clamp(5rem, 10vh, 7rem) var(--gutter) clamp(3rem, 8vh, 5rem);
    max-width: 36rem;
  }
  .contact__email {
    font-family: var(--f-serif);
    font-size: clamp(1.5rem, 2.8vw, 2.5rem);
    font-weight: 300;
    text-transform: lowercase;
    color: var(--ink);
    text-decoration: none;
    border-bottom: 1px solid var(--ink);
    padding-bottom: 0.08em;
    letter-spacing: -0.01em;
    transition: color .35s var(--e-out-2), border-color .35s var(--e-out-2);
  }
  .contact__email:hover {
    color: var(--accent);
    border-color: var(--accent);
  }
  .contact__intro {
    font-family: var(--f-sans);
    font-size: clamp(1rem, 1.2vw, 1.2rem);
    color: var(--char);
    max-width: 34ch;
    margin: 0;
    line-height: 1.5;
  }
  .contact__details {
    display: inline-flex;
    flex-wrap: wrap;
    align-items: baseline;
    font-family: var(--f-mono);
    font-size: var(--t-small);
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: var(--char);
  }
  .contact__details .sep {
    color: var(--mist);
    margin: 0 0.65em;
  }
  .contact__details a:hover { color: var(--ink); }
  @media (max-width: 760px) {
    .contact__split { grid-template-columns: 1fr; }
    .contact__media { height: 40vh; order: -1; }   /* video on top, card below */
    .contact__panel { max-width: none; align-items: center; text-align: center; }
    .contact__intro { margin-inline: auto; }
  }

  /* =============================================
     QUOTE — closing pull-quote (last cream beat before the footer reveals)
     ============================================= */
  .section--quote {
    position: relative;
    background: var(--bg);
    display: flex;
    align-items: center;
    justify-content: center;
    text-align: center;
    padding: clamp(7rem, 20vh, 13rem) var(--gutter);
  }
  .section--quote .quote { max-width: 28ch; margin: 0; }
  .quote__text {
    font-family: var(--f-serif);
    font-weight: 400;
    font-style: italic;
    font-size: clamp(2rem, 5.5vw, 4.25rem);
    line-height: 1.08;
    letter-spacing: -0.02em;
    color: var(--ink);
    margin: 0;
  }
  .quote__cite {
    display: block;
    margin-top: clamp(1.5rem, 3.5vh, 2.75rem);
    font-family: var(--f-mono);
    font-style: normal;
    font-size: var(--t-small);
    font-weight: 400;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: var(--char);
  }
  .quote__portrait {
    display: block;
    width: 150px;
    height: auto;
    margin: clamp(1.75rem, 4vh, 3rem) auto 0;
  }

  /* =============================================
     FOOTER — non-sticky, after .stack
     ============================================= */
  .footer {
    /* navy field — invert the whole palette to cream by redefining the vars in this scope,
       so every var(--ink) / --char / --mist inside the footer flips light automatically */
    --ink:  var(--linen);                  /* navy text → cream */
    --char: rgba(239, 234, 224, 0.72);     /* mid cream (secondary) */
    --mist: rgba(239, 234, 224, 0.45);     /* dim cream (labels) */
    background: #1a1714;                    /* warm near-black charcoal (var(--ink) is now cream) */
    color: var(--ink);
    padding: var(--s-16) var(--gutter) var(--s-48);   /* tight top so the marquee rides the edge */
    position: relative;
    z-index: 2;
  }
  /* BOTTOM REVEAL — the footer pins to the viewport bottom, behind the page; .stack scrolls up
     and lifts off it. JS adds .footer-reveal + a matching bottom margin on .stack only when the
     footer fits the viewport (so it can fully uncover); otherwise the footer just flows normally. */
  body.footer-reveal .footer {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 0;
  }
  .footer__inner {
    max-width: var(--max);
    margin: 0 auto;
    display: flex;
    flex-direction: column;
    gap: var(--s-80);
  }

  /* Marquee — scrolling text strip at the top of the footer (same as the hero, cream on navy) */
  .footer__marquee {
    overflow: hidden;
    margin: 0 calc(-1 * var(--gutter));   /* full-bleed to the footer edges */
  }
  .footer__marquee-track {
    display: flex;
    align-items: center;
    width: max-content;
    animation: heroMarquee 60s linear infinite;
  }
  .footer__marquee-track .fm-item,
  .footer__marquee-track .fm-sep {
    font-family: var(--f-mono);
    font-size: clamp(0.72rem, 1.1vw, 1.05rem);
    font-weight: 100;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--linen);
    white-space: nowrap;
  }
  .footer__marquee-track .fm-sep { margin: 0 0.6em; }
  @media (prefers-reduced-motion: reduce) { .footer__marquee-track { animation: none; } }
  .footer__brand {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: clamp(10px, 1.4vw, 20px);
    align-self: end;
  }
  .footer__brand-word,
  /* the 19 mark — cream fill on the dark footer field. Fill sits on the <svg>, not on a
     descendant `path`: the mark is a <use> reference and CSS can't select into a shadow
     tree, but `fill` is inherited so it cascades in. */
  .footer__brand-mark {
    width: clamp(88px, 10.5vw, 140px);
    height: auto;
    fill: var(--ink);
  }
  .footer__meta {
    display: grid;
    grid-template-columns: auto auto;
    column-gap: var(--s-32);
    row-gap: var(--s-8);
    font-family: var(--f-mono);
    font-size: var(--t-micro);
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: var(--char);
    text-align: right;
    padding-bottom: 0.3em;
  }
  .footer__meta dt { color: var(--mist); }
  .footer__meta dd { margin: 0; color: var(--ink); font-feature-settings: "tnum" 1; }

  /* Sitemap: four columns */
  .footer__sitemap {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: var(--s-48);
  }
  .footer__col-label {
    display: block;
    font-family: var(--f-sans);
    font-size: 0.8125rem;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--mist);
    margin-bottom: var(--s-24);
  }
  .footer__list {
    list-style: none;
    display: flex;
    flex-direction: column;
    gap: var(--s-12);
    margin: 0;
    padding: 0;
  }
  .footer__list li {
    font-family: var(--f-sans);
    font-size: clamp(0.9rem, 1vw, 1rem);
    line-height: 1.45;
    color: var(--ink);
  }
  .footer__list a {
    font-family: var(--f-sans);
    color: var(--ink);
    text-decoration: none;
    position: relative;
    display: inline-flex;
    align-items: baseline;
    gap: 0.35em;
  }
  /* invisible tap expander: 21px tall -> ~43px. The vertical growth fills the existing
     list gap exactly, so neighbouring targets meet edge-to-edge and never overlap. */
  .footer__list a { position: relative; }
  .footer__list a::before {
    content: '';
    position: absolute;
    inset: -11px -8px;
  }
  .footer__list a::after {
    content: "";
    position: absolute;
    left: 0;
    right: 100%;
    bottom: -3px;
    height: 1px;
    background: currentColor;
    transition: right .4s var(--e-out-2);
  }
  .footer__list a:hover::after { right: 0; }
  .footer__list .arr {
    font-family: var(--f-sans);
    font-size: 0.8em;
    color: var(--char);
  }

  /* Bottom bar (below the hairline): dog left, conditions + copyright right */
  .footer__bottom {
    display: grid;
    grid-template-columns: 1fr auto;
    align-items: end;
    gap: var(--s-32) var(--s-48);
    padding-top: var(--s-48);
    border-top: 1px solid rgba(239, 234, 224, 0.16);
  }
  .footer__end {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: var(--s-16);
  }
  .footer__copy {
    font-family: var(--f-mono);
    font-size: var(--t-micro);
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: var(--mist);
    text-align: right;
  }

  @media (max-width: 1024px) {
    .footer__sitemap { grid-template-columns: repeat(2, 1fr); gap: var(--s-64) var(--s-32); }
  }

  @media (max-width: 880px) {
    .contact__details { font-size: 0.625rem; }
    .contact__details .sep { margin: 0 0.4em; }
    .footer { padding: var(--s-16) var(--gutter) var(--s-32); }
    .footer__inner { gap: var(--s-48); }
    .footer__meta { text-align: left; justify-self: start; }
    .footer__sitemap { grid-template-columns: repeat(2, 1fr); gap: var(--s-64) var(--s-24); }
    .footer__bottom {
      grid-template-columns: 1fr;
      gap: var(--s-32);
    }
    .footer__end { align-items: flex-start; }
    .footer__copy { text-align: left; }
  }

  /* =============================================
     RAIL — fixed right-edge section indicator
     ============================================= */
  .rail {
    display: none !important; /* TEMP: right-side rail hidden — delete this line to bring it back */
    position: fixed;
    right: var(--gutter);
    top: 50%;
    transform: translateY(-50%);
    z-index: 45;
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: var(--s-12);
    font-family: var(--f-mono);
    font-size: var(--t-micro);
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: var(--char);
    font-variant-numeric: tabular-nums;
    user-select: none;
    pointer-events: none;
    transition: opacity .35s var(--e-out-2);
  }
  body.drawer-open .rail { opacity: 0; }
  .rail__name {
    color: var(--ink);
    transition: opacity .22s var(--e-out-2);
  }
  .rail__name.is-changing { opacity: 0; }
  .rail__count {
    color: var(--mist);
    display: inline-flex;
    gap: 0.35em;
  }
  .rail__num { color: var(--ink); }
  .rail__total { color: var(--mist); }
  .rail__line {
    width: 2px;
    height: 96px;
    background: rgba(28, 43, 71, 0.14);
    overflow: hidden;
    position: relative;
    margin-top: var(--s-8);
    border-radius: 1px;
  }
  .rail__line-fill {
    position: absolute;
    inset: 0;
    background: var(--ink);
    transform-origin: top;
    transform: scaleY(0);
    transition: transform .18s linear;
  }

  @media (max-width: 880px) {
    .rail { display: none; }
  }

  /* =============================================
     MENU DRAWER
     ============================================= */
  .drawer {
    position: fixed;
    inset: 0;
    z-index: 40;
    background: var(--ink);
    color: var(--linen);
    clip-path: inset(0 0 100% 0);
    transition: clip-path .58s cubic-bezier(.7, 0, .3, 1);
    pointer-events: none;
    overflow: hidden;
  }
  .drawer.is-open {
    clip-path: inset(0 0 0 0);
    pointer-events: auto;
  }

  .drawer__inner {
    position: relative;
    height: 100%;
    padding: calc(var(--s-120) + 16px) var(--gutter) var(--s-120);
    display: flex;
    align-items: center;
  }

  .drawer__nav { position: relative; z-index: 2; }
  .drawer__list {
    list-style: none;
    display: flex;
    flex-direction: column;
    gap: var(--s-8);
    margin: 0;
    padding: 0;
  }
  .drawer__item {
    opacity: 0;
    transform: translateY(28px);
  }
  .drawer.is-open .drawer__item {
    opacity: 1;
    transform: translateY(0);
    transition: opacity .75s var(--e-out-3), transform .75s var(--e-out-3);
  }
  .drawer.is-open .drawer__item:nth-child(1) { transition-delay: 0.18s; }
  .drawer.is-open .drawer__item:nth-child(2) { transition-delay: 0.26s; }
  .drawer.is-open .drawer__item:nth-child(3) { transition-delay: 0.34s; }
  .drawer.is-open .drawer__item:nth-child(4) { transition-delay: 0.42s; }
  .drawer.is-open .drawer__item:nth-child(5) { transition-delay: 0.50s; }
  .drawer.is-open .drawer__item:nth-child(6) { transition-delay: 0.58s; }

  .drawer__item a {
    display: inline-block;
    font-family: var(--f-stencil);
    font-size: clamp(2.5rem, 6vw, 5.25rem);
    font-weight: 300;
    letter-spacing: 0.02em;
    text-transform: uppercase;
    line-height: 1.05;
    color: var(--linen);
    text-decoration: none;
    position: relative;
    /* was 0.05em: the links rendered 28px tall with an 8px gap, a 36px pitch, so a
       44px tap target could not fit between them however the hit box was expanded.
       This is padding, not type size — the lettering is unchanged, the full-screen
       menu just stops being four thin lines you have to aim at. */
    padding: 0.42em 0;
    transition: color .35s var(--e-out-2);
  }
  .drawer__item a::after {
    content: '';
    position: absolute;
    left: 0;
    right: 100%;
    bottom: 0.16em;
    height: 1px;
    background: var(--linen);
    transition: right .5s var(--e-out-3);
  }
  .drawer__item a:hover::after { right: 0; }
  /* pointer-gated: the sibling-dim reads as a bug on touch — tapping one link leaves
     every other item greyed out, in the one menu a phone user actually needs. */
  @media (hover: hover) and (pointer: fine) {
    .drawer__list:hover .drawer__item a { color: rgba(239, 234, 224, 0.4); }
    .drawer__list:hover .drawer__item a:hover { color: var(--linen); }
  }

  /* HAMBURGER → X MORPH */
  .nav__menu-icon span {
    transition:
      top .35s var(--e-out-3),
      transform .35s var(--e-out-3),
      background-color .25s var(--e-out-2);
    transform-origin: center;
  }
  .nav__menu-toggle.is-open .nav__menu-icon span:nth-child(1) {
    top: 14px;
    transform: rotate(45deg);
  }
  .nav__menu-toggle.is-open .nav__menu-icon span:nth-child(2) {
    top: 14px;
    transform: rotate(-45deg);
  }
  .nav__menu-toggle.is-open { color: var(--linen); }

  /* NAV STATE WHEN DRAWER IS OPEN */
  .nav__location,
  .nav__brand img {
    transition: opacity .3s var(--e-out-2), filter .35s var(--e-out-2);
  }
  body.drawer-open .nav__location {
    opacity: 0;
    pointer-events: none;
  }
  body.drawer-open .nav__brand img {
    filter: brightness(0) invert(1);
    opacity: 0.94;
  }
  body.drawer-open .cursor {
    border-color: var(--linen);
  }
  body.drawer-open .cursor.is-hover {
    background-color: rgba(239, 234, 224, 0.08);
  }

  /* NAV ON DARK — over the navy bag-tag / services stage, the nav assets turn cream */
  .nav__location-display,
  .nav__menu-toggle { transition: color .35s var(--e-out-2); }
  .nav-on-dark .nav__brand img { filter: brightness(0) invert(1); opacity: 0.94; }
  .nav-on-dark .nav__location-display { color: var(--linen); }
  .nav-on-dark .nav__location-display .sep { color: rgba(239, 234, 224, 0.5); }
  .nav-on-dark .nav__menu-toggle { color: var(--linen); }   /* menu word + icon use currentColor */
  .nav-on-dark .nav__menu-toggle:hover .nav__menu-icon span { background: var(--linen); }

  @media (max-width: 880px) {
    .drawer__inner { padding: calc(var(--s-80) + 56px) var(--gutter) var(--s-120); }
    .drawer__item a { font-size: clamp(2.25rem, 10vw, 3.5rem); }
    .nav__menu-toggle.is-open .nav__menu-icon span:nth-child(1) { top: 14px; }
    .nav__menu-toggle.is-open .nav__menu-icon span:nth-child(2) { top: 14px; }
  }

  /* =============================================
     MENU AS DROPDOWN — clicking the button drops the links right below it, on top of the
     page (no full-screen navy drawer). Hamburger → X, click X to close. (Overrides the
     drawer styles above; placed last so it wins.)
     ============================================= */
  .drawer {
    inset: 0 0 auto auto;       /* anchor top-right, sized to its content */
    background: transparent;
    color: var(--ink);
    clip-path: none;
    overflow: visible;
    transition: none;
    padding: clamp(4.25rem, 9vh, 5.5rem) var(--gutter) var(--s-24);   /* clear the fixed nav */
  }
  .drawer.is-open { clip-path: none; }
  .drawer__inner { position: static; height: auto; padding: 0; display: block; }
  .drawer__list { align-items: flex-end; text-align: right; gap: var(--s-12); }
  .drawer__item {
    transform: translateY(10px);
    transition: opacity .3s var(--e-out-2), transform .3s var(--e-out-2);
  }
  /* navy over the cream page (readable, no blend) */
  .drawer__item a { font-size: clamp(1.5rem, 3.2vw, 2.5rem); color: var(--ink); }
  .drawer__item a::after { background: var(--ink); }
  .drawer__list:hover .drawer__item a { color: rgba(28, 43, 71, 0.35); }
  .drawer__list:hover .drawer__item a:hover { color: var(--ink); }
  /* over the navy bag-tag section the links + their underline flip to cream */
  body.menu-on-dark .drawer__item a { color: var(--linen); }
  body.menu-on-dark .drawer__item a::after { background: var(--linen); }
  body.menu-on-dark .drawer__list:hover .drawer__item a { color: rgba(239, 234, 224, 0.4); }
  body.menu-on-dark .drawer__list:hover .drawer__item a:hover { color: var(--linen); }
  body.menu-on-dark .nav__menu-toggle.is-open { color: var(--linen); }   /* X turns cream too */
  .nav__menu-toggle.is-open { color: var(--ink); }                 /* X stays navy over the page */
  body.drawer-open .nav__brand img { filter: none; opacity: 1; }   /* brand stays navy (no invert) */
  body.drawer-open .nav__location { opacity: 1; pointer-events: auto; }   /* keep the ticker — dropdown doesn't cover it */
  @media (max-width: 880px) {
    .drawer__inner { padding: 0; }
    .drawer__item a { font-size: clamp(1.5rem, 6vw, 2.25rem); }
  }

  /* =============================================
     CUSTOM CURSOR
     ============================================= */
  .cursor {
    position: fixed;
    top: 0; left: 0;
    width: 10px;
    height: 10px;
    border: 1px solid #ffffff;
    border-radius: 50%;
    pointer-events: none;
    z-index: 9999;
    transform: translate(-50%, -50%);
    opacity: 0;
    mix-blend-mode: difference;
    transition:
      width .35s var(--e-out-2),
      height .35s var(--e-out-2),
      background-color .35s var(--e-out-2),
      opacity .25s;
    display: none;
    will-change: transform;
  }
  .cursor.is-visible { opacity: 1; }
  .cursor.is-hover {
    width: 36px;
    height: 36px;
    background-color: rgba(255, 255, 255, 0.08);
  }
  @media (hover: hover) and (pointer: fine) {
    .cursor { display: block; }
    html, body { cursor: none; }
    a, button, .hero__scroll, .nav__menu-toggle { cursor: none; }
  }

  @media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
      animation-duration: 0.001ms !important;
      transition-duration: 0.001ms !important;
    }
    .hero { clip-path: none; }
    .hero__logo path { fill-opacity: 1; stroke-dashoffset: 0; stroke-opacity: 0; }
    .hero__golfer { clip-path: inset(0 0 0 0); opacity: 1; }
    .hero__scroll-row {
      opacity: 1;
      transform: none;
      filter: none;
    }
    .hero__scroll-line::after { animation: none; transform: scaleY(1); transform-origin: top; opacity: 0.4; }
    .drawer, .drawer__item { transition: none; }
    .drawer.is-open .drawer__item { opacity: 1; transform: none; }
    .rail__name.is-changing { opacity: 1; }
  }
