/* ==========================================================================
   v7 — motion pass (staged Sep 23 2026). Loaded AFTER v6.css on every page. Everything this
   pass adds lives here and in js/v7.js, so reverting is deleting two files and the markup
   hooks listed in _dev/V7.md — v6.css and the v6 rules underneath are unchanged.

   House rules this file keeps (CLAUDE.md §7):
     - transform and opacity only; nothing animates layout
     - scroll-driven work is CSS (compositor thread), guarded by @supports
     - every animation sits inside prefers-reduced-motion: no-preference
     - the un-animated state is always a complete, sensible picture — that is what
       Firefox, older Safari and reduced-motion visitors get
   ========================================================================== */


/* (Section 1, the P.003 waveforms, was reverted Sep 23 2026 at Julio's call — the v6 scrub is back.) */


/* ------------------------------------------------------------ 2. Ore-Ida — colourway sequence */
/* The four colourway renders share one camera, so stacked in a sticky frame they read as ONE
   cap changing colour in place. Frames 02..04 fade in over the one beneath (opacity only);
   their visibility rides along so the transparent frames above cannot catch the click meant
   for the one you are looking at. The lightbox still walks all four with its arrows, which is
   how a keyboard user reaches frames that have not been scrolled to yet. */

.seq__bar, .seq__num { display: none; }

@supports ((animation-timeline: view()) and (animation-range: entry)) {
  @media (min-width: 1024px) and (prefers-reduced-motion: no-preference) {
    .seq { height: 300vh; view-timeline: --seq block; }
    .seq__stage {
      --seq-size: min(calc(100svh - var(--header-h) - var(--space-7)), 56vw);
      position: sticky; top: var(--header-h);
      height: calc(100svh - var(--header-h));
      display: flex; align-items: center;
      padding-inline: var(--frame-pad); box-sizing: border-box;
    }
    .cs-grid--seq { position: relative; display: block; margin: 0; padding: 0;
                    width: var(--seq-size); aspect-ratio: 1; }
    .cs-grid--seq .cs-cell { position: absolute; inset: 0; }
    .cs-grid--seq .cs-zoom { height: 100%; }
    .cs-grid--seq .cs-cell img { width: 100%; height: 100%; object-fit: cover; }

    .cs-grid--seq .cs-cell:nth-child(n+2) .cs-zoom {
      animation: v7SeqIn linear both; animation-timeline: --seq;
    }
    .cs-grid--seq .cs-cell:nth-child(2) .cs-zoom { animation-range: contain 20% contain 30%; }
    .cs-grid--seq .cs-cell:nth-child(3) .cs-zoom { animation-range: contain 45% contain 55%; }
    .cs-grid--seq .cs-cell:nth-child(4) .cs-zoom { animation-range: contain 70% contain 80%; }

    /* The captions become the sequence's index: all four listed beside the frame, the current
       one at full ink and nudged in. */
    .cs-grid--seq .cs-cell__cap {
      position: absolute; left: calc(100% + var(--space-5)); top: calc(var(--i) * 2.25rem);
      margin: 0; white-space: nowrap; font-size: .8125rem; color: var(--ink);
      opacity: .32;
      animation: linear both; animation-timeline: --seq; animation-range: contain 0% contain 100%;
    }
    .cs-grid--seq .cs-cell:nth-child(1) .cs-cell__cap { --i: 0; animation-name: v7Cap1; }
    .cs-grid--seq .cs-cell:nth-child(2) .cs-cell__cap { --i: 1; animation-name: v7Cap2; }
    .cs-grid--seq .cs-cell:nth-child(3) .cs-cell__cap { --i: 2; animation-name: v7Cap3; }
    .cs-grid--seq .cs-cell:nth-child(4) .cs-cell__cap { --i: 3; animation-name: v7Cap4; }

    /* The count, in the display face, sitting on the frame's baseline to its right. */
    .seq__num {
      display: block; position: absolute;
      left: calc(var(--frame-pad) + var(--seq-size) + var(--space-5));
      top: calc(50% + var(--seq-size) / 2); transform: translateY(-100%);
      font-family: var(--font-display); text-transform: uppercase; font-weight: 700;
      font-size: clamp(6rem, 13vw, 12rem); line-height: .8; color: var(--ink);
    }
    .seq__num b { position: absolute; left: 0; bottom: 0; font-weight: inherit;
                  --lo: 0; --nudge: 0;
                  animation: linear both; animation-timeline: --seq;
                  animation-range: contain 0% contain 100%; }
    .seq__num b:nth-child(1) { position: relative; animation-name: v7Cap1; }
    .seq__num b:nth-child(2) { animation-name: v7Cap2; }
    .seq__num b:nth-child(3) { animation-name: v7Cap3; }
    .seq__num b:nth-child(4) { animation-name: v7Cap4; }

    /* Progress under the frame: a scale, not a width. */
    .seq__bar {
      display: block; position: absolute; left: var(--frame-pad);
      top: calc(50% + var(--seq-size) / 2 + var(--space-2));
      width: var(--seq-size); height: 2px; background: var(--ink);
      transform-origin: left center; transform: scaleX(0);
      animation: v7SeqBar linear both; animation-timeline: --seq;
      animation-range: contain 0% contain 100%;
    }
  }
}

