/* Knead premium layer for the HOMEPAGE ONLY (premium pass 1 + 2 and the oil-drop intro; Phat 2026-11-05: "let's
   swap it out to make the home-orginal the real home page now"). Specs: docs/superpowers/specs/2026-11-05-knead-
   premium-pass-design.md, 2026-11-05-knead-premium-pass-2-design.md, 2026-11-05-knead-home-a-foldin-design.md.
   Loaded right after /css/styles.css by site/index.html alone. Every selector starts with
   html[data-look="premium"] (site-docs/tests/home.test.mjs checks), so the file is inert anywhere else. Same world,
   same tokens: lacquer ground, ivory ink, amber for Book only, one blush thread. Carrying it to every page later =
   a separate decision (fold-in spec 12 Q2; it would change Option B, /lab/home-warm/). */

/* ---------- the thread: one line, one strength (spec 8.4) ----------
   styles.css draws it per section on .sec .wrap::before with a 200px overhang each way, so neighbours
   double it where they overlap. Here each block draws exactly its own box: no overlaps, no gaps. */
html[data-look="premium"] .sec .wrap::before { display: none; }
html[data-look="premium"] .sec::before,
html[data-look="premium"] .interlude::before,
html[data-look="premium"] .wave-div::before {
  content: ""; position: absolute; top: 0; bottom: 0; z-index: 0; pointer-events: none;
  left: calc(max(0px, (100% - var(--max)) / 2) + var(--pad));
  width: 1px; background: var(--rule);
}
/* the line still ends at the last studio row (styles.css: .sec:last-of-type .wrap::before { bottom: 0 }) */
html[data-look="premium"] .sec:last-of-type::before { bottom: clamp(64px, 7vw, 104px); }

/* ---------- P1-1 hero: the headline holds the wider field ---------- */
@media (min-width: 1600px) {
  html[data-look="premium"] .hero-copy h1 { font-size: clamp(5rem, 4.6vw, 6.25rem); }
}

/* ---------- iPhone fixes 2026-11-06 (spec 1 + 2 + 12.1 #3/#8): Phat "the home hero seems to have gotten zoomed in" ---
   Below 900 .hero-media spanned the hero AND the stations under it (~1580px at 390x844), so the clip was scaled to
   cover a box whose lower half sits under the stations' solid ground: ~44% of its width showed. It now fills the
   first screen only (82% of the width at 390x844). Portrait only: landscape phones keep their rules. vh first for
   browsers without svh. Option B (data-look="warm") relies on the tall box and is untouched. */
@media (max-width: 899px) and (min-height: 521px) {
  html[data-look="premium"] .hero:not(.sh) .hero-media { bottom: auto; height: 100vh; height: 100svh; }
}
/* ---------- hero copy 2026-11-06 (spec 2026-11-06-knead-hero-copy-design.md 2.2): no shade on phones ----------
   Phat "for the home, on mobile: i think we should remove the shade after all because it creates a weird divider
   between the whole page, and it doesn't blend well". The copy-zone layer and the whole-box scrim are off on the
   homepage hero (portrait AND landscape phones: "on mobile"); the footage keeps its uniform .66 grade (no edge), and
   a text-shadow on the type is the only legibility aid, at its cap (0 1px 2px .8, 0 0 24px .7; no layer comes back).
   Measured on the brightest frame (t = 2.536 s) it does NOT reach AA: h1 ~2.5-3.0, "kneads." ~1.0-1.3, lede ~2.0-2.9
   (hero-copy BUILD-REPORT.md); Phat ruled 2026-11-06: accept the no-shade phone legibility as is (spec 8 Q1). */
@media (max-width: 699px) and (min-height: 521px) {
  html[data-look="premium"] .hero:not(.sh) > .wrap::before { content: none; }
  html[data-look="premium"] .hero:not(.sh) .hero-media::after { background: none; }
  html[data-look="premium"] .hero:not(.sh) .hero-copy h1,
  html[data-look="premium"] .hero:not(.sh) .hero-copy .lede,
  html[data-look="premium"] .hero:not(.sh) .hero-copy .sh-meta,
  html[data-look="premium"] .hero:not(.sh) .hero-copy .sh-kicker { text-shadow: 0 1px 2px rgba(26, 25, 21, .8), 0 0 24px rgba(26, 25, 21, .7); }
}
/* landscape phones are mobile too: styles.css's phone scrim (.hero-media::after) and copy-zone layer off here as well */
@media (max-width: 699px) and (max-height: 520px) {
  html[data-look="premium"] .hero:not(.sh) > .wrap::before { content: none; }
  html[data-look="premium"] .hero:not(.sh) .hero-media::after { background: none; }
  html[data-look="premium"] .hero:not(.sh) .hero-copy h1,
  html[data-look="premium"] .hero:not(.sh) .hero-copy .lede { text-shadow: 0 1px 2px rgba(26, 25, 21, .8), 0 0 24px rgba(26, 25, 21, .7); }
}
/* ---------- hero life fix round 2026-11-06 (spec 10; Phat's banner: phones = "B + Linen kneads") ----------
   Phones, portrait and the landscape phone layout: a soft glow behind the copy only (one radial gradient, no straight
   edge: its left edge is the screen's, its bottom the hero's own fold where the stations' solid ground starts; it fades
   to nothing before the top and the right), and the h1 accent "kneads." in full Linen (desktop keeps the taupe italic).
   Not the band Phat removed ("a weird divider between the whole page"). Alpha and shape tuned on the brightest frames
   (t = 2.536 s / 2.469 s) so h1, "kneads.", the facts line and the lede hold AA: BUILD-REPORT.md section 7. */