@keyframes v7SeqIn  { from { opacity: 0; visibility: hidden; } to { opacity: 1; visibility: visible; } }
@keyframes v7SeqBar { from { transform: scaleX(0); } to { transform: scaleX(1); } }
/* --lo and --nudge let the big counter reuse the caption keyframes: a caption rests at .32
   and slides in, a numeral rests at 0 (they share one spot) and does not move. */
@keyframes v7Cap1 { 0%, 20% { opacity: 1; transform: translateX(var(--nudge, .75rem)); }
                    30%, 100% { opacity: var(--lo, .32); transform: none; } }
@keyframes v7Cap2 { 0%, 20% { opacity: var(--lo, .32); transform: none; }
                    30%, 45% { opacity: 1; transform: translateX(var(--nudge, .75rem)); }
                    55%, 100% { opacity: var(--lo, .32); transform: none; } }
@keyframes v7Cap3 { 0%, 45% { opacity: var(--lo, .32); transform: none; }
                    55%, 70% { opacity: 1; transform: translateX(var(--nudge, .75rem)); }
                    80%, 100% { opacity: var(--lo, .32); transform: none; } }
@keyframes v7Cap4 { 0%, 70% { opacity: var(--lo, .32); transform: none; }
                    80%, 100% { opacity: 1; transform: translateX(var(--nudge, .75rem)); } }


/* (Section 3, the hero print pass, was removed Sep 23 2026 at Julio's call — the outline lockup stays as v6 had it.) */


/* ------------------------------------------------------------ 4. Milo's slow blink */
/* Each frame is the portrait's own 700x700 canvas, transparent except for the lids, stacked on
   the photo with the SAME object-fit/position — so it crops exactly as the photo does at every
   size. (The crew photos render 427x700 on desktop, not square: the img's height="700"
   attribute outranks crt.css's aspect-ratio: 1. A box in percentages would miss the eyes.)
   One frame shows at a time; the jumps are
   0.01% wide, i.e. cuts, not fades — a blink is a few discrete frames, and crossfading two
   eyelid positions reads as ghosting. 9s cycle, the blink itself ~1s starting at 180ms, so
   it happens right as he arrives on screen and then only now and then. */

.crew__frame { position: relative; display: block; }
.blink { position: absolute; inset: 0; pointer-events: none; }
.blink img { position: absolute; inset: 0; width: 100%; height: 100%; opacity: 0;
             object-fit: cover; object-position: 50% 50%; }

@media (prefers-reduced-motion: no-preference) {
  .blink.is-live img { animation: 9s linear infinite; }
  .blink.is-live img:nth-child(1) { animation-name: v7BlinkQ; }
  .blink.is-live img:nth-child(2) { animation-name: v7BlinkH; }
  .blink.is-live img:nth-child(3) { animation-name: v7BlinkShut; }
}

@keyframes v7BlinkQ {
  0%, 2%       { opacity: 0; } 2.01%, 3.11%  { opacity: 1; } 3.12%, 10.44% { opacity: 0; }
  10.45%, 11.78% { opacity: 1; } 11.79%, 100% { opacity: 0; }
}
@keyframes v7BlinkH {
  0%, 3.11%    { opacity: 0; } 3.12%, 4.22%  { opacity: 1; } 4.23%, 9.11%  { opacity: 0; }
  9.12%, 10.44%  { opacity: 1; } 10.45%, 100% { opacity: 0; }
}
@keyframes v7BlinkShut {
  0%, 4.22%    { opacity: 0; } 4.23%, 9.11%  { opacity: 1; } 9.12%, 100%   { opacity: 0; }
}


/* ------------------------------------------------------------ 5. still tiles open a gallery */
/* The four non-case tiles were dead ends. They are now <button>s that open the case-study
   lightbox (js/case.js) with the tile's photo first and the project's other real frames after
   it. The plate says what you get — "N images +" — so a gallery can never be mistaken for a
   case study ("Case study ->"), which is still the only thing that leaves the page. */

.tile--gallery {
  font: inherit; color: inherit; text-align: left; border: 0; margin: 0; width: 100%;
  -webkit-appearance: none; appearance: none; cursor: zoom-in;
}
.tile--gallery:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; }
.tile--gallery:hover .tile__go,
.tile--gallery:focus-visible .tile__go { background: var(--green); color: var(--ink); }

/* Flat artwork on a transparent ground (the brochure spread, the shirt, the bag, the badge)
   disappears on the lightbox's dark backdrop. Give every frame the sheet it was made on;
   opaque photos cover it and are unaffected. */
.lb__img { background: var(--paper); }


/* ------------------------------------------------------------ 6. living tiles (ready, dormant) */
/* Any tile given data-loop="/img/work/<name>-loop.mp4" gets a short silent loop over its still
   (js/v7.js). The still stays as the poster and the fallback. Nothing downloads until intent:
   hover or keyboard focus on desktop, "mostly on screen" on touch. No tile carries data-loop
   yet — the clips come from Kling (see _dev/V7.md for the prompts). */

.tile__loop { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
              opacity: 0; pointer-events: none; }
.tile.is-looping .tile__loop { opacity: 1; }
@media (prefers-reduced-motion: no-preference) {
  .tile__loop { transition: opacity 280ms ease; }
}


/* ------------------------------------------------------------ 7. Milo at the counter */
/* The 3D Milo (Julio's milo.blend, rendered in Cycles) beside the contact form. Pre-rendered,
   not WebGL: the scene is ~830k triangles and ~350k strands of particle fur, which no web 3D
   format carries, and three.js alone would be three times the whole JS budget.
   Two images: the head, and a vertical sprite of 17 eye frames. The sprite sits in a window
   cut exactly over the eyes (percentages of the head, measured from the renders) and js/v7.js
   picks a frame by translating it — one transform, one element. Without JS, or with reduced
   motion, the window shows frame 7: level gaze, eyes open, identical to the head beneath. */

.milo { position: relative;
        /* the eye window, measured from the renders (build_assets.py) */
        --eye-l: 19.3314%; --eye-t: 40.6612%; --eye-w: 56.1047%; --eye-h: 16.1983%; }
.milo__head { display: block; width: 100%; height: auto; }
.milo__eyes { position: absolute; overflow: hidden;
              left: var(--eye-l); top: var(--eye-t); width: var(--eye-w); height: var(--eye-h); }
.milo__frames { display: block; width: 100%; height: auto;
                transform: translateY(calc(-100% * 7 / 17)); }

/* Desktop: the FAQ column ends well above the form's Send button, so Milo sits in that empty
   corner, level with the message box, and the form spans both rows beside him. */