@media (max-width: 699px), (max-width: 999px) and (max-height: 520px) {
  html[data-look="premium"] .hero:not(.sh) .hero-copy { position: relative; }
  html[data-look="premium"] .hero:not(.sh) .hero-copy::before {
    content: ""; position: absolute; z-index: -1; pointer-events: none;
    inset: -96px calc(-1 * var(--pad)) calc(-42px - env(safe-area-inset-bottom)) calc(-1 * var(--pad));
    background: radial-gradient(ellipse 95% 64% at 38% 66%, rgba(26, 25, 21, .70) 0, rgba(26, 25, 21, .62) 40%, rgba(26, 25, 21, .3) 74%, rgba(26, 25, 21, 0) 100%);
  }
  html[data-look="premium"] .hero:not(.sh) .hero-copy h1 em { color: var(--ivory); }
  /* hero edges #L1 (Phat 2026-11-06): the kicker on phones, one line down to 360 wide */
  html[data-look="premium"] .hero:not(.sh) .sh-kicker { letter-spacing: .18em; }
}
/* hero edges #L1: where phones show the kicker (portrait, 600px+ tall; styles.css), it sits at the top of the glow over
   the clip's brightest skin: --kicker #C8A07A measured 2.30-2.38 there on the bright frame (t = 2.5 s), and even a
   denser glow left it under 4.5. So a second soft ellipse (no edge) sits behind the kicker inside the same glow box,
   and the phone kicker is a lighter gold: 5.04-5.35 at 360x780, 375x667, 390x844, 414x896 (h1 rises to 7.7-8.5). */