@media (min-width: 1100px) {
  .form-wrap:has(> .milo) { grid-template-rows: auto 1fr; row-gap: var(--space-5); }
  .form-wrap:has(> .milo) > .contact-form { grid-row: 1 / span 2; }
  .form-wrap > .milo { grid-column: 2; grid-row: 2; align-self: end; justify-self: center;
                       width: min(100%, 21rem); }
}
/* Phones and tablets: one column. He sits small above the form, on the right, looking down
   at the first field — and follows focus as you move down it. */
@media (max-width: 1099px) {
  .form-wrap > .milo { order: -1; justify-self: end; width: 9.5rem;
                       margin-bottom: calc(var(--space-2) - var(--space-6)); }
}


/* ------------------------------------------------------------ 8. lightbox motion */
/* js/case.js animates the viewer the way the menu drawer moves. The dark shade moves from
   ::backdrop onto a real element (.lb__scrim) because a pseudo-element cannot be tweened; the
   dialog clips its own overflow so the sliding image never flashes a scrollbar. Without JS the
   lightbox cannot open at all, so there is no state in which the shade is missing. */

.lb { overflow: hidden; }
.lb::backdrop { background: transparent; }
.lb__scrim { position: absolute; inset: 0; background: rgba(14, 11, 7, .93); }
.lb__inner { position: relative; }


/* ------------------------------------------------------------ 9. poster-fit headlines */
/* Julio's call: the big statements should take up the space, not float in it. Each headline is
   set as a lockup — deliberate lines (.ln), a few of which only break on phones (.ln--m) — and
   sized so its WIDEST line fills the width of its section, at every screen size. Pure CSS
   (container units), so it lays out right on the first frame: no script, no jump.

     font-size = container width / (widest line's width in em)  ×  .97 slack

   --em / --em-m are those widths, measured in the browser in Cook Conthic 700 uppercase (see
   _dev/V7.md for the snippet). IF A HEADLINE'S WORDING CHANGES, RE-MEASURE ITS --em — the
   size is only right for the words it was measured on. --fit-cap stops a lockup outgrowing a
   short viewport. Browsers without container units keep the old clamp() sizes. */

.fit .ln { display: block; }
.fit .ln--m { display: inline; }

@supports (width: 1cqi) {
  .studio__lead, .door, .fold, .cs-fold { container-type: inline-size; }

  /* :is() carries the container class into the specificity (0,2,0), so this beats v6's
     `.studio__lead .title` clamp — without it the studio headline silently stayed at 72px. */
  :is(.studio__lead, .door, .fold, .cs-fold) .fit {
    font-size: min(calc(100cqi / var(--em) * .97), var(--fit-cap, 100svh));
  }

  .studio__lead .fit    { --em: 3.534; --fit-cap: 19svh; }            /* THE VISIBLE */
  .door .fit            { --em: 7.003; --em-m: 5.288; --fit-cap: 24svh; }  /* BRING US THE VISION — / OR THE PROBLEM. */
  .fold__title.fit      { --em: 7.703; --em-m: 3.799; --fit-cap: 24svh; }  /* EVERYTHING WE'VE BUILT. / WE'VE BUILT. */
  .cs-fold__title.fit   { --fit-cap: 26svh; line-height: .88; }
  .fit--sc              { --em: 5.255; --em-m: 4.781; }                /* SUNDAY MORNING / FROM VISION TO */
  .fit--oreida          { --em: 4.479; }                               /* BROUGHT BACK */
  .fit--vr              { --em: 4.029; }                               /* AGING OUT OF */

  /* The line under each statement grows with it, in the text face. */
  .studio__lead .sub { font-size: clamp(1.25rem, 5cqi, 2rem); }
  .door .sub, .fold__sub { font-size: clamp(1.25rem, 2.2cqi, 2.25rem); }
}

@media (max-width: 767px) {
  .fit .ln--m { display: block; }
  @supports (width: 1cqi) {
    :is(.studio__lead, .door, .fold, .cs-fold) .fit {
      font-size: min(calc(100cqi / var(--em-m, var(--em)) * .97), var(--fit-cap, 100svh));
    }
  }
}

/* Sublines break into even lines — "…the problem behind / it." left a one-word orphan. */
.studio__lead .sub, .door .sub, .fold__sub { text-wrap: balance; }

/* Less air around the bigger type. */
.door { padding-block: var(--space-6); }
.studio__body { padding-block: var(--space-7) var(--space-7); }


/* ------------------------------------------------------------ 10. phones (Sep 23 2026 audit) */
/* Julio's mobile audit. EVERYTHING in this section sits inside max-width: 767px (the one
   exception is hiding the drawer CTA above it), so desktop and tablet are untouched — verified
   by a layout fingerprint of every page at 1440 / 1024 / 800 taken before and after.
   Decisions, in his words: crew as roster rows; /work as a two-column grid; case galleries two
   across; a smaller Start-here button; lightbox swipe + close top-right + caption on its own
   line; menu as big full-width rows with a Start-a-project plate; "How we think" as a poster;
   a slimmer header; the Record ledger as one list; and the four clear fixes (16px fields,
   44px taps, 13px small text, rail arrows beside the counter). */

.site-nav__cta { display: none; }

@media (max-width: 767px) {

  /* ---- clear fixes ---------------------------------------------------------------------- */

  /* 16px or iOS Safari zooms the whole page in whenever a field is tapped. */
  .field input, .field select, .field textarea { font-size: 16px; }

  /* 11px labels up to 13px. */
  .tile__code, .tile__cat, .tile__go, .story__go, .crew__role, .quote__chip, .quote cite,
  .ledger dt, .door__no, .fold__no, .fold__meta, .cs-fold__no, .err__no, .field label,
  .cs-cell__cap, .cs-figure figcaption, .cs-next__label, .site-nav__meta, .foot-meta,
  .rail__count, .mv__head span:last-child, .game__readout, .game__state, .game__start,
  .fab__item { font-size: .8125rem; }
  .cue span, .more span, .err__links a, .lb__close, .lb__nav button,
  .site-nav__meta a { font-size: .8125rem; }
  .cs-cell__cap { letter-spacing: .06em; line-height: 1.35; }   /* two-up columns are narrow */

  /* 44px tap targets. Padding with an equal negative margin grows the hit area without moving
     anything; inline links only need the padding (it never affects an inline line box). */
  .rail__btn { width: 44px; height: 44px; }
  .social { gap: var(--space-1); margin-inline: -10px; }
  .social a { width: 44px; height: 44px; padding: 10px; box-sizing: border-box; }
  .foot-meta a, .ledger dd a { padding-block: 13px; }
  .site-nav__meta a { padding-block: 12px; }
  .cue { padding-block: 14px; margin-block: -14px; }
  .err__links a { padding-block: 14px; margin-block: -14px; }
  .site-header__mark { padding-block: 7px; margin-block: -7px; box-sizing: content-box; }
  .story__go { min-height: 44px; align-items: center; padding-inline: 14px; }

  /* Rail arrows beside the counter, on the left — the Start-here button lives bottom-right. */
  .rail__ctl { justify-content: flex-start; }

  /* ---- header, Start-here button --------------------------------------------------------- */

  /* 77px -> 61px. The menu button keeps its 44px; crt.js re-measures --header-h itself. */
  .site-header { padding-block: var(--space-1); }

  .fab, .fab__toggle { width: 44px; height: 44px; }
  .fab__toggle svg { width: 22px; height: 22px; }
  .fab__item { height: 44px; }

  /* ---- menu ------------------------------------------------------------------------------- */

  .site-nav { justify-content: center; gap: var(--space-5);
              padding: calc(var(--header-h) + var(--space-3)) var(--gutter) var(--space-5);
              overflow-y: auto; }
  .site-nav ul { gap: 0; }
  .site-nav li { border-bottom: 1px solid rgba(14, 11, 7, .18); }
  .site-nav ul a { display: flex; align-items: center; min-height: 56px; font-size: 2rem; }
  /* The round Start-here button sat on top of the open menu (it is z-index 140, the drawer 120);
     the drawer carries its own Start-a-project plate now. */
  body.nav-open .fab { opacity: 0; visibility: hidden; pointer-events: none; }
  .site-nav .site-nav__cta { display: block; border-bottom: 0; margin-top: var(--space-4); }
  .site-nav__cta a { justify-content: center; background: var(--ink); color: var(--green);
                     font-size: 1.625rem; }
  .site-nav__cta a:hover, .site-nav__cta a:focus-visible { text-decoration: none;
                                                           outline-color: var(--ink); }

  /* ---- homepage ------------------------------------------------------------------------------ */

  /* "How we think" bigger on phones, at ONE size (Julio: mixed sizes read wrong). The size is
     the one at which the longest phrase — SOMETIMES IT'S A MERCH DROP. — exactly fills the
     width, so the first two lines run edge to edge and the last phrase wraps naturally.
     --em measured like §9; re-measure if the words change. */
  @supports (width: 1cqi) {
    .pin__copy { container-type: inline-size; }
    .pin__line { font-size: calc(100cqi / 9.635 * .97); line-height: 1; text-wrap: balance; }
    .pin__line + .pin__line { margin-top: .2em; }
  }

  /* Crew as a roster: square photo, name and role beside it. The photos were 700px tall on a
     phone (the img's height="700" beat crt.css's aspect-ratio); height:auto restores the square.
     Milo's blink frames use the same object-fit, so they still land on his eyes. */
  .crew { display: flex; flex-direction: column; gap: var(--space-3); }
  .crew__member { display: grid; grid-template-columns: 5.5rem minmax(0, 1fr);
                  column-gap: var(--space-3); row-gap: 2px; align-items: center; }
  .crew__member > :first-child { grid-row: 1 / span 2; }
  .crew__photo { height: auto; aspect-ratio: 1; }
  .crew__name { align-self: end; }
  .crew__role { align-self: start; }
  .studio__body { padding-bottom: var(--space-4); }
  .crew { margin-top: var(--space-3); }                 /* was 64px on top of the section padding */
  .studio .wrap--pad-8 { padding-bottom: var(--space-6); }

  /* The Record ledger as one list: one title, the Terms rows running straight on, and the
     "Say hello" email dropped — the form is directly below it and the footer repeats it. */
  #record .ledger { gap: 0; }
  #record .ledger__col + .ledger__col .ledger__title { display: none; }
  #record .ledger__col + .ledger__col dl > div:last-child { display: none; }
  #record .ledger dl > div { padding-block: var(--space-2); }

  /* Milo below the form on phones, before the FAQs (Julio's call) — on the right, under the
     message box, looking up at the fields. §7 still puts him above the form from 768px to
     1099px, so tablets are unchanged. */
  .form-wrap > .milo { order: 0; margin-block: calc(var(--space-3) - var(--space-6)) calc(var(--space-2) - var(--space-6)); }
  .form-wrap > .faq { order: 1; }

  /* ---- /work: the full index as a two-column grid, not a rail ------------------------------ */
  .grid--index { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
                 overflow: visible; scroll-snap-type: none; }
  .grid--index .tile { min-width: 0; }

  /* ---- case studies: galleries two across ------------------------------------------------- */
  .cs-grid, .cs-grid--pair { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-3) var(--space-2); }

  /* ---- lightbox ------------------------------------------------------------------------- */
  .lb__inner { padding-top: calc(44px + var(--space-4)); }
  .lb__img { touch-action: pan-y pinch-zoom; }       /* sideways drags reach js/case.js */
  .lb__bar { flex-wrap: wrap; row-gap: var(--space-2); }
  .lb__cap { flex: 1 0 100%; }
  .lb__nav button { min-width: 44px; min-height: 44px; }
  .lb__close { position: absolute; top: var(--space-3); right: var(--space-3);
               min-height: 44px; }
}