@media (max-width: 699px) and (min-height: 600px) {
  html[data-look="premium"] .hero:not(.sh) .hero-copy::before {
    background:
      radial-gradient(ellipse 230px 64px at calc(var(--pad) + 150px) 104px, rgba(26, 25, 21, .45) 0, rgba(26, 25, 21, .30) 55%, rgba(26, 25, 21, 0) 100%),
      radial-gradient(ellipse 95% 64% at 38% 66%, rgba(26, 25, 21, .70) 0, rgba(26, 25, 21, .62) 40%, rgba(26, 25, 21, .3) 74%, rgba(26, 25, 21, 0) 100%);
  }
  html[data-look="premium"] .hero:not(.sh) .hero-copy .sh-kicker { color: #E2C4A2; }
}
/* narrow phones (320, Display Zoom): "Tune into what / your body kneads." keeps two lines, so the golden split holds
   (spec 1.3: 320x568 reach 56.8% -> 47.7%); the lede has no override here (hero-brand.test.mjs) */
@media (max-width: 359px) {
  html[data-look="premium"] .hero:not(.sh) .hero-copy h1 { font-size: 2.5rem; }
}
/* ---------- hero copy 2026-11-06 (spec 2.3 + 2.4): a lighter desktop shade ----------
   Phat "on desktop ... the shade is quite overwhelming the hero video as well, so let's reduce or remove that": a
   shorter, lighter left fade and a lighter grade (.66 -> .78); the nav band is unchanged (#28). The bottom layer is
   today's exactly (spec 2.3 stop rule): lighter steps left the stations note ("Explore a studio...", 4.5) at 2.84 one
   step short of today's at 1024x768 on the brightest frame (t = 2.50 s). Everything >= 700px wide
   (desktop, tablets, landscape phones 700-999). The wide intro mirrors these values byte for byte, so the dissolve
   changes the film, not the light; the tall intro keeps the original grade (below, #P1). */
@media (min-width: 700px) {
  html[data-look="premium"] .hero:not(.sh) .hero-media video,
  html[data-look="premium"] .hero:not(.sh) .hero-media img { filter: brightness(.78) contrast(1.06) saturate(.94); }
  /* hero life 2026-11-06 (spec 2.2): the nav band (.60/.40 90px/0 260px) is gone: the homepage nav is tucked at the
     top and the solid bar whenever shown, so nothing reads over the top of the clip any more */
  html[data-look="premium"] .hero:not(.sh) .hero-media::after {
    background:
      linear-gradient(to top, rgba(35, 33, 28, .96) 0%, rgba(35, 33, 28, .74) 30%, rgba(35, 33, 28, .16) 62%, rgba(35, 33, 28, .20) 100%),
      linear-gradient(to right, rgba(35, 33, 28, .56) 0%, rgba(35, 33, 28, .30) 34%, rgba(35, 33, 28, 0) 58%);
  }
  html[data-look="premium"]:not(.intro-tall) .kn-intro-vid { filter: brightness(.78) contrast(1.06) saturate(.94); }
  html[data-look="premium"]:not(.intro-tall) .kn-intro-media::after {
    background:
      linear-gradient(to top, rgba(35, 33, 28, .96) 0%, rgba(35, 33, 28, .74) 30%, rgba(35, 33, 28, .16) 62%, rgba(35, 33, 28, .20) 100%),
      linear-gradient(to right, rgba(35, 33, 28, .56) 0%, rgba(35, 33, 28, .30) 34%, rgba(35, 33, 28, 0) 58%);
  }
}
/* ---------- hero edges 2026-11-06 (#L2, Phat: "the blur/shadow/shader effect ... kills the hero video ... clean it up
   or remove it from the hero video for both homepage's"; banner pick (a), mocks in
   site-docs/screenshots/2026-11-06-hero-edges/mocks/) ----------
   Desktop 1000px+: no blur-fill. The homepage's sharp portrait cut (home-tall, --rendition read by script.js) fills the
   right 64% to the screen edge and fades into the page colour over a long mask (transparent at its left edge, full by
   58% of it), so no edge reads anywhere: the first build's 42% box with a 72px feather read as a divider (Phat on
   6aee73c3: "the divider is not what we're going for, we just took a step backwards"; banner "Long blend", mocks in
   site-docs/screenshots/2026-11-06-hero-edges/blend-mocks/). The left shade is gone; the bottom fade into the stations
   is the 700px block's own layer, unchanged. Tablets (700-999) keep the wide clip and the block above. Mount Pleasant
   shares the <video> tag but is .sh. */
@media (min-width: 1000px) {
  html[data-look="premium"] .hero:not(.sh) .hero-media { background: var(--lacquer); }
  html[data-look="premium"] .hero:not(.sh) .hero-media video {
    --rendition: tall;
    position: absolute; top: 0; right: 0; left: auto; width: 64%; height: 100%;
    object-fit: cover; object-position: center 40%;
    -webkit-mask-image: linear-gradient(to right, transparent 0, rgba(0, 0, 0, .25) 22%, rgba(0, 0, 0, .7) 40%, #000 58%);
    mask-image: linear-gradient(to right, transparent 0, rgba(0, 0, 0, .25) 22%, rgba(0, 0, 0, .7) 40%, #000 58%);
  }
  html[data-look="premium"] .hero:not(.sh) .hero-media::after {
    background: linear-gradient(to top, rgba(35, 33, 28, .96) 0%, rgba(35, 33, 28, .74) 30%, rgba(35, 33, 28, .16) 62%, rgba(35, 33, 28, .20) 100%);
  }
}

/* ---------- hero life 2026-11-06 (spec docs/superpowers/specs/2026-11-06-knead-hero-life-design.md 2 + 9) ----------
   The nav: Phat "dropping the menu when the page first lands so it blends into the video / the menu can transition in
   on the first scroll". The head gate puts .nav-tuck on <html> (no hash); premium.js removes it for good past 24px, on
   focus inside the nav, or if it starts too late. Tucked = opacity 0 + no pointer events ONLY: no visibility/display
   (the links stay in the tab order; :focus-within and .open show it) and no transform (a transformed .nav would become
   the containing block of the fixed phone sheet .nav-links and collapse it to the bar; review S2). Shown, the homepage
   nav is always the solid bar, so nav text never sits on the clip. Failsafe: shown at 4s if premium.js never takes over
   (.nav-js), pointer events included. */
html[data-look="premium"] .nav { background: var(--lacquer); border-bottom: 1px solid var(--rule-2); transition: opacity .5s var(--ease); }
html[data-look="premium"].nav-tuck .nav:not(.open):not(:focus-within) { opacity: 0; pointer-events: none; }
html[data-look="premium"].nav-tuck:not(.nav-js) .nav { animation: kn-nav-show 0s 4s forwards; }
@keyframes kn-nav-show { to { opacity: 1; pointer-events: auto; } }
/* The hero, after the Marpole studio hero (Phat "let's give the hero text some more life, let's make it similar to
   marpole's"): the studio heroes' own kicker + facts line (styles.css .sh-kicker / .sh-meta). The kicker is a gold caps
   line in the brand guide's Ember family (Ember #896247's hue, lifted; Marpole's brass is that look's, not the guide's);
   the facts dots share it. Phones show no kicker and the facts line only on tall screens (styles.css, review R1). */
html[data-look="premium"] { --kicker: #C8A07A; }
html[data-look="premium"] .hero:not(.sh) .sh-kicker { color: var(--kicker); letter-spacing: .24em; }
html[data-look="premium"] .hero:not(.sh) .sh-meta > * + *::before { color: var(--kicker); }

/* ---------- P1-6 signature motion: the line bundle sweeps in, each studio's dot lands as it arrives ----------
   A clip-path sweep, not a dash-draw: the paths are non-scaling strokes in a preserveAspectRatio="none" SVG,
   so dash lengths would be wrong on screen. It plays only when the block is in view (spec 8.3, code review
   2026-11-05 #1): premium.js adds .draw at once when the stations are in the first screen, otherwise marks
   them .sweep-wait and adds .draw from an IntersectionObserver as they approach. Only premium.js ever hides
   the line (.sweep-wait), and only off-screen, so without JS, if premium.js fails, or under reduced motion
   the line is static and fully drawn. */
html[data-look="premium"] .stations.sweep-wait .line { clip-path: inset(0 100% 0 0); }
html[data-look="premium"] .stations.sweep-wait .st::before { opacity: 0; }
html[data-look="premium"] .stations.draw .line { animation: kn-sweep 1.6s cubic-bezier(.45, .05, .25, 1) var(--sweep-delay, .35s) both; }
html[data-look="premium"] .stations.draw .st::before { animation: kn-dot .55s var(--ease) both; animation-delay: var(--dot-delay, .7s); }
html[data-look="premium"] .stations.draw .st:nth-child(2)::before { animation-delay: var(--dot-delay, 1.05s); }
html[data-look="premium"] .stations.draw .st:nth-child(3)::before { animation-delay: var(--dot-delay, 1.45s); }
@keyframes kn-sweep { from { clip-path: inset(0 100% 0 0); } to { clip-path: inset(0 0 0 0); } }
@keyframes kn-dot { from { opacity: 0; transform: scale(.35); } }

/* station hover / focus: the arrow leans toward the studio, the dot fills */
html[data-look="premium"] .st .studio-link span { display: inline-block; transition: transform .4s var(--ease); }
html[data-look="premium"] .st:hover .studio-link span,
html[data-look="premium"] .st:focus-within .studio-link span { transform: translateX(4px); }
html[data-look="premium"] .st::before { transition: background-color .35s var(--ease), box-shadow .35s var(--ease); }
html[data-look="premium"] .st:hover::before,
html[data-look="premium"] .st:focus-within::before { background: var(--blush); box-shadow: inset 0 0 0 0 var(--lacquer), inset 0 0 0 0 var(--blush); }

/* ---------- P1-2 one line under each studio number; studio names read as titles ---------- */
html[data-look="premium"] .st .call,
html[data-look="premium"] .bookat .call { text-decoration: none; font-variant-numeric: tabular-nums lining-nums; }
html[data-look="premium"] .bookat h3 a {
  text-decoration: none; padding-bottom: 3px;
  background: linear-gradient(var(--blush), var(--blush)) 0 100% / 0 1px no-repeat;
  transition: background-size .45s var(--ease), color .25s var(--ease);
}
html[data-look="premium"] .bookat h3 a:hover,
html[data-look="premium"] .bookat h3 a:focus-visible { background-size: 100% 1px; }

/* ---------- P2-3 (two rules): the treatment links' underline stops at the words; the arrow leans on hover ---------- */
html[data-look="premium"] .board-layout .treatment-link span { display: inline-block; transition: transform .35s var(--ease); }
html[data-look="premium"] .board-layout .treatment-link:hover span { transform: translateX(3px); }

/* ---------- P1-7 the Buccal: one heading pair, figures at display size, no dead band ---------- */
html[data-look="premium"] .spot { padding-bottom: clamp(40px, 4.4vw, 64px); }
html[data-look="premium"] .spot .grid { border-block-start: 0; padding-block: clamp(20px, 3vw, 36px); }
html[data-look="premium"] .spot .prices { gap: clamp(36px, 4vw, 56px); padding-block: 20px 18px; }
html[data-look="premium"] .spot .prices b { font-size: clamp(2.25rem, 3.2vw, 2.9rem); margin-bottom: 8px; }

/* ---------- P1-5 one photographic interlude (between memberships and reviews) ----------
   No padding of its own: it sits between the two sections' own padding. Starts at the content inset,
   the left edge every heading and row shares; the thread runs past it on its own x. */
html[data-look="premium"] .interlude { position: relative; margin: 0; }
html[data-look="premium"] .interlude .wrap { padding-left: calc(var(--pad) + var(--inset)); }
html[data-look="premium"] .interlude picture { position: relative; display: block; overflow: hidden; background: var(--lacquer-2); }
html[data-look="premium"] .interlude picture::after { content: ""; position: absolute; inset: 0; box-shadow: inset 0 0 0 1px var(--rule-2); pointer-events: none; }
html[data-look="premium"] .interlude img { width: 100%; height: auto; aspect-ratio: 2 / 1; object-fit: cover; }
html[data-look="premium"] .interlude.scale img { transform: scale(1.05); transition: transform 2.4s var(--ease); }
html[data-look="premium"] .interlude.scale.on img { transform: none; }
@media (max-width: 699px) {
  html[data-look="premium"] .interlude img { aspect-ratio: 4 / 5; }
}

/* ---------- owner photo band (Phat 2026-11-08): the owner's face-massage photo, full bleed, after the FAQ ----------
   Edge to edge (no .wrap), so it is the one place the thread breaks: a held breath before the three calendars.
   12:5 on wide screens, 4:5 on phones. Static: no drift, nothing to reduce. */
html[data-look="premium"] .band { position: relative; margin: 0; background: var(--lacquer-2); }
html[data-look="premium"] .band picture { display: block; overflow: hidden; }
html[data-look="premium"] .band img { display: block; width: 100%; height: auto; aspect-ratio: 12 / 5; object-fit: cover; }
@media (max-width: 699px) {
  html[data-look="premium"] .band img { aspect-ratio: 4 / 5; }
}
/* owner round 3 (2026-11-09): the band left the homepage (its rules above are unused, kept for a possible return; its
   two files were deleted 2026-11-11 when the owner asked for the face photo to be replaced).
   The Combo spotlight's photo is still-owner-combo-hands.jpg since 2026-11-11 (two hands flat on a lower back); its
   phone crop is 3:2, and this position keeps both hands and the line of the back in that window. */
html[data-look="premium"] .spot-combo .spot-fig img { object-position: 50% 35%; }
/* The membership spotlight (2026-11-11): still-owner-member-tee.jpg; this position keeps the T-shirt's words, the
   "A quiet space" line and both glowing wall lights in the 3:2 phone window. */
html[data-look="premium"] .spot-member .spot-fig img { object-position: 50% 40%; }

/* ---------- P1-4 the phone menu: a full sheet, set in Caslon, with each studio's number ---------- */
html[data-look="premium"] .menu-studios { display: none; }
@media (max-width: 899px) {
  html[data-look="premium"] .nav-links {
    bottom: 0; overflow-y: auto; overscroll-behavior: contain; border-bottom: 0;
    padding: clamp(12px, 3vh, 28px) var(--pad) calc(32px + env(safe-area-inset-bottom));
  }
  html[data-look="premium"] .nav-links > a {
    font-family: var(--display); font-size: 1.6rem; line-height: 1.2; letter-spacing: 0;
    color: var(--ivory); padding: 18px 0;
    opacity: 0; transform: translateY(10px);
    transition: opacity .45s var(--ease), transform .6s var(--ease), color .25s var(--ease);
  }
  html[data-look="premium"] .nav-links > a.on { color: var(--blush); }
  /* owner round 2026-11-07 #1b: Treatments is a link + chevron row (.nav-drop), set like the sheet's other links */
  html[data-look="premium"] .nav-drop { opacity: 0; transform: translateY(10px); transition: opacity .45s var(--ease), transform .6s var(--ease); }
  html[data-look="premium"] .nav.open .nav-drop { opacity: 1; transform: none; transition-delay: .04s; }
  html[data-look="premium"] .nav-drop > a { font-family: var(--display); font-size: 1.6rem; line-height: 1.2; letter-spacing: 0; color: var(--ivory); padding: 18px 0; }
  html[data-look="premium"] .nav-drop > a.on { color: var(--blush); }
  html[data-look="premium"] .menu-studios {
    display: block; margin-top: 40px;
    opacity: 0; transform: translateY(10px); transition: opacity .45s var(--ease), transform .6s var(--ease);
  }
  html[data-look="premium"] .nav.open .nav-links > a,
  html[data-look="premium"] .nav.open .menu-studios { opacity: 1; transform: none; }
  html[data-look="premium"] .nav.open .nav-links > a:nth-child(1) { transition-delay: .04s; }
  html[data-look="premium"] .nav.open .nav-links > a:nth-child(2) { transition-delay: .08s; }
  html[data-look="premium"] .nav.open .nav-links > a:nth-child(3) { transition-delay: .12s; }
  html[data-look="premium"] .nav.open .nav-links > a:nth-child(4) { transition-delay: .16s; }
  html[data-look="premium"] .nav.open .nav-links > a:nth-child(5) { transition-delay: .2s; }
  html[data-look="premium"] .nav.open .menu-studios { transition-delay: .26s; }
  html[data-look="premium"] .menu-studios p {
    font-size: .78rem; letter-spacing: .2em; text-transform: uppercase; color: var(--ivory-2);
    padding-bottom: 10px; border-bottom: 1px solid var(--rule);
  }
  html[data-look="premium"] .nav-links .menu-studios a {
    display: flex; justify-content: space-between; align-items: baseline; gap: 16px;
    padding: 15px 0; font-size: 1rem; color: var(--ivory);
  }
  html[data-look="premium"] .menu-studios b { font-family: var(--display); font-weight: 400; font-size: 1.2rem; }
  html[data-look="premium"] .menu-studios span { color: var(--ivory-2); font-variant-numeric: tabular-nums lining-nums; }
  /* open: the page behind cannot scroll (html AND body, for iOS) and the sticky Book/Call bar steps aside,
     so the sheet never half-covers it (premium.js sets .menu-open and makes the page behind inert) */
  html[data-look="premium"].menu-open,
  html[data-look="premium"].menu-open body { overflow: hidden; }
  html[data-look="premium"].menu-open .stick,
  html[data-look="premium"].menu-open .call-sheet { visibility: hidden; }
}

/* ---------- #P1 the oil-drop intro (spec docs/superpowers/specs/2026-11-05-knead-oil-drop-intro-design.md) ----------
   The head gate (stage.mjs) puts .intro-on (+ .intro-tall at max-width 699px, the hero's own tall rule) on <html>
   before the first paint, once per session; premium.js runs it and adds .intro-js when it takes over. Without
   .intro-on the overlay is display:none (and the markup carries `hidden`, so it stays hidden if this stylesheet
   never loads), so its posters are never fetched. The wide film mirrors the homepage hero's look-premium values
   (hero-copy spec 2.3, the min-width 700px block above), so the dissolve changes the film, not the light (spec 7.3);
   the tall film keeps the original grade (the filter and scrim below). */
html[data-look="premium"] .kn-intro { display: none; }
html[data-look="premium"].intro-on .kn-intro,
html[data-look="premium"].intro-out .kn-intro {
  display: block; position: fixed; inset: 0; z-index: 1000; overflow: hidden; background-color: var(--lacquer-deep);
}
/* desktop: premium.js places this box on .hero-media's rect (--kn-top / --kn-h); until then, the viewport */
html[data-look="premium"] .kn-intro-media { position: absolute; left: 0; right: 0; top: var(--kn-top, 0px); height: var(--kn-h, 100%); }
html[data-look="premium"] .kn-intro-vid {
  display: block; width: 100%; height: 100%; object-fit: cover; object-position: center 40%;
  background: var(--lacquer-deep) url("/img/intro-wide-poster.jpg") center 40% / cover no-repeat;
  filter: brightness(.66) contrast(1.06) saturate(.92);
}
html[data-look="premium"] .kn-intro-media::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background:
    linear-gradient(to bottom, rgba(35, 33, 28, .60) 0, rgba(35, 33, 28, .40) 90px, rgba(35, 33, 28, 0) 260px),
    linear-gradient(to top, rgba(35, 33, 28, .96) 0%, rgba(35, 33, 28, .74) 30%, rgba(35, 33, 28, .16) 62%, rgba(35, 33, 28, .20) 100%),
    linear-gradient(to right, rgba(35, 33, 28, .74) 0%, rgba(35, 33, 28, .48) 38%, rgba(35, 33, 28, 0) 66%);
}
/* tall: the full portrait frame fills the screen; the left-to-right gradient (for the desktop copy column)
   would sit on the drops' fall line, so it is left out (spec 7.3) */
html[data-look="premium"].intro-tall .kn-intro-media { top: 0; height: 100%; }
html[data-look="premium"].intro-tall .kn-intro-vid { object-position: center; background-image: url("/img/intro-tall-poster.jpg"); background-position: center; }
html[data-look="premium"].intro-tall .kn-intro-media::after {
  background:
    linear-gradient(to bottom, rgba(35, 33, 28, .60) 0, rgba(35, 33, 28, .40) 90px, rgba(35, 33, 28, 0) 260px),
    linear-gradient(to top, rgba(35, 33, 28, .96) 0%, rgba(35, 33, 28, .74) 30%, rgba(35, 33, 28, .16) 62%, rgba(35, 33, 28, .20) 100%);
}
/* the line: on desktop exactly where the H1 will be (premium.js copies its box and type size); on the tall
   clip below where the drops land, on the dark back */
html[data-look="premium"] .kn-intro-line {
  position: absolute; z-index: 1; margin: 0; left: var(--kn-x, var(--pad)); top: var(--kn-y, 40%);
  font-family: var(--display); font-weight: 400; font-size: var(--kn-fs, clamp(2.75rem, 5.6vw, 5rem));
  line-height: var(--kn-lh, .98); letter-spacing: -.015em; color: var(--ivory); white-space: nowrap;
}
html[data-look="premium"].intro-tall .kn-intro-line { left: var(--pad); right: var(--pad); top: 66svh; white-space: normal; }
/* wide: hidden until premium.js has placed it on the H1 (just after the first paint), so placing it is not a
   layout shift. Tall: its CSS spot is final (placing only copies the H1 size, the same clamp), so it shows. */
html[data-look="premium"]:not(.intro-tall) .kn-intro:not(.placed) .kn-intro-line { visibility: hidden; }
html[data-look="premium"] .kn-intro-line em { font-style: italic; color: var(--blush); }
/* each word lands on its drop's impact frame: a quick fade (so it is there while the drop is) + the one settle */
html[data-look="premium"] .kn-intro .w { display: inline-block; opacity: 0; transform: translateY(14px); transition: opacity .28s var(--ease), transform .5s var(--ease); }
html[data-look="premium"] .kn-intro .w.on { opacity: 1; transform: none; }
/* the cut: the film dissolves onto the hero (already on frame 54, the hands); the line lifts .3s later */
html[data-look="premium"].intro-out .kn-intro { background-color: transparent; transition: background-color .7s cubic-bezier(.65, 0, .35, 1); }
html[data-look="premium"].intro-out .kn-intro-media { opacity: 0; transition: opacity .7s cubic-bezier(.65, 0, .35, 1); }
html[data-look="premium"] .kn-intro.lift .kn-intro-line { opacity: 0; transform: translateY(-14px); transition: opacity .45s var(--ease), transform .45s var(--ease); }
/* from the lift on, the first screen is coming in: taps go to the page, not the dissolving overlay (code review #4).
   A skip's fade (.quick) keeps them, so the skipping tap cannot click through (spec 7.7). */
html[data-look="premium"] .kn-intro.lift { pointer-events: none; }
/* a skip, a stall, a refusal or the deadline: everything fades together, fast */
html[data-look="premium"] .kn-intro.quick { opacity: 0; transition: opacity .3s ease; }
/* the first screen waits under the veil, then settles in as the line lifts (spec 7.14) */
html[data-look="premium"].intro-on main > section:first-of-type .in,
html[data-look="premium"].intro-out main > section:first-of-type .in { animation: none; opacity: 0; }
html[data-look="premium"].intro-done main > section:first-of-type .in { opacity: 1; animation: kn-in 1.1s var(--ease) both; }
html[data-look="premium"].intro-done main > section:first-of-type .in:nth-child(2) { animation-delay: .08s; }
html[data-look="premium"].intro-done main > section:first-of-type .in:nth-child(3) { animation-delay: .16s; }
html[data-look="premium"].intro-done main > section:first-of-type .in:nth-child(4) { animation-delay: .24s; }
/* hero life (review N1): kicker + facts made the CTA row the 5th child and the price strip the 6th */
html[data-look="premium"].intro-done main > section:first-of-type .hero-copy > .in:nth-child(5) { animation-delay: .32s; }
html[data-look="premium"].intro-done main > section:first-of-type .hero-copy > .in:nth-child(6) { animation-delay: .4s; }
@keyframes kn-in { from { opacity: 0; transform: translateY(14px); } }
/* failsafe: if premium.js never takes over (.intro-js), the veil clears itself and the page shows, 3s after
   the first paint */
html[data-look="premium"].intro-on:not(.intro-js) .kn-intro { animation: kn-veil-off 0s 3s forwards; }
html[data-look="premium"].intro-on:not(.intro-js) main > section:first-of-type .in { animation: kn-show 0s 3s forwards; }
@keyframes kn-veil-off { to { opacity: 0; visibility: hidden; } }
@keyframes kn-show { to { opacity: 1; } }
@media print {
  html[data-look="premium"] .kn-intro { display: none !important; }
}

/* ---------- premium pass 2 (spec 2026-11-05-knead-premium-pass-2-design.md; section 13 overrides) ---------- */
/* P2-1 lead testimonial: the first of the same three quotes, larger, beside the other two stacked (13.3) */
@media (min-width: 900px) {
  html[data-look="premium"] .words ol { grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr); grid-template-rows: auto auto; align-items: start; }
  html[data-look="premium"] .words ol > li:first-child { grid-row: 1 / span 2; }
  html[data-look="premium"] .words ol > li:first-child blockquote p { font-size: clamp(1.75rem, 2.6vw, 2.4rem); line-height: 1.28; max-width: 24em; }
}
/* P2-5 section rhythm: tops full, bottoms short, so no boundary is two full paddings (3) */
html[data-look="premium"] .sec.board,
html[data-look="premium"] .sec.member,
html[data-look="premium"] .sec.faq { padding-bottom: clamp(40px, 4.4vw, 64px); }
html[data-look="premium"] .sec.words { padding-block: clamp(40px, 4.4vw, 64px); }
/* P2-2 membership terms: one ruled line, blush square dots between items; a dot that would open a line falls
   left of the list's own edge and is clipped (4). Flex credits stack flush with the price column (13.1). */
@media (min-width: 700px) {
  /* the line sits on the last membership row's rule (one hairline, not two: code review #3) */
  html[data-look="premium"] .member .terms {
    display: flex; flex-wrap: wrap; gap: 8px 0; margin-top: 0; padding: 14px 0;
    border-block: 1px solid var(--rule-2); border-top: 0; overflow: hidden;
  }
  html[data-look="premium"] .member .terms li { padding-left: 0; margin: 0 1.6em 0 0; color: var(--ivory-2); }
  html[data-look="premium"] .member .terms li::before { left: -.95em; top: .62em; width: 3px; height: 3px; }
  html[data-look="premium"] .member .flex > div:last-child { display: grid; justify-items: end; }
  html[data-look="premium"] .member .flex .credits { display: flex; flex-direction: column; align-items: flex-end; }
  html[data-look="premium"] .member .flex .credits > div { text-align: right; }
  html[data-look="premium"] .member .flex .credits b { font-variant-numeric: tabular-nums lining-nums; }
}
/* P2-7 FAQ: tier A (Chrome 131+) animates the height both ways; tier B (Safari, iPhone, the rest) lets the
   answer fade and settle up on open; close snaps (5 + 13.11) */
@supports (interpolate-size: allow-keywords) and selector(::details-content) {
  html[data-look="premium"] .faq details { interpolate-size: allow-keywords; }
  html[data-look="premium"] .faq details::details-content {
    block-size: 0; overflow: clip;
    transition: block-size .4s var(--ease), content-visibility .4s allow-discrete;
  }
  html[data-look="premium"] .faq details[open]::details-content { block-size: auto; }
}
@supports not ((interpolate-size: allow-keywords) and selector(::details-content)) {
  html[data-look="premium"] .faq details[open] > div { animation: kn-faq-in .4s var(--ease) both; }
}
@keyframes kn-faq-in { from { opacity: 0; transform: translateY(6px); } }
/* P2-8 interlude drift: 1.03 -> 1 tied to the band's own pass through the viewport; replaces the one-shot
   settle wherever it runs (premium.js skips .scale there). The timeline is on the clipping frame (6). */
@supports (animation-timeline: view()) {
  html[data-look="premium"] .interlude picture { view-timeline: --kn-band block; }
  html[data-look="premium"] .interlude img {
    transform-origin: 50% 60%;
    /* explicit 1ms: MDN (animation-duration, read 2026-11-05) says Firefox needs a duration to apply a
       scroll-driven animation; the timeline, not the duration, drives it (code review #6) */
    animation: kn-drift 1ms linear both; animation-timeline: --kn-band; animation-range: entry 0% exit 100%;
  }
}
@keyframes kn-drift { from { transform: scale(1.03); } to { transform: scale(1); } }
/* P2-9 board photo: one layer per row photo over the base image's 4:5 box; desktop only (7 + 13.9) */
html[data-look="premium"] .board-photo .kn-ph {
  position: absolute; top: 0; left: 0; width: 100%; aspect-ratio: 4 / 5; object-fit: cover;
  opacity: 0; transition: opacity .6s var(--ease); pointer-events: none;
}
/* the incoming photo fades in on top; the outgoing one holds until it is covered, so the base hands photo never
   shows through mid-swap (code review #4). Back to the base photo (no layer on): the last layer fades at once. */
html[data-look="premium"] .board-photo .kn-ph.on { opacity: 1; z-index: 1; }
html[data-look="premium"] .board-photo:has(.kn-ph.on) .kn-ph:not(.on) { transition-delay: .6s; }
@media (max-width: 899px) { html[data-look="premium"] .board-photo .kn-ph { display: none; } }

/* ---------- reduced motion: static thread, no stagger, no delays (styles.css zeroes durations only) ---------- */
@media (prefers-reduced-motion: reduce) {
  html[data-look="premium"] .stations .line,
  html[data-look="premium"] .st::before { animation: none !important; }
  html[data-look="premium"] .stations.sweep-wait .line { clip-path: none !important; }
  html[data-look="premium"] .stations.sweep-wait .st::before { opacity: 1 !important; }
  html[data-look="premium"] .interlude img { transform: none !important; animation: none !important; }
  html[data-look="premium"] .faq details::details-content { transition: none !important; }
  html[data-look="premium"] .faq details > div { animation: none !important; }
  html[data-look="premium"] .board-photo .kn-ph { transition: none !important; }
  html[data-look="premium"] *,
  html[data-look="premium"] *::before,
  html[data-look="premium"] *::after { transition-delay: 0s !important; animation-delay: 0s !important; }
  /* hero life (review B2 + N2): the two failsafes are delays, not motion; the zeroed delays above would fire them at
     first paint (the nav over the clip; the sticky bar beside the hero Book = two Books). More specific, so they win. */
  html[data-look="premium"] .nav { transition: none !important; }
  html[data-look="premium"].nav-tuck:not(.nav-js) .nav { animation-delay: 4s !important; }
  html[data-look="premium"] .stick { animation-delay: 4s !important; }
}
