/* =========================================================
   Cinema Skin FX — Landing Page
   Palette + voice per BRAND.md. Do not introduce colors
   outside the token block below.
   ========================================================= */

/* ---------- Tokens ---------- */
:root{
  /* Brand core */
  --black:#000000;
  /* ⚠️ The closing CTA band, which is deliberately NOT --black. Measured:
     white on it is 14.94:1 and the band's own body copy, white at .66, is
     7.3:1 — so nothing in the band had to be re-lit to stop it being black.
     The FOOTER is still --black; the band fades back down to it. */
  --espresso:#332417;
  --champagne:#FAEEC8;
  --seafoam:#94B7C8;
  --white:#FFFFFF;
  --taupe:#B8AFA3;

  /* Working gold ramp derived from Champagne Gold.
     #FAEEC8 is ~1.2:1 on white — unusable as type on light.
     --gold-ink clears 4.5:1.

     ⚠️ --gold-accent DOES NOT CLEAR 3:1, AND THIS COMMENT USED TO SAY IT
       DID. Measured from the rendered pixels in #process: 2.80:1 on
       rgb(252,249,244), and it is not the ground — #B8912F on PURE WHITE
       computes to 2.945:1 (L = .30651). Large type owes 3.0.

       The colour is the emphasis word in .section-title em, .coll-title em,
       .res-title em, .story-title em and .rt-title-lg em. NONE of them has
       been changed: fixing one leaves four headings that no longer match,
       and fixing all of them means moving THIS token, which also carries
       SVG strokes, borders and gradient stops across hero.css,
       interactive.css and routine.css. That is a page-wide decision and it
       is on the open list. Measure before trusting this ramp again. */
  --gold-ink:#8A6B1F;
  --gold-accent:#B8912F;
  --gold:#C6A455;
  --gold-soft:#E9DCB4;
  --gold-tint:#F7F0DE;

  /* Seafoam at 1.9:1 on its own tint is unreadable as an icon stroke.
     --sea-ink is the same hue darkened to clear 4.5:1 on --sea-tint. */
  --sea-ink:#3E6274;

  /* Neutrals */
  --ink:#0E0E0E;
  --ink-2:#3F3D39;
  --ink-3:#77736C;
  --cream:#FDFBF7;
  --sand:#F7F3EB;
  --line:#E9E3D6;
  --sea-tint:#EAF2F6;

  /* Type */
  --f-display:"Playfair Display", "Iowan Old Style", Georgia, serif;
  --f-body:"Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --f-logo:"Outfit", "Futura", "Century Gothic", var(--f-body);

  /* Rhythm */
  --shell:1260px;
  --gutter:clamp(20px, 4vw, 40px);
  --r-sm:10px;
  --r-md:16px;
  --r-lg:24px;
  --r-pill:999px;

  --shadow-sm:0 2px 10px rgba(24,20,12,.05);
  --shadow-md:0 14px 40px -18px rgba(24,20,12,.22);

  --ease:cubic-bezier(.22,.68,.32,1);

  /* Header */
  --hdr-h:88px;                          /* header box height — titlecard.css offsets against it */
  --cap-lift:clamp(8px, 1.1vw, 14px);    /* gap above and below the front-page capsule */
  --cap-side:8px;                        /* and beside it, so it floats at every width */
}

/* Registered so the frost can *ramp* in as the pill materialises.
   An unregistered custom property would jump from 0 to full blur. */
@property --hdr-blur{ syntax:"<length>"; inherits:true; initial-value:0px; }
@property --hdr-sat { syntax:"<number>"; inherits:true; initial-value:1; }
@property --cap-blur{ syntax:"<length>"; inherits:true; initial-value:0px; }
@property --cap-sat { syntax:"<number>"; inherits:true; initial-value:1; }

/* ---------- Reset ---------- */
*,*::before,*::after{box-sizing:border-box;}
/* The bar is permanent, so every in-page jump has to clear it or the
   heading it lands on ends up underneath. */
html{
  -webkit-text-size-adjust:100%;
  scroll-behavior:smooth;
  scroll-padding-top:calc(var(--hdr-h) + 14px);
}
body{
  margin:0;
  background:var(--cream);
  color:var(--ink);
  font-family:var(--f-body);
  font-size:16px;
  font-weight:400;
  line-height:1.65;
  -webkit-font-smoothing:antialiased;
  overflow-x:hidden;
}
h1,h2,h3,h4,p,figure,blockquote,ul,ol{margin:0;}
ul,ol{list-style:none;padding:0;}
a{color:inherit;text-decoration:none;}
img,svg{display:block;max-width:100%;}
button{font:inherit;color:inherit;background:none;border:0;cursor:pointer;}
:focus-visible{outline:2px solid var(--gold-ink);outline-offset:3px;border-radius:4px;}

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

.skip-link{
  position:absolute;left:-9999px;top:0;z-index:200;
  background:var(--black);color:var(--champagne);
  padding:12px 20px;border-radius:0 0 var(--r-sm) 0;
}
.skip-link:focus{left:0;}

.shell{width:100%;max-width:var(--shell);margin-inline:auto;padding-inline:var(--gutter);}

/* ---------- Typography helpers ---------- */
.eyebrow{
  font-size:11px;
  font-weight:600;
  letter-spacing:.20em;
  text-transform:uppercase;
  color:var(--gold-ink);
  margin-bottom:18px;
}
.section-title{
  font-family:var(--f-display);
  font-weight:400;
  font-size:clamp(28px, 3.4vw, 44px);
  line-height:1.15;
  letter-spacing:-.015em;
}
.br-lg{display:none;}
@media (min-width:768px){ .br-lg{display:inline;} }

/* ---------- Buttons ---------- */
.btn{
  display:inline-flex;align-items:center;gap:10px;
  white-space:nowrap;
  padding:15px 28px;
  border-radius:var(--r-pill);
  font-size:14px;font-weight:500;letter-spacing:.01em;
  border:1px solid transparent;
  transition:transform .35s var(--ease), box-shadow .35s var(--ease),
             background-color .3s var(--ease), border-color .3s var(--ease), color .3s var(--ease);
}
.btn .arw{transition:transform .35s var(--ease);}
/* ⚠️ A button must not move under the pointer. There is no hover transform
   here on purpose — it was translateY(-2px), and together with the magnetic
   module that used to sit in main.js it meant every control drifted as you
   approached it. Each variant answers a hover without leaving its box:
   .btn-gold deepens its shadow, .btn-ghost and .btn-outline-gold take a
   tint and a gold border, .btn-outline-light goes champagne. The arrow
   inside still nudges — the glyph moves, the target does not.
   will-change:transform is gone with it: a compositor layer per button
   bought nothing once the only transform left was the press. */
.btn:hover .arw{transform:translateX(4px);}
.btn-sm{padding:11px 22px;font-size:13px;}

.btn-gold{
  background:linear-gradient(135deg,#DCC486 0%, var(--gold) 45%, #B08D3E 100%);
  color:#17130A;
  box-shadow:0 10px 26px -12px rgba(160,124,40,.75);
}
.btn-gold:hover{box-shadow:0 16px 34px -12px rgba(160,124,40,.85);}

.btn-ghost{
  background:var(--white);
  border-color:var(--line);
  color:var(--ink);
}
.btn-ghost:hover{border-color:var(--gold);background:var(--gold-tint);}

.btn-outline-light{
  background:transparent;
  border-color:rgba(255,255,255,.4);
  color:var(--white);
}
.btn-outline-light:hover{border-color:var(--champagne);color:var(--champagne);}

.link-quiet{font-size:14px;color:var(--ink-2);transition:color .25s var(--ease);}
.link-quiet:hover{color:var(--gold-ink);}

.link-arrow{
  display:inline-flex;align-items:center;gap:8px;
  font-size:13px;color:var(--ink-2);
  padding-bottom:3px;
  border-bottom:1px solid var(--line);
  transition:color .25s var(--ease), border-color .25s var(--ease);
}
.link-arrow:hover{color:var(--gold-ink);border-color:var(--gold);}
.link-arrow .arw{transition:transform .3s var(--ease);}
.link-arrow:hover .arw{transform:translateX(4px);}

/* ---------- Logo ---------- */
.logo{display:inline-flex;align-items:center;gap:2px;}
.logo-mark{width:44px;height:44px;flex:none;margin-right:-4px;}
.logo-type{display:flex;flex-direction:column;line-height:.98;padding-top:2px;}
.logo-line-1{
  font-family:var(--f-logo);
  font-weight:300;
  font-size:22px;
  letter-spacing:.01em;
  color:var(--gold-accent);
}
.logo-line-2{
  font-family:var(--f-display);
  font-weight:500;
  font-size:19px;
  letter-spacing:.005em;
  color:var(--gold-accent);
  padding-left:14px;
}
.logo-light .logo-line-1,
.logo-light .logo-line-2{color:var(--white);}

/* ---------- The docked lockup is Boss RDR's ----------

   ⚠️ THE BRAND IN THE BAR CHANGES WHEN THE PAGE DOCKS. Asked for against a
   screenshot of the scrolled bar: this landing page runs under Boss RDR, so
   past the title card the bar carries his lockup instead of Cinema's. Cover
   mode is untouched — the card prints the Cinema wordmark at full size just
   below, and the bar keeps the ring monogram beside it.

   ⚠️ THE RDR LAYER IS OUT OF FLOW ON PURPOSE. In flow it is ~90px wider
   than the Cinema lockup, and with justify-content:space-between a wider
   first item walks the nav half that distance sideways — the links would
   drift right every time the page crossed the card, and drift back on the
   way up. Absolute, the box stays the Cinema lockup's size and the nav sits
   still. Nothing lives in the space it overhangs: docked, the gap from
   lockup to nav is ~400px at 1900 and it is measured down to 1025, where
   the nav folds into the burger anyway.

   ⚠️ THE CREST IS A MASK, NOT A PICTURE. The source is white line art on
   RDR's navy, and this bar is cream down seven sections and noir for the
   last two. Painting the mask with background-color is what lets one file
   read on both grounds — see the .is-on-dark rule further down.

   ⚠️ THE FILE IS A 41x42 SCREENGRAB, which is all there was: it is cut
   from the lockup pasted into chat, not from artwork. It is sharp at 1x and
   soft on a 2x screen. Ask RDR for the real mark and drop it in over this
   one — nothing but the file needs to change. */
:root{
  --rdr-navy:#254884;      /* sampled off the lockup's own plate */
  --rdr-navy-2:#55688F;    /* the subline, kept over 4.5:1 on cream */
}
/* The two lockups stand on the same spot, so something has to hold the
   place: .logo is in flow and sizes the box, the RDR link is absolute
   inside it. A wrapper rather than the link itself, because they are two
   <a>s now and an anchor cannot be nested in an anchor. */
.logo-stack{position:relative;display:flex;align-items:center;}
.site-header .logo{transition:opacity .45s var(--ease), visibility 0s;}
.logo-rdr{
  position:absolute;left:0;top:50%;translate:0 -50%;
  display:inline-flex;align-items:center;gap:11px;
  white-space:nowrap;
  opacity:0;visibility:hidden;
  transition:opacity .45s var(--ease), visibility 0s linear .45s;
}
.rdr-mark{
  width:41px;height:42px;flex:none;
  /* no background until the mask is known to work: an unmasked solid would
     be a navy brick in the middle of the bar */
}
/* ⚠️ DECLARED HERE AND USED IN TWO PLACES — the header lockup's .rdr-mark and
   the footer partnership card's .rp-mark. It lived inside the @supports block
   below until the card needed it. It is a 6KB base64 string and a second copy
   would be two of them to keep in step; the note below says the master is
   assets/logo/boss-rdr-crest.png and to re-inline it if it is ever replaced,
   which only works while there is exactly ONE place to re-inline.
   A custom property applies nothing by itself, so hoisting it out of the
   @supports block changes no behaviour — the guard stays on the mask. */
:root{
  --rdr-crest:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACkAAAAqCAYAAAAu9HJYAAAI20lEQVR4AayZCbSVUxTHv5simmehNM+SlBLJWGKZigYL0SKWNJhJihZLkkRmLbRMSWmtIgollWYkQyqUWEKkKJX0/H6v77vu+O596a7zf/ucfaZ9ztl7n/2dVyLYT7+CgoIuYCGYBN4Ai8CJ+2P4/yUkQvQH3cEhCPM5eBoMBFeAh8Fa6qqA3uA2yvuU9llIJj2bGTuBq8FwcB6oD6LUkExPoNAXQpvRR0q2eGmfhGQyhTuZqd4DL4M/QB8wBHwPfgR3gX7gAPAOmA2a0LcrkEcxv1RsIZngMYa+DGwH7p7C3kG+ApgOHgrxJvRAcBM4BVwCKoHTwIsg71QsIRGwMyPXBuWAR3cmtCJQJ7tC1cvjoMK87ctStv2p0O7geFCNsXpB80p5C8mgMUa8GRwMjgHlQQswF6wHqsDjUI9dw3HH3eU18DzuY6EKezjURQ1izJLkc6a8hAwHu4bRmoMm4C+g4dwP/RM0AJPBjWA0uBdcC14ATYGqMQzqzv4DrQXqAa3exZPNnvISku4DwLfAo9YwLic/AoyJxWLPgF3k7wGVgTqpK1I463dQ/xR8d/l2qMf8JfQwsBt0A0WmIoVkB0uDvoxQBSwGGsCrUCeazORzyEdJHVSwAhg7gO5oK/0LLZm2Hvks+Oqylu5uvku5FW16gKw7WqSQDHAV+Bq4E79DnwNacF0mTbVQdU4du5s29wGFbQVtjQDqYEAfd7gRPN3WFMq/kFclVAGNimJ6yiokA2skqxhoLthAVyfSSHQl4ymnJndlEswvwKfgJfAA2AISk8feA8Yu5ijP2KvBFMolKV8ATUtZhaRlY7AIRMkbxJ3Q7SyLmAlUV+SOLISnakACrXsTQmg4QfhbCe0NRgGtHVKY5vM3411flJA/0ynG6vRp15P3OD+D7mHSX6GpyQmXwFRY3ZOLupK2v8FLTB6xbmwBzFqMPxDoS1UVbyrYyakoIbVSV6yheMxl6Po30P1A0tIRcNwNhdIDaDCvwUtKCO1u6w22UqGwq6GekFb+KPm0lFVIBnPFOmIHMa9e6mLKs3Kvu9TBJsDQD+r/Liavg3ehZP9L9C1NSdSFOq6n48LmMadeAXZyyipk2Ow7qP7xBKj62QX6E1AQSFLaQ+lQ8AhwJ8+FZlKLI+GvBReBTcDTUthMek51EOQSciOtqgJ3ztWqm09QVlhIELAzZYC+bwA7oYuqToU3j1fnCurcNVjxdBY5rX4wVLfm0euedFmw0lORQjKpV57CbSa/k+6rwDrQi8nLgmrklwOvSJ0z2UDlj/TW63Ix7VqCDsD2qoJG6U3l8Wps6qd9M6JIIcMeM6G1mcAd8aYZS3kQ8LaxzlvInetEm6HwjSH1qYZqOvyv4L0NvLtdkPXPUh5Ce4ON88lPBVlTTiHZwY/orVVXIu/Kt1FuC3TK6ifZwJ0xfwMFA2CDYfXMuNNyf/gep4vRuDYwllbu7i8n7+nQJHPKKWTYLQb1mwUSqEMak7ulbk2E6RG7owqgUIZp3jqlqPM4DXx13nUo6y0igxpH2Q2AZE85heRI2tDd3RtB3qh6AiufAU8hvFGuI++uPg9VDTpA9Xn2e4u8bsuw7XTys+nr0S5hLJ29O92CvMJTnTnlFJJuWqDR9KXkOzKJuqY1zqNsuGZE7q6qjwYX6ttI6oYD9VCH7eKG0VedhB24INsb8ukv3YQg2y+nkAy8FvQHtwI/ruJjUd4NRoE7YS4FRjha+jnkDduWUadwxpXqIOzABRbA7wMcs28sFlN/g2y/nEJm65jKZ8KdYAZQ6JHQ6UC3ldq02OUS6ENbMBfMB/NS8AHl98FYYNCQNAE8/WVqnznwxwN1M6m9BfgGLLOgjh31Tc1Ppt7LwC5BCVbrMXlHO2gzuIZiEfygWgHPD/wNdLSeYjy9Ts4I3GvTq9Kjtq0uZTbtlwKPnWZ7E/N5tFp1RziGZuqueu2cXpXqtOpiXx8egui4HZQ+gfqyjYEiGJXr24wPDcVmMqkhVeCPdrqjH8wDQ7it8FYCv3f8hPVr8mP66FdpEk/e3Rb0ndtpH823kbxGpbcwinqQvtUiIe2gL5QmgU5+LL0C03pvCO9yiknJumRGLPYhDF2R9/40JktTF+qzpTFhheFhq0QhQ35G0h6uq9YQdNYU80p+fBmV16C1UREknhwvbXFhrZeCWT3C+kQh7SSsjIMdMMLxzjYU68bOZvJpaf0cgLaeQhQ8qK+yI0QCJspgVKXzj4LfJxljdVIDeldEqDUh1kFVco/aALY1HbxBaJYxRZOmVhpFyTOOlCbCxU1kHuf0mdDrVXidGmcazsUNx45OsgVBGgoYhll+GWocxoh+h8DOmOzrhJkq/Wa33tg0tV5+r3A+r0mvThfl5jWG70kkCekA8YlsAAzFfPz0avMFV4uzXV5ghw6ioQbjuI5FMZ7kCdVIr+It5AODrknhhtLf76a4kDYW8RESMr7xOJC7qb4kVMWz9hVxRpjxEVVBvf+nhbxcRLdnlOSGnGFjt1Uawe2P8oWU3VRHtFL1xLecQn7Cn0i4pL7sghPo87TQnuE4Cd0yZ2nneH4n2aAf48QiIWvKAaVgugKySUm/ZefB1OugEyv9+CosU+crRDmor20amZatwbnIwjbhn8QT8Ts9ZMeJUZSurh2cft7dPm14HamwGskCJvGIqI8nn1C8yrTyoyMu7Qxm1VePxz7fUPcJ0OiMNauzM+oZrL2JPkbjegy/FJ1zHLykeJI+bojvUH511vfungqzBqgaoj1Up713VP5SVqkHQX2o8o0HbqCyj4ZXLYQC1SZfH3QH84GLDhJ/8Px0sJ3tnVNvEr+Wo7a0mwRsd0t03FFdIWVlPsBXgtYDPs21g/qvDnmNyVcAbUAd0Bz4HGP5API1QQNgW/s2Je8Toq9rEb8zvEagMjAK87nFdo5bF16SfmcUEkn9MjRi9q5WB42QfPTUeIzSdSv6NY3C93GPWv0x+FC/WzOGPk+rPoq8KmEEVY/d2Uy5JfBBzM8K73YjqJPgOY5vTkl28S8AAAD//9ffx7MAAAAGSURBVAMARgF5Mab8xD0AAAAASUVORK5CYII=");
}

@supports ((-webkit-mask-image:url(a.png)) or (mask-image:url(a.png))){
  .rdr-mark{
    /* ⚠️ THE CREST IS INLINE, AND NOT BECAUSE OF A REQUEST COUNT. A mask
       loaded by path is DROPPED when the page is opened off the disk: over
       file:// every file is its own origin, the mask image reads as
       cross-origin, and Chromium paints the element not at all — no console
       error, no broken-image mark, just a lockup with a 41px hole where the
       crest should be. Measured both ways: by path the box paints nothing,
       inline it paints. That is how this page is being looked at while it is
       being built, so a path is not usable here.
       @supports cannot catch it either: masks ARE supported, it is the image
       that never arrives, so the guard sits there quite happily while the
       mark is invisible.
       The master is assets/logo/boss-rdr-crest.png. If it is ever replaced,
       re-inline it — base64 of the file, nothing else changes. */
    background-color:var(--rdr-navy);
    -webkit-mask:var(--rdr-crest) center/contain no-repeat;
    mask:var(--rdr-crest) center/contain no-repeat;
    transition:background-color .45s var(--ease);
  }
}
.rdr-type{display:flex;flex-direction:column;line-height:1.05;}
.rdr-line-1{
  font-family:var(--f-body);
  font-weight:600;
  font-size:16px;
  letter-spacing:.055em;
  text-transform:uppercase;
  color:var(--rdr-navy);
  transition:color .45s var(--ease);
}
.rdr-line-2{
  font-family:var(--f-display);
  font-style:italic;
  font-weight:400;
  font-size:13.5px;
  letter-spacing:.01em;
  color:var(--rdr-navy-2);
  transition:color .45s var(--ease);
}
/* ⚠️ visibility, NOT JUST opacity, ON WHICHEVER ONE IS OFF. These are two
   links to two different places, and a transparent link still takes the
   click and still takes a tab stop — docked, the invisible Cinema lockup
   sits under the left half of the RDR one. The 0s delay on the way out
   holds it visible for the length of the fade and drops it the moment the
   fade ends. */
.site-header:not(.is-cover) .logo{
  opacity:0;visibility:hidden;
  transition:opacity .45s var(--ease), visibility 0s linear .45s;
}
.site-header:not(.is-cover) .logo-rdr{
  opacity:1;visibility:visible;
  transition:opacity .45s var(--ease), visibility 0s;
}
/* ⚠️ THE SUBLINE IS ON AT EVERY WIDTH NOW, AND THE RULE THAT HID IT IS
   GONE. It read:

     @media (max-width:1100px){ .rdr-line-2{display:none;} }

   and the note with it said that between 1025 and 1100 the lockup and the nav
   row "close to about ten pixels" with the full name on. That was true when it
   was written. The bar has changed since — the nav lost links and the actions
   block was rebuilt — so it was measured again rather than taken on trust,
   with the subline forced on at twelve widths:

     320  lockup 170  gap to the burger  66px
     390  lockup 170  gap to the burger 136px
    1025  lockup 176  gap to the nav     67px
    1100  lockup 176  gap to the nav    100px
    1440  lockup 176  gap to the nav    249px

   The tightest gap anywhere is 66px. The ten-pixel squeeze does not reproduce,
   so the reason for hiding it has gone and the two-line lockup — the mark, the
   name, and the name beneath it — is what the bar carries at every size. */

/* ---------- Header ----------
   A docked bar, full width and flush to the top edge.

   It used to be a floating glass pill. That reads as overlap: the page
   is visible in the gap above it, down both flanks, and straight through
   the plate — so every section looked like it was sliding underneath.
   Docking removes all three at once. There is no gap for content to show
   in, and the plate is opaque enough that nothing reads through it.

   It is still glass, just frosted rather than clear: the blur and the
   saturation lift are what make the colour behind it move as you scroll.
   Content is never legible through it.

   The bar is also permanent now — it appears the moment you leave the
   title card and stays. See the auto-hide note in main.js. */
.site-header{
  position:sticky;top:0;z-index:100;
  background-color:rgba(253,251,247,.66);
  backdrop-filter:blur(var(--hdr-blur)) saturate(var(--hdr-sat));
  -webkit-backdrop-filter:blur(var(--hdr-blur)) saturate(var(--hdr-sat));
  border-bottom:1px solid rgba(233,220,180,.9);
  box-shadow:0 12px 30px -24px rgba(24,20,12,.55);
  --hdr-blur:26px;
  --hdr-sat:1.9;
  transition:
    opacity .4s var(--ease),
    translate .4s var(--ease),
    --hdr-blur .45s var(--ease),
    --hdr-sat .45s var(--ease),
    background-color .45s var(--ease),
    border-color .45s var(--ease),
    box-shadow .45s var(--ease);
}
.header-inner{
  position:relative;
  display:flex;align-items:center;justify-content:space-between;gap:24px;
  min-height:var(--hdr-h);

  /* ---- the rails the bar's content runs on ----
     The div carries .shell, but the two header states want different rails,
     so the shell's own max-width and padding come off here and the inset
     becomes one variable instead.

     --hdr-side at rest reproduces .shell exactly: 1260 centred with the
     gutter inside it, so the logo lands on the same column as every section
     below. That is the box the cover capsule is drawn in, which is why cover
     mode keeps it.

     Written as padding rather than max-width because the two states have to
     transition into each other, and there is no animatable value between
     1260px and no rail at all. */
  --hdr-side:max(var(--gutter), calc((100% - var(--shell)) / 2 + var(--gutter)));
  /* Where the capsule's edge sits: --cap-side outside the content, measured
     off the shell box rather than off the element, now that the element is
     the whole window. */
  --cap-x:calc(var(--hdr-side) - var(--gutter) + var(--cap-side));
  max-width:none;
  padding-inline:var(--hdr-side);
  transition:padding-inline .45s var(--ease);
}

/* ⚠️ DOCKED, THE CONTENT TAKES THE WINDOW. Asked for against a screenshot of
   the scrolled bar: the lockup goes to the far left and the CTA to the far
   right, instead of both standing on the 1260px rail with ~320px of empty
   glass outside each of them. The plate is already full bleed once it docks;
   this is the content catching up with it.

   Only past the shell's own width, which is the only place that dead space
   exists. Narrower than that the rail IS the window, and moving the inset
   would buy nothing but a logo that twitches every time the page crosses in
   or out of cover.

   The max() is the ceiling for very wide screens: past ~1968 the content
   stops spreading and centres on 1840, so the nav does not end up strung
   across a metre of glass. */
@media (min-width:1261px){
  .site-header:not(.is-cover) .header-inner{
    --hdr-side:max(clamp(24px, 3vw, 64px), calc((100% - 1840px) / 2));
  }
}

/* Rim light along the top edge and a soft fall below it. On its own layer
   so its opacity can be animated — opacity on the bar itself would isolate
   it into its own backdrop root and kill the frost. */
.site-header::after{
  content:"";
  position:absolute;inset:0;
  z-index:-1;                /* over the bar's own ground, under the nav */
  pointer-events:none;
  background-image:linear-gradient(180deg,
    rgba(255,255,255,.75) 0%, rgba(255,255,255,.14) 46%, rgba(255,255,255,0) 100%);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.95);
  transition:opacity .45s var(--ease);
}

/* Cover mode: the title card owns the screen and carries the full logo,
   so the bar stays out of the way until you scroll past it. Keyed on
   .is-cover rather than :not(.is-stuck) so the bar is the default — with
   JS off the header is readable instead of floating bare over the page. */
.site-header.is-cover{
  /* Zeroing the values is not enough: in Chromium ANY backdrop-filter,
     identity included, still creates a backdrop root, and that root prints
     its own edge over the title card's filtered artwork. The property has
     to be removed outright. The vars still reset so the transition back
     out of cover mode starts from the right place. */
  --hdr-blur:0px;
  --hdr-sat:1;
  backdrop-filter:none;
  -webkit-backdrop-filter:none;
  background-color:transparent;
  border-bottom-color:transparent;
  box-shadow:none;
}
.site-header.is-cover::after{ opacity:0; }

/* ---------- The bar over a dark section ----------

   The docked plate is thin enough to read the page through, which is the
   point — but seven of this page's nine sections are cream and the last two
   are black. At one alpha the bar can only suit one of them: raise it until
   the ink survives the black and it stops being glass anywhere; lower it for
   the glass and the ink dies on the black.

   So the ink follows the ground instead. main.js watches which [data-scene]
   band sits under the bar's own midline — not which one owns the viewport,
   which flips a good half-screen too early — and sets .is-on-dark. Over noir
   the plate goes smoked and the type goes light; everywhere else it is the
   bright frost below. Both are the same glass, lit from opposite sides. */
.site-header.is-on-dark:not(.is-cover){
  background-color:rgba(16,14,10,.5);
  border-bottom-color:rgba(250,238,200,.16);
  box-shadow:0 16px 36px -28px rgba(0,0,0,.9);
}
.site-header.is-on-dark:not(.is-cover) .nav a{color:rgba(255,255,255,.84);}
.site-header.is-on-dark:not(.is-cover) .nav a:hover{color:#fff;}
.site-header.is-on-dark:not(.is-cover) .nav a.is-active{color:var(--champagne);}
.site-header.is-on-dark:not(.is-cover) .nav a::after{background:var(--champagne);}
.site-header.is-on-dark:not(.is-cover) .logo-line-1,
.site-header.is-on-dark:not(.is-cover) .logo-line-2{color:var(--champagne);}
.site-header.is-on-dark:not(.is-cover) .rdr-mark{background-color:var(--champagne);}
.site-header.is-on-dark:not(.is-cover) .rdr-line-1{color:var(--champagne);}
.site-header.is-on-dark:not(.is-cover) .rdr-line-2{color:rgba(255,255,255,.72);}
/* it leaves the page, so it answers the pointer: the name under the mark
   comes up to the mark's own weight of ink */
.logo-rdr:hover .rdr-line-2{color:var(--rdr-navy);}
.site-header.is-on-dark:not(.is-cover) .logo-rdr:hover .rdr-line-2{color:var(--champagne);}
.site-header.is-on-dark:not(.is-cover) .nav-toggle span{background:rgba(255,255,255,.92);}
/* The rim reads from the other side too — a light hairline on top rather
   than a shadow under it. */
.site-header.is-on-dark:not(.is-cover)::after{
  background-image:linear-gradient(180deg,
    rgba(250,238,200,.18) 0%, rgba(250,238,200,.03) 46%, rgba(250,238,200,0) 100%);
  box-shadow:inset 0 1px 0 rgba(250,238,200,.4);
}

/* The front-page capsule.

   Over the title card the bar floats instead of docking: a rounded glass
   capsule sitting on the artwork, the way a cover treatment should. It is
   only ever on over the card — the moment you scroll into content it fades
   out and the docked bar fades in, because a floating pill over a section
   is exactly the overlap this layout is trying to avoid.

   Painted on .header-inner so it inherits the shell width, and defined
   transparent at rest so both directions of the swap can transition. */
.header-inner::before{
  content:"";
  position:absolute;
  inset:var(--cap-lift) var(--cap-x);
  z-index:-1;                /* over the header's ground, under the nav */
  pointer-events:none;
  border-radius:var(--r-pill);
  background-color:transparent;
  border:1px solid transparent;
  transition:
    --cap-blur .45s var(--ease),
    --cap-sat .45s var(--ease),
    background-color .45s var(--ease),
    border-color .45s var(--ease),
    box-shadow .45s var(--ease);
}
.site-header.is-cover .header-inner::before{
  /* Clear glass, not frosted milk. The plate is thin enough to read the
     artwork through, so the frost has to do the work the tint used to:
     more blur, and a saturation lift so the colour behind stays colour
     rather than going grey as it defocuses. */
  --cap-blur:22px;
  --cap-sat:2;
  backdrop-filter:blur(var(--cap-blur)) saturate(var(--cap-sat));
  -webkit-backdrop-filter:blur(var(--cap-blur)) saturate(var(--cap-sat));
  background-color:rgba(255,253,248,.17);
  /* ⚠️ WARM, NOT WHITE, AND THE CTA IS THE REASON. This was
     rgba(255,255,255,.85), and with the inset rim light under it that stacked
     into a 2px near-white band stepping 1.113:1 off the cream. It runs 6px
     above and below the gold button and wraps its right cap, so the one place
     on the page where it had something to outline, it outlined it — two
     concentric pills reading as a stray ring around the button rather than as
     the edge of a plate. Warm it to the page's own tint and the plate keeps
     its edge without drawing around whatever sits inside it. */
  border-color:rgba(247,240,222,.9);
  box-shadow:
    0 22px 48px -26px rgba(60,44,16,.5),
    0 3px 10px -6px rgba(60,44,16,.2),
    /* Rim light. With this little tint left, the edges are most of what
       tells you there is an object here at all. */
    /* ⚠️ THE TOP ONE WAS FULLY OPAQUE WHITE and sat directly inside a
       near-white border — 1px + 1px of the same colour is a 2px band, which is
       what made it read as a drawn line instead of a lit edge. Warm and
       halved; it is still a rim light, it is no longer a second border. */
    inset 0 1px 0 rgba(255,252,243,.58),
    inset 0 -1px 0 rgba(255,252,243,.3),
    inset 1px 0 0 rgba(255,252,243,.24),
    inset -1px 0 0 rgba(255,252,243,.24),
    inset 0 -2px 6px -4px rgba(198,164,85,.3);
}
/* Sheen, on its own layer: opacity on the plate would isolate it into a
   backdrop root and kill the frost. */
.header-inner::after{
  content:"";
  position:absolute;
  inset:var(--cap-lift) var(--cap-x);
  z-index:-1;
  pointer-events:none;
  border-radius:var(--r-pill);
  opacity:0;
  background-image:
    linear-gradient(180deg, rgba(255,255,255,.5) 0%, rgba(255,255,255,.07) 46%, rgba(255,255,255,0) 100%),
    linear-gradient(104deg, rgba(255,255,255,0) 30%, rgba(255,255,255,.34) 47%, rgba(255,255,255,0) 64%);
  transition:opacity .45s var(--ease);
}
.site-header.is-cover .header-inner::after{ opacity:1; }
.nav{display:flex;align-items:center;gap:clamp(16px,2.2vw,34px);}
.nav a{
  position:relative;
  font-size:14px;
  color:var(--ink-2);
  padding:6px 0;
  transition:color .25s var(--ease);
}
.nav a::after{
  content:"";position:absolute;left:0;right:0;bottom:-2px;height:1.5px;
  background:var(--gold);
  transform:scaleX(0);transform-origin:left;
  transition:transform .3s var(--ease);
}
.nav a:hover{color:var(--ink);}
.nav a:hover::after,
.nav a.is-active::after{transform:scaleX(1);}
/* A deeper gold than `--gold-ink` for this one link. It is small type on
   glass, and glass varies — the plate is a shade darker wherever something
   dark passes under it, and at 14px that variance is what decides whether the
   active link clears 4.5:1. The eyebrows and small gold type elsewhere sit on
   solid cream and keep the token. */
.nav a.is-active{color:#7A5E1A;font-weight:500;}

.header-actions{display:flex;align-items:center;gap:20px;}

/* The drawer's own furniture. It is display:none by default and comes back
   only where .nav is a drawer, which is the same gate .nav-toggle uses: above
   1024 the nav is six links in a row and none of this belongs in it. */
.nav-head,.nav-foot{display:none;}

.nav-toggle{display:none;width:44px;height:44px;flex-direction:column;justify-content:center;gap:5px;padding:0 10px;}
.nav-toggle span{display:block;height:1.5px;background:var(--ink);border-radius:2px;transition:transform .3s var(--ease), opacity .2s var(--ease), background .18s var(--ease);}
/* ⚠️ IT WAS THE ONLY CONTROL ON THE COVER THAT ANSWERED NOTHING. Every other
   one has a press state; this had the open/close bar animation and no
   acknowledgement of the touch itself, which on a phone — where there is no
   hover to fall back on — is the difference between a button and a drawing.
   touch-action:manipulation because it is a tap target and nothing else, and
   the tap-highlight goes because there is a real press state now. */
.nav-toggle{transition:scale .18s var(--ease);touch-action:manipulation;-webkit-tap-highlight-color:transparent;}
.nav-toggle:active{scale:.88;}
.nav-toggle:active span{background:var(--gold-ink);}
.nav-toggle[aria-expanded="true"] span:nth-child(1){transform:translateY(6.5px) rotate(45deg);}
.nav-toggle[aria-expanded="true"] span:nth-child(2){opacity:0;}
.nav-toggle[aria-expanded="true"] span:nth-child(3){transform:translateY(-6.5px) rotate(-45deg);}

.hero-title em{
  font-style:normal;
  color:var(--gold-accent);
}









/* ---------- Trust strip ----------
   ⚠️ THE TRUST BAND LIVES IN assets/css/trust.css, NOT HERE.
   A .trust / .trust-inner / .trust-item / .trust-ico / .trust-text component
   sat at this spot for months with no markup anywhere on the page using it.
   When the real band arrived under the same names it inherited two things
   nobody had written for it: a hard border-block above and below, on top of
   the band's own faded hairlines, and a 1x34px divider between every pair of
   columns. Both were in the first photograph of it.
   Deleted rather than renamed around, because two definitions of .trust-item
   on one page is the trap, not the symptom. */

/* ---------- Sections ---------- */
.section{padding:clamp(56px,7vw,96px) 0;}
/* The line-up panels are full-bleed and end the section, so the bottom
   padding would read as a stripe between two photographs. */
/* ⚠️ The section carries NO padding because the product hero has to run
   full-bleed and full-height inside it — padding here would inset the
   photograph. The cost was that the heading lost its rhythm too: the tinted
   band began 13px above the eyebrow and the gold rule sat 36px off the top of
   the photograph, so the whole head read as jammed between the card above and
   the picture below.

   The rhythm goes back on the SHELL, which wraps only the heading. The hero is
   a sibling of that shell, so it cannot be reached by this. */
/* ⚠️ THE CREAM STARTS AT THE TOP OF THE SECTION, not at the first product.
   The heading used to sit on the page's own ground and the products on cream,
   meeting at a hard horizontal line — which is what made the title read as
   belonging to whatever was above it rather than to the products.

   ⚠️ .section.collections, NOT .collections. atmosphere.css loads AFTER this
   file and carries `.section{background:transparent}` at the same specificity
   (0,1,0), so a single class here loses on source order and the background
   silently does not appear. Two classes score (0,2,0) and win.

   ⚠️ NO overflow:hidden HERE. The field canvas is position:sticky, and
   overflow:hidden would make this a scroll container and stop it sticking —
   that is precisely the bug this change exists to fix. */
.section.collections{
  padding:0;
  position:relative;
  --agrav-bg:radial-gradient(ellipse 80% 90% at 50% 47%, #FEEEDD 0%, #FCE9D5 52%, #F7E1CA 100%);
  --agrav-bg-sm:radial-gradient(ellipse 130% 78% at 50% 33%, #FEEFDF 0%, #FBE7D2 46%, #F4DDC6 100%);
  background:var(--agrav-bg);
}
/* =========================================================
   The products ground

   Measured at 1900x950 before this existed: the peach varied by 7 to 8 levels
   of red across the whole section, of which the bloom canvas was worth 3 to 5
   (sampled with it visible and then hidden). Seven levels out of 255 is under
   three percent, which is what "too plain" means in numbers. The canvas was
   working as built; what was built was a wash.

   Two layers, both from vocabulary already on this page — the gold hairline
   curves are the .why filigree, and the bubbles are what every product
   photograph in this section is already full of.

   ⚠️ NOTHING GOES BESIDE A BOTTLE. Each photograph is masked to a soft ellipse
   around its own product and everything outside it is this CSS ground; the two
   agree to within four or five levels immediately beside the bottle, and that
   match is the only reason the seam is invisible. Both layers sit at z-index 0
   under the slides, and every element is placed in the outer margins or in the
   gaps between bands.
   ========================================================= */
.agrav-fil,
.agrav-drops{
  position:absolute;inset:0;
  z-index:0;
  pointer-events:none;
  /* ⚠️ TWO clips, and they do different jobs — I shipped only the first and
     it was not enough.

     clip-path:inset(0) is for the SVG. .collections cannot have
     overflow:hidden (that is what stopped the field canvas sticking), and SVG
     ink is not contained by an ancestor's overflow anyway: it clips
     visually and still counts toward scrollWidth. Same fix as .why-filigree.

     overflow:hidden is for the BUBBLES, and clip-path does not do it —
     clip-path clips PAINT, not layout, so an absolutely positioned child
     still contributes its overflow to the scroll box. The drops are placed by
     percentage and sized in pixels, so a 96px bubble at left:96.5% hangs off
     the right edge by a fixed amount at every width — which is why the damage
     grew as the screen shrank. Measured before this line: 5px of sideways
     scroll at 1440, 12px at 1280, 24px at 1024. */
  clip-path:inset(0);
  overflow:hidden;
}

/* ---------- Both layers ride the scroll ----------
   main.js writes two numbers on the section as it travels through the
   viewport: --agrav-p, 0 to 1 across the whole passage, and --agrav-draw,
   the same thing on a shorter fuse for the curves.

   ⚠️ The transform goes on the WRAPPERS, never on a bubble. Each .agrav-drops
   i already animates its own transform for the rise, and a running animation
   beats a plain declaration on the same property — putting the parallax on the
   children would have been silently ignored, exactly as transform:scale() was
   in the phone block. Two elements move; the twenty inside them are carried.

   ⚠️ And the defaults are the NO-JS state, not zero. --agrav-p falls back to
   .5, which is the midpoint of the travel and therefore no offset at all, and
   --agrav-draw falls back to 1, which is a fully drawn curve. Script off, and
   the section looks exactly as it did before any of this. */
.agrav-drops{
  transform:translate3d(0, calc((var(--agrav-p,.5) - .5) * -150px), 0);
  will-change:transform;
}
.agrav-fil{
  width:100%;height:100%;
  /* a third of the drops' travel — that difference IS the depth */
  transform:translate3d(0, calc((var(--agrav-p,.5) - .5) * -52px), 0);
  will-change:transform;
}
.af-curve{
  fill:none;
  stroke:rgba(198,164,85,.5);
  stroke-width:1.3;
  stroke-linecap:round;
  /* the viewBox is stretched to the section, so without this the hairline
     would be stretched with it and stop being a hairline */
  vector-effect:non-scaling-stroke;
  stroke-dasharray:100;
  /* ⚠️ NOT an animation any more. This used to be a 3.2s afDraw that fired
     once on load, which meant the curves had finished drawing themselves long
     before anyone had scrolled far enough to see them. Driven off the scroll
     they draw as you arrive — and it has to be a declaration, because if the
     animation were still here it would win over this and the scroll value
     would do nothing at all. */
  stroke-dashoffset:calc(100 * (1 - var(--agrav-draw, 1)));
}
.af-2{stroke:rgba(198,164,85,.42);}
.af-3{stroke:rgba(198,164,85,.34);stroke-width:1.1;}
.af-4{stroke:rgba(198,164,85,.34);stroke-width:1.1;}
/* the later curves lag, so the four do not draw as one stroke */
.af-2{stroke-dashoffset:calc(100 * (1 - clamp(0, (var(--agrav-draw, 1) - .08) * 1.35, 1)));}
.af-3{stroke-dashoffset:calc(100 * (1 - clamp(0, (var(--agrav-draw, 1) - .16) * 1.5,  1)));}
.af-4{stroke-dashoffset:calc(100 * (1 - clamp(0, (var(--agrav-draw, 1) - .24) * 1.6,  1)));}

/* ---------- Serum ----------
   A bubble, not a dot: a bright specular high on the upper left, a warm body,
   and a heavier rim low down where the light wraps under it. That is what the
   bubbles in the artwork do, and matching them is what keeps this layer
   reading as part of the picture rather than as confetti over it. */
.agrav-drops i{
  position:absolute;
  left:var(--x);top:var(--y);
  width:var(--s);height:var(--s);
  border-radius:50%;
  background:
    radial-gradient(circle at 33% 27%, rgba(255,255,255,.92) 0%, rgba(255,255,255,0) 38%),
    radial-gradient(circle at 68% 74%, rgba(255,236,212,.5) 0%, rgba(255,236,212,0) 46%),
    radial-gradient(circle at 50% 50%, rgba(253,232,210,.42) 0%, rgba(240,206,172,.3) 58%, rgba(232,192,152,.16) 100%);
  border:1px solid rgba(255,255,255,.55);
  /* ⚠️ Warm, never black. A neutral shadow over this cream turns it grey —
     which is the note already standing on the value cards. */
  box-shadow:
    inset 0 -6px 14px rgba(206,158,110,.22),
    inset 0 4px 10px rgba(255,255,255,.5),
    0 10px 26px -8px rgba(190,140,90,.22);
  animation:agravRise var(--dur,28s) linear var(--d,0s) infinite;
}
/* Rises and drifts, and the scale breathes so a cluster does not move as a
   sheet. translate/scale only — nothing here triggers layout. */
@keyframes agravRise{
  0%   {transform:translate3d(0,14px,0) scale(.94); opacity:0;}
  12%  {opacity:1;}
  88%  {opacity:1;}
  100% {transform:translate3d(10px,-190px,0) scale(1.06); opacity:0;}
}

@media (prefers-reduced-motion:reduce){
  /* Everything arrives finished and nothing tracks the scroll. main.js stops
     writing the two properties as well, so these are what the layers keep. */
  .af-curve{stroke-dashoffset:0 !important;}
  .agrav-drops,.agrav-fil{transform:none !important;will-change:auto;}
  /* the bubbles stay — they are the texture, and only the drift was motion */
  .agrav-drops i{animation:none;opacity:1;}
}

/* A phone is one column and the artwork spans most of it, so the outer margins
   the drops were placed in barely exist. Fewer and smaller, or they crowd the
   product.

   ⚠️ SIZED, NOT SCALED. transform:scale(.62) here did nothing: agravRise
   animates transform, and a running animation beats a plain declaration on the
   same property — the same trap that let the title card tagline keep the
   desktop letter-spacing a media query had already overridden. Measured: the
   drops still came out 70, 41 and 95px on a 393px screen, and a 95px bubble is
   a quarter of that screen. width/height are not animated, so they land. */
@media (max-width:720px){
  .agrav-drops i{
    width:calc(var(--s) * .56);
    height:calc(var(--s) * .56);
    opacity:.72;
  }
  .agrav-drops i:nth-child(3n){display:none;}
  .af-3,.af-4{display:none;}
}

/* the heading rides above the field rather than under it */
.collections > .shell{position:relative;z-index:1;}

/* =========================================================
   The head band's own light

   Measured before this existed, at 393x852, with the type and the sticky
   header hidden so the reading is of the ground rather than of the words:

     DETAIL  1.04 levels between a pixel and its neighbour 6px away
             1% of the band had any local structure at all
     LIFE    0% of pixels moved 5 levels or more in two seconds
     and 0 of the 14 bubbles shown at that width reached the band

   ⚠️ THE RANGE ACROSS THE BAND WAS 39 LEVELS AND THAT MEANT NOTHING. A
   smooth gradient from black to white has a range of 255 and nothing
   whatever to look at; what "boring" means is no LOCAL structure. The first
   two attempts at measuring this reported a range — one of them including
   the black title ink, at 242 levels — and both would have said the ground
   was fine. scratchpad/groundlife.js carries the corrected instrument and
   both faults in its header.

   ⚠️ AND THE TARGET IS A REFERENCE BAND, NOT A NUMBER I LIKED. The first two
   sets were tuned against thresholds invented for the probe, which printed
   "there is nothing to look at" over a capture in which the light was
   plainly visible. Measured against .why-head — decoration on this same page
   that nobody has objected to:

                    .why-head   this band, before   after
     DETAIL  6px      1.32        1.04              see README
     DETAIL 24px      2.29        1.43
     LIFE             0.2%        0%

   The reference moves almost not at all, so the thing this band was short of
   was never movement — it was static structure, and two more rounds of
   making the motes drift harder would have made it worse. Run
   scratchpad/groundlife.js with a selector as its 6th argument to compare
   any two bands on the page.

   ⚠️ NOTHING HERE IS GOLD. Gold is darker than this peach, and the accent in
   the title has 0.71 of headroom over its 3:1 requirement. A layer that
   subtracts light spends that; one that adds light can only widen every
   ratio in the band, because the ink does not move. Warm white and
   champagne only — and re-measured afterwards rather than reasoned about.
   ========================================================= */
.coll-motes{
  position:absolute;
  /* ⚠️ THE SHELL'S BOX, NOT A HEIGHT OF ITS OWN. A clamped height was the
     first answer: clamp(420px,54vh,780px) is 460px at 393x852, and the
     product stage starts at 382 — 78px over the one rule this section has.
     Inset to the shell it is the head band by construction, at every width. */
  inset:0;
  z-index:0;
  pointer-events:none;
  /* both, and they do different jobs — the same pair .agrav-drops documents:
     clip-path clips paint, overflow keeps a percentage-placed child from
     contributing to scrollWidth */
  clip-path:inset(0);
  overflow:hidden;
}
/* the words sit on top of it */
.coll-head{position:relative;z-index:1;}

/* The two blooms are the "blend in" half — a wash that breathes, with no
   edge anywhere in it. They are pseudo-elements so the layer stays two
   elements of markup rather than four. */
/* ⚠️ THESE USED TO HANG OUTSIDE THE LAYER AND GET CUT OFF BY ITS OVERFLOW,
   AND THE CUT WAS VISIBLE. left:-18% and right:-16% put the clip 83px and
   97px inside a 1440 frame — a hard vertical line in the one layer on the
   page that must not contain a line. Measured by averaging every column of
   the band and looking for a step against the band's own variation:

     column to column   typical 0.194 levels, 99th percentile 0.426
     left edge  x83     step 2.126   —  5x the 99th percentile
     right edge x1343   step 4.928   — 11x

   No mask: mask-composite needs the -webkit- spelling and the standard one
   to agree about two gradients, and none of it is needed. The gradient is
   already zero alpha at 72% of its radius, so if that circle lands inside
   the layer nothing visible is ever cut. Each figure below is worked to
   hold at the far end of the breathe too, which is where the first attempt
   would have failed again: visible half-extent is .36 x size x scale, and
   the drift adds to it.

   Ellipses rather than circles, because aspect-ratio:1 on a percentage
   width made an 806px circle inside a 290px box at 1440 — clipped top and
   bottom by construction, whatever the horizontal numbers said. */
.coll-motes::before,
.coll-motes::after{
  content:"";
  position:absolute;
  top:0;height:100%;
  border-radius:50%;
  background:radial-gradient(ellipse at 50% 50%,
    rgba(255,251,242,.62) 0%,
    rgba(255,246,228,.26) 44%,
    rgba(255,244,224,0) 72%);
  will-change:transform, opacity;
}
.coll-motes::before{
  width:62%;left:-2%;
  animation:collBloom 17s ease-in-out infinite alternate;
}
.coll-motes::after{
  width:56%;right:-1%;
  animation:collBloom 21s ease-in-out -8s infinite alternate-reverse;
}
/* 1.12 and 3%, not 1.18 and 7% — at 1.18 the 72% circle reached past the
   top of the layer again and the seam would have come back at one end of
   the breathe only, which is the hardest kind to catch */
@keyframes collBloom{
  from{transform:translate3d(0,0,0) scale(1);opacity:.5;}
  to  {transform:translate3d(3%,-3%,0) scale(1.12);opacity:.95;}
}

/* The motes are the "light elements that keep moving" half. Out-of-focus
   points of light, which is the one ambient idea this brand can use without
   borrowing: the section above it is called Cinema. Each carries its own
   rate, size, drift and resting opacity — a set that shares a duration moves
   as a sheet, and a sheet reads as decoration rather than as air. */
.coll-motes i{
  position:absolute;
  left:var(--x);top:var(--y);
  width:var(--s);height:var(--s);
  border-radius:50%;
  /* a harder core than the first set: at 40% of the radius the old ramp was
     already down to half, so a 14px spark had about a 3px centre and read as
     a smudge rather than as a point of light */
  background:radial-gradient(circle at 50% 50%,
    rgba(255,254,250,.98) 0%,
    rgba(255,250,236,.86) 26%,
    rgba(253,243,218,.34) 58%,
    rgba(251,240,210,0) 100%);
  opacity:0;
  animation:collMote var(--dur,30s) linear var(--d,0s) infinite;
}
/* translate and opacity only — nothing here touches layout or paint.

   ⚠️ THE TWINKLE IS NOT DECORATION ON TOP OF THE DRIFT, IT IS WHAT MAKES THE
   DRIFT VISIBLE. Measured: a soft 100px blob travelling its whole 130px in
   30s moves about 10px in two seconds and changes its own pixels by roughly
   1.6 levels doing it — real movement, below the threshold at which a person
   notices anything. An opacity pulse changes the whole element at once, so
   three pulses per cycle buy perceptible change without another twenty
   elements to animate.

   ⚠️ AND --o2 IS A LITERAL, NOT A calc(). `opacity:calc(var(--o) * .28)`
   reads better and fails silently: an opacity that does not resolve is
   dropped from that keyframe and the animation interpolates straight past
   it, so the twinkle would disappear with nothing at all to show for it.
   Every element carries both numbers. */
@keyframes collMote{
  0%  {transform:translate3d(0,20px,0) scale(.85);opacity:0;}
  10% {opacity:var(--o,.5);}
  26% {opacity:var(--o2,.2);}
  42% {opacity:var(--o,.5);}
  59% {opacity:var(--o2,.2);}
  75% {opacity:var(--o,.5);}
  89% {opacity:var(--o2,.2);}
  100%{transform:translate3d(var(--dx,7px),-130px,0) scale(1.14);opacity:0;}
}

/* The house convention in this file and in atmosphere.css: the light stays,
   the movement stops. Resting opacity rather than 0, or the band goes back
   to being the flat wash this was written to fix. */
@media (prefers-reduced-motion:reduce){
  .coll-motes i{animation:none;opacity:var(--o,.5);}
  .coll-motes::before,
  .coll-motes::after{animation:none;opacity:.72;}
}
.collections > .shell{
  padding-block:clamp(56px,7vw,96px) clamp(30px,3.6vw,46px);
}
.collections .section-head{margin-bottom:0;}

/* =========================================================
   The collections head

   Measured before this: the whole band was 237px tall with a 594px title in a
   1260px shell, an 11px eyebrow, a 74px bar, and the outbound link sitting
   ~640px away from the words it belongs to. Next to the .why head — eyebrow,
   sparkle rule, two-line 46px title, sparkle rule, sub — it read as a caption
   over a photograph rather than as the opening of the longest section here.

   Same parts as .why, different arrangement. That is the point: a second
   centred-and-symmetrical head would be a repeat, and this one has a job .why
   does not — it carries the link off the page to the shop, which wants its
   own side.
   ========================================================= */
.coll-head{
  align-items:flex-end;
  gap:clamp(20px,3vw,56px);
}
/* Narrower than the shell on purpose. The title breaks where the writing
   wants it to (a <br>), and the sub needs a measure it can be read at — a
   1260px line of 15px type is about 150 characters, roughly twice the width
   anyone tracks comfortably. */
.coll-lede{max-width:min(660px,100%);}

.coll-eyebrow{
  display:flex;align-items:center;gap:9px;
  /* .3em to match .why-eyebrow — the shared .eyebrow is .20em, and side by
     side the looser tracking is what makes the two heads read as a pair */
  letter-spacing:.3em;
  margin-bottom:clamp(10px,1.1vw,16px);
}
.coll-eyebrow i{display:inline-flex;flex:none;}
.coll-eyebrow svg{width:12px;height:12px;fill:var(--gold-accent);}

/* ⚠️ TWO FAULTS IN ONE LABEL, BOTH MEASURED AT 393x852.

   THE .3em ABOVE NEVER REACHED A PHONE. The phone legibility block sets
   `.eyebrow{letter-spacing:.16em}` for a reason written down there — "the
   label runs wider than the column it labels" — and that reason is not true
   of this label. Measured: at .30em the whole thing, sparkle and gap
   included, is 217px inside a 353px column. 136px of slack. Both rules are
   (0,1,0) and the floor is further down the file, so it won, and the head
   documented as the one that leads the page carried tracking TIGHTER than
   the page default and tighter than .why-eyebrow's .22em — the opposite of
   what the pairing was for. `.coll-head .coll-eyebrow` is (0,2,0) and wins
   wherever either rule ends up sitting, which matters in a file this long.

   ⚠️ AND THE COLOUR IS NOT LEGIBLE ON THIS SECTION'S GROUND. --gold-ink
   clears 4.5:1 on the page cream; this section is not on the page cream.
   .section.collections paints its own peach (--agrav-bg-sm on a phone), and
   the pixels behind this label — sampled with the text hidden, not computed
   from the gradient's stops, because the head sits part way down a radial —
   are rgb(240,218,191). 12px/600 gold-ink on that is 3.68:1 against a 4.5
   requirement. #74580F is the same gold carried down until it clears:
   measured 4.9:1 on the same sampled pixels. Scoped to this head, because
   the page-wide gold question is open and is not this change's to settle. */
.coll-head .coll-eyebrow{
  letter-spacing:.3em;
  color:#74580F;
}
/* the mark is decoration and owes no ratio, but a pale sparkle beside a
   darker label reads as a different colour rather than the same one */
.coll-head .coll-eyebrow svg{fill:var(--gold-ink);}

.coll-title{
  /* the shared .section-title tops out at 44px; .why-title at 50. This sits
     just above both, which is the order the page should read in: the range is
     the largest thing on it.

     ⚠️ AND ON A PHONE IT WAS NONE OF THAT. 3.9vw does not overtake the 30px
     floor until 769px, so every phone got the floor and the floor is the
     page average — measured at 393: this 30px, .section-title 30px,
     .why-title 29.1px. The 52px in the note above and in the README is a
     desktop figure that was quietly doing all the work of the claim.

     9.7vw is the same shape of curve, steeper, and it caps at exactly the
     same 52 — so nothing above 536px moves. Below it: 31px at 320, 35px at
     360, 38px at 393. Measured against the column rather than guessed: the
     widest written line is 272px at 38px inside a 353px lede, and 42px still
     fits on two lines. The 30px floor now only bites under 310px. */
  font-size:clamp(30px,9.7vw,52px);
  line-height:1.08;
  letter-spacing:-.02em;
}
/* the same lift the hero gives "Close-Up".

   ⚠️ EXCEPT THAT ON THIS GROUND THE GOLD WAS NOT LEGIBLE. --gold-accent over
   the section's own peach, sampled rgb(247,227,204) behind these two words,
   is 2.36:1. 30px is already past the 24px large-text threshold, so 3:1 is
   the allowance that applies and it still fails — and no size increase can
   reach it, because the threshold was never the binding constraint.
   --gold-ink is 4.0:1 on the same sampled pixels.

   The italic is the elegance the accent did not have. Playfair's italic is a
   REAL cut here and not a synthesised slope — document.fonts.check says yes
   for both 400 and 500, and the advances confirm it independently ("Your
   Skin" at 40px: 172.3px roman against 169.8px italic; a skew leaves advances
   untouched). That gives the accent a second axis besides colour, which is
   the only thing separating this title from every other gold-emphasis title
   on the page. */
.coll-title em{
  font-style:italic;
  font-weight:500;
  color:var(--gold-ink);
}

/* The old 74px bar, kept and extended: solid for its original length, then
   the sparkle, then a hairline that fades out. Reading left to right it
   starts exactly where the bar used to and simply carries on. */
.coll-rule{
  display:flex;align-items:center;
  gap:clamp(9px,1vw,14px);
  /* 430 is .why-rule width. At 320 the fade had nowhere to go — the
     hairline was transparent within about 35px of the sparkle and the whole
     flourish read as a stub. Same length as its sibling, so the two heads
     carry the same measure. */
  width:min(430px,92%);
  margin:clamp(14px,1.7vw,24px) 0 clamp(12px,1.4vw,18px);
}
.coll-rule i{height:1px;}
.coll-rule i:first-child{
  flex:0 0 clamp(40px,4.6vw,74px);
  background:rgba(198,164,85,.7);
}
.coll-rule i:last-child{
  flex:1 1 auto;
  background:linear-gradient(90deg,rgba(198,164,85,.7),rgba(198,164,85,0));
}
.coll-rule svg{
  flex:none;
  width:clamp(12px,1vw,15px);height:auto;aspect-ratio:1;
  fill:var(--gold-accent);
}

/* THE DECK. One flat sans paragraph was carrying two different jobs — the
   idea and the housekeeping — at one size, and that size was the smallest
   type in the section. Split, the display italic takes the idea and the sans
   note below takes the errand. Same family as the title, one cut away, which
   is what makes it read as part of the heading rather than as the start of
   the body copy. */
.coll-deck{
  font-family:var(--f-display);
  font-style:italic;
  font-weight:400;
  font-size:clamp(17px,5.1vw,22px);
  line-height:1.34;
  letter-spacing:.004em;
  color:var(--ink);
  max-width:34ch;
  margin-bottom:clamp(7px,.9vw,11px);
  /* progressive: where it is unsupported the line simply breaks where it
     always did, which is what it does here today anyway — this is insurance
     for the day the client's own words are longer than these */
  text-wrap:balance;
}

.coll-sub{
  /* ⚠️ 13.5px WAS THE FLOOR AND THE PHONE ALWAYS GOT THE FLOOR — 1.05vw does
     not overtake it until 1286px, so the smallest run of type in the section
     was also its only description. 15px changes nothing a desktop sees (at
     1440 the vw term already gives 15.1px) and lifts every phone off the
     bottom of the clamp. */
  font-size:clamp(15px,1.05vw,16px);
  line-height:1.62;
  color:var(--ink-2);
  max-width:44ch;
  /* it ends on one word otherwise — measured at 1440, the second line was
     "store." on its own, and at 393 it was "the store." */
  text-wrap:balance;
}

/* It is the only way off this section, and it was set in the body sans at
   13px in the body colour — the same face, near enough the same size and
   exactly the same ink as the note two lines above it. Measured 9.21:1, so
   nothing is wrong with the colour; what was missing is any sign that it is
   a different KIND of thing. A gold hairline under it is enough, and the
   rule is decoration, so it owes no ratio of its own. */
.coll-head .link-arrow{border-bottom-color:rgba(198,164,85,.55);}

/* Under 900 the head has already wrapped and the link is on its own row, so
   the two are no longer sharing a baseline and flex-end has nothing to do. */
@media (max-width:900px){
  .coll-head{align-items:flex-start;}
  .coll-sub{max-width:none;}
}

/* ⚠️ THE PHONE FLOURISH WAS CHECKED AND LEFT ALONE. It looks in a screenshot
   like a bar, a dot and a long empty gap, and it was shortened here on that
   basis before anyone measured it. Measured — a 9px strip photographed
   through the rule, walked column by column, counting where the darkest
   pixel stops being at least 3 levels below the peach behind it:

     width 92% (this)   fade 255px long, painted for 247px  — 97%
     width 66% (mine)   fade 163px long, painted for 156px  — 96%

     levels below ground: +0px 43.8  +40px 37.3  +80px 30.5  +160px 16.9

   160px past the sparkle it is still 16.9 levels down. The hairline is faint
   by design and it paints essentially all the way; shortening it changed one
   percentage point and cost 92px of flourish. Don't fix this from a
   screenshot — scratchpad/rulefade.js takes the measurement, and it will
   take the counterfactual too if you pass it a width to try. */
/* ⚠️ The tight bottom padding above only makes sense while a full-bleed
   photograph starts immediately under the heading. With the hero lifted out
   there is nothing to butt against, and 46px leaves the head crowding the
   next section — measured: the whole section collapses to 237px and the gap
   to .why is 0.

   :has() decides it from the markup rather than from a flag, so pasting the
   hero back into index.html restores the tight value on its own and there is
   nothing to remember. Where :has() is unsupported the tight value stands,
   which is only a cosmetic difference. */
@supports selector(:has(*)){
  .collections:not(:has(.prodhero, .agrav)) > .shell{
    padding-block:clamp(56px,7vw,96px);
  }
}
/* ---------- ANTIGRAV: the lineup, one backdrop, products in a zigzag ----------

   Four products on their own pinned viewport came to 600svh — about eight
   screens of scrolling to see the range. This is the same idea laid out
   differently: each product keeps its own place on the page, alternating left
   and right, and its elements arrive as it comes into view. Nothing is pinned
   and nothing is scrubbed, so four products cost about two and a half screens
   instead of eight.

   What did NOT change, because it is the concept:

     * ONE backdrop under all of them. Each photograph is masked to a soft
       ellipse around its own product and everything outside is a single CSS
       gradient — which is why product 2's lighting had to be matched to
       product 1's before any of this worked.
     * the elements are the reveal: the artwork fades up out of a blur with an
       8px lift, and it is the only movement in the section
     * a product never moves, scales or rotates as you scroll

   ⚠️ THE ARTWORK'S OWN WORDS SET THE MINIMUM WIDTH. Rendered at 1:1, its chip
   labels are unreadable at 392px and legible at 660px, so the scene is held at
   min(52vw, 720px) rather than made as small as it could be. The copy beside
   it carries the product's NAME, not its benefits — the artwork already has
   those, and printing them twice would be reading the same list back. */
.agrav{
  position:relative;
  z-index:1;              /* above the field canvas, which is the section's first child */
}
.agrav-stage{
  position:relative;
  /* the cream comes from .section.collections now, so the whole section —
     heading included — is one surface with no seam across it */
  padding:clamp(28px,5vw,72px) 0 clamp(36px,6vw,88px);
  /* ⚠️ clip, NOT hidden — AND THE DIFFERENCE WAS COSTING A FEATURE.
     overflow:hidden makes an element a scroll container, and a sticky
     descendant then sticks to THAT container's scrollport, which never
     scrolls. So .agrav-hold, declared sticky for the two-beat reveal, simply
     did not stick: measured, the product drifted 422px across its band, the
     exact distance it was supposed to hold still for.

     ⚠️ AND THE FILE ALREADY KNEW. The markup carries "DO NOT PUT
       overflow:hidden ON .collections — that is the exact thing that broke
       it", written about the field canvas sticking. The canvas is a sibling
       OUTSIDE this box so it was never affected, which is why the same fault
       could sit here unnoticed until something inside the stage needed to
       stick.

     overflow:clip clips identically and creates no scroll container. Nothing
     else about this box changes. */
  overflow:clip;
}

/* ⚠️ STICKY, and its own height pulled back out of the flow. Absolute and
   inset:0 would make the canvas as tall as the whole section — four bands, so
   roughly two and a half viewports of fill to redraw. Sticky keeps it one
   viewport whatever the section grows to, and the negative margin means it
   takes no space in the column. */
.agrav-field{
  position:sticky;
  top:0;
  z-index:0;
  display:block;
  width:100%;
  height:100svh;
  margin-bottom:-100svh;
  pointer-events:none;
}

/* ---------- a band ---------- */
.agrav-slide{
  position:relative;
  z-index:1;
  display:grid;
  /* ⚠️ Not an even split. The copy beside the picture is a product name and
     one line under it; the picture is the thing anyone came to look at, and
     its artwork has words of its own that have to stay legible. */
  grid-template-columns:minmax(0,1.3fr) minmax(0,0.7fr);
  align-items:center;
  gap:clamp(16px,3vw,56px);
  padding:clamp(14px,2.4vw,34px) clamp(16px,4vw,72px);
  max-width:1440px;
  margin-inline:auto;
}
/* ⚠️ nth-of-type, not nth-child: the field canvas is the stage's first child,
   so nth-child would count it and put every product on the same side. Typed on
   the element, the divs number from one and the alternation is right.

   ⚠️ And the TEMPLATE flips, with both items placed explicitly. Doing this
   with order: alone was a bug that only appeared once the columns stopped
   being equal — order changes when an item is laid out, not which column it
   lands in, so every even band's picture was being put in the 0.7fr column and
   drawn at half size. */
/* ⚠️ display:contents — THE BOX IS NOT SUPPOSED TO EXIST HERE. .agrav-hold
   wraps each band's scene and its words so a phone can pin the pair together.
   On a desktop that wrapper would make them ONE grid item and collapse the
   two-column zigzag, so it is removed from layout entirely and .agrav-scene /
   .agrav-notes-slide go on being direct grid items of .agrav-slide.
   Every selector that reaches them uses a descendant combinator, never `>`,
   so nothing had to change to accommodate it — checked before wrapping. */
.agrav-hold{display:contents;}

.agrav-scene{grid-column:1;grid-row:1;}
.agrav-notes-slide{grid-column:2;grid-row:1;}
.agrav-slide:nth-of-type(even){grid-template-columns:minmax(0,0.7fr) minmax(0,1.3fr);}
.agrav-slide:nth-of-type(even) .agrav-scene{grid-column:2;}
.agrav-slide:nth-of-type(even) .agrav-notes-slide{grid-column:1;text-align:right;}

.agrav-scene{
  position:relative;
  width:100%;
  max-width:min(58vw, 840px);
  aspect-ratio:1672 / 941;
  justify-self:center;
}
.agrav-base{position:absolute;inset:0;z-index:2;}
.agrav-veil{
  position:absolute;
  inset:0;
  z-index:3;
  /* the artwork is drawn a little inside the frame so its outer bubbles do not
     sit against the edge of the scene */
  --veil-scale:0.96;
  transform:scale(var(--veil-scale));
}
.agrav-base img,
.agrav-veil img{display:block;width:100%;height:100%;}
.agrav-base img{object-fit:cover;object-position:50% 50%;}
.agrav-veil img{object-fit:contain;}

/* ⚠️ Each ellipse is centred on ITS OWN product, measured, not on the frame.
   Bottle 1 sits at cols 713..948 and rows 76..863 — centre 49.6%, 49.4%.
   Bottle 2 at 710..977 and 76..918 — centre 50.5%, 52.2%, and slightly larger.
   The solid stop clears the glass; the fade lands in the band where the
   photographs agree. */
.agrav-slide[data-agrav-slide="0"] .agrav-base{
  -webkit-mask-image:radial-gradient(ellipse 21% 53% at 49.6% 49.4%, #000 78%, transparent 100%);
  mask-image:radial-gradient(ellipse 21% 53% at 49.6% 49.4%, #000 78%, transparent 100%);
}
.agrav-slide[data-agrav-slide="1"] .agrav-base{
  -webkit-mask-image:radial-gradient(ellipse 23% 56% at 50.5% 52.2%, #000 78%, transparent 100%);
  mask-image:radial-gradient(ellipse 23% 56% at 50.5% 52.2%, #000 78%, transparent 100%);
}
/* ⚠️ Bright Rx is a JAR: measured, 316px wide by 607 tall against the Line
   Silencer's 237 by 788. Its ellipse is wider and shorter to match. It can
   afford to be generous — this photograph's backdrop already agrees with
   product 1's to within five levels across the whole frame. */
.agrav-slide[data-agrav-slide="2"] .agrav-base{
  -webkit-mask-image:radial-gradient(ellipse 24% 48% at 49.6% 51.7%, #000 78%, transparent 100%);
  mask-image:radial-gradient(ellipse 24% 48% at 49.6% 51.7%, #000 78%, transparent 100%);
}
/* ⚠️ THE WIDEST ELLIPSE OF THE FOUR, because this is the widest product: a
   411px supplement bottle against the Line Silencer's 241 and the Centella's
   270. Same construction as the others — the solid stop clears the glass
   (0.78 x 31.4% of 1672 = 410px against a 205px half-width) and the fade lands
   outside it. It reaches from 18% to 81% of the frame, so it still closes well
   inside the picture. */
.agrav-slide[data-agrav-slide="3"] .agrav-base{
  -webkit-mask-image:radial-gradient(ellipse 31.4% 54.2% at 49.7% 51.9%, #000 78%, transparent 100%);
  mask-image:radial-gradient(ellipse 31.4% 54.2% at 49.7% 51.9%, #000 78%, transparent 100%);
}

/* ⚠️ REGISTRATION. The products were photographed at different distances, and
   although they no longer cross-fade in one place, a lineup in which one
   bottle is 6% taller than the next reads as a mistake. Measured in the
   masters: the Centella is 843px tall against the Line Silencer's 788 and sits
   12.5px to the right; the jar is 16.5px low and 8.7px right.

   These are STATIC — nothing here is driven by scroll — and they sit on the
   SCENE so that each product's artwork takes the same correction and stays
   registered to the product it describes. */
.agrav-slide[data-agrav-slide="1"] .agrav-scene{
  --reg-x:-0.719%;
  --reg-y:-2.740%;
  --reg-scale:0.93476;
  transform:translate(var(--reg-x), var(--reg-y)) scale(var(--reg-scale));
}
.agrav-slide[data-agrav-slide="2"] .agrav-scene{
  --reg-x:-0.518%;
  --reg-y:-1.753%;
  transform:translate(var(--reg-x), var(--reg-y));
}
/* ⚠️ ALMOST NOTHING TO CORRECT, and that is the measurement, not an omission.
   The Collagen was photographed at 797px tall against the Line Silencer's 789
   — 1% apart before anyone touched it — so the scale is 789/797. The Centella
   needed 0.935 and the difference was plain to the eye; this one would not
   be, and it is applied anyway because a lineup is only as registered as its
   loosest member.
   ⚠️ The scale runs FIRST (transform lists apply right to left), so the
   vertical offset is taken against the already-scaled product: the bottle sits
   1.913% below the frame's centre, 1.894% after scaling, and the Line
   Silencer sits 0.213% above it — hence -2.107 and not -2.126. */
.agrav-slide[data-agrav-slide="3"] .agrav-scene{
  --reg-x:0.057%;
  --reg-y:-2.107%;
  --reg-scale:0.98996;
  transform:translate(var(--reg-x), var(--reg-y)) scale(var(--reg-scale));
}

/* ---------- the copy beside it ---------- */
.agrav-notes-slide{min-width:0;}
.agrav-lead{margin:0;}
.agrav-lead b{
  display:block;
  font-family:var(--f-display);
  font-weight:600;
  font-size:clamp(24px,3vw,42px);
  line-height:1.08;
  letter-spacing:-.01em;
  color:#2B1A12;
}
.agrav-lead span{
  display:block;
  margin-top:clamp(8px,1vw,14px);
  font-family:var(--f-body);
  font-size:clamp(11px,1vw,13px);
  letter-spacing:.24em;
  text-transform:uppercase;
  color:#8A7360;
}

/* ⚠️ The benefits are in the document at every size but shown only on a phone.
   On a desktop the artwork carries them, legibly, at the width the scene is
   held to — and printing them beside it would be the same list twice. Not
   display:none: they are the product's claims, and a screen reader needs them
   whatever the artwork is doing. */
.agrav-notes{
  position:absolute;
  width:1px;height:1px;
  margin:0;padding:0;
  overflow:hidden;
  clip-path:inset(50%);
  white-space:nowrap;
  list-style:none;
}

/* ---- with the script ----
   Everything above is the resting state AND the no-script state: every product
   with its elements already on it, one band after another, nothing hidden and
   nothing moving. The reveal is the enhancement.

   ⚠️ FIVE THINGS ARRIVE NOW, AND THEY ARRIVE IN ORDER. Before this, the bottle
   was ALREADY THERE when you got to it and only the elements faded on — so
   half the picture never transitioned at all, and the band was finished .95s
   after it started. Now the product itself comes into focus first, the
   elements settle onto it, then the name, then the line under the name. About
   1.6s from the first movement to the last, which is what "more transition"
   is: not a faster fade, more things happening one after another.

   ⚠️ THE SCENE ENTERS FROM ITS OWN SIDE. Products on the left slide in from
   the left, products on the right from the right — the zigzag becomes the
   rhythm of the reveal instead of only a layout. On a phone there is one
   column and no sides, so this is turned off there.

   ⚠️ `translate:`, NOT `transform:`, ON THE SCENE. Products 2 and 3 carry a
   registration `transform` that keeps the lineup the same size — writing
   `transform` here would silently throw it away and the Centella would jump
   back to being 6% taller than the rest. The individual `translate` property
   composes with `transform` instead of replacing it.

   ⚠️ EVERY `filter` HERE IS CLEARED IN JS WHEN IT LANDS. A finished
   `blur(0px)` is still a filter: it holds a promoted layer for the life of
   the page, and this is now two per band rather than one. Section `antiGrav`
   clears both on transitionend. */
:root{
  --agrav-ease:cubic-bezier(.22,.61,.36,1);
  /* ⚠️ HOW FAR #about CLIMBS OVER WHAT PRECEDES IT, and the hero has to pay
     for it. #about sets margin-top:calc(-1 * var(--lift-rise)); .hero adds
     the same number to its own bottom padding, so the clearance that is
     actually left is the hero's plain padding-bottom and nothing has to be
     tuned against anything.

     ⚠️ There used to be a second half, --lift-gap, which was the room made
     under .hero-facts — the pill-shaped fact bar that closed the hero. The
     bar was removed on request; the variable went with it rather than being
     left declared and unused, because the next reader would have had to work
     out which of two numbers the layout still depended on. Anything that
     still names --lift-gap in a comment is describing the old arrangement. */
  --lift-rise:clamp(30px,3.4vw,64px);
}

/* 1 — the product comes into focus, from its own side */
.is-antigrav .agrav-scene{
  translate:var(--agrav-from, -7%) 0;
  transition:translate 1.2s var(--agrav-ease);
}
.is-antigrav .agrav-slide:nth-of-type(even) .agrav-scene{--agrav-from:7%;}
.is-antigrav .agrav-base{
  opacity:0;
  filter:blur(7px);
  transition:opacity 1.2s var(--agrav-ease),
             filter 1.2s var(--agrav-ease);
}
.is-antigrav .agrav-base img{
  transform:scale(1.06);
  transition:transform 1.2s var(--agrav-ease);
}

/* 2 — the elements settle onto it, a quarter of a second behind */
.is-antigrav .agrav-veil{
  opacity:0;
  filter:blur(11px);
  transition:opacity 1.35s var(--agrav-ease) .25s,
             filter 1.35s var(--agrav-ease) .25s;
}
.is-antigrav .agrav-veil img{
  transform:translate3d(0,22px,0) scale(1.035);
  transition:transform 1.35s var(--agrav-ease) .25s;
}

/* 3 and 4 — the name, then the line under it.
   ⚠️ The two lines are staggered SEPARATELY and the block itself no longer
   moves. Moving the whole block was one gesture; this is two, and the second
   one is what makes the name look written rather than pasted. */
.is-antigrav .agrav-lead b{
  opacity:0;
  transform:translate3d(0,26px,0);
  transition:opacity .95s var(--agrav-ease) .5s,
             transform .95s var(--agrav-ease) .5s;
}
.is-antigrav .agrav-lead span{
  opacity:0;
  transform:translate3d(0,16px,0);
  transition:opacity .95s var(--agrav-ease) .68s,
             transform .95s var(--agrav-ease) .68s;
}

/* 5 — the light that arrives with it.
   ⚠️ A ONE-SHOT ANIMATION WITH NO `forwards`, ON PURPOSE. It ends where it
   started — at opacity 0 — so nothing is held on the element afterwards and
   there is no third layer left promoted per band. It sits BEHIND the
   photograph (z-index 1 against the base's 2), so what you see is a halo
   opening around the bottle and not a wash over it. */
.agrav-scene::before{
  content:"";
  position:absolute;
  inset:-8%;
  z-index:1;
  pointer-events:none;
  opacity:0;
  background:radial-gradient(ellipse 44% 48% at 50% 50%,
    rgba(198,164,85,.40) 0%,
    rgba(198,164,85,.15) 46%,
    rgba(198,164,85,0) 72%);
}
.is-antigrav .agrav-slide.is-shown .agrav-scene::before{
  animation:agravBloom 1.9s var(--agrav-ease) .08s 1;
}
@keyframes agravBloom{
  0%  {opacity:0; transform:scale(.74)}
  34% {opacity:1}
  100%{opacity:0; transform:scale(1.18)}
}

/* ---- landed ---- */
.is-antigrav .agrav-slide.is-shown .agrav-scene{translate:0 0;}
.is-antigrav .agrav-slide.is-shown .agrav-base{opacity:1;filter:blur(0px);}
.is-antigrav .agrav-slide.is-shown .agrav-base img{transform:none;}
.is-antigrav .agrav-slide.is-shown .agrav-veil{opacity:1;filter:blur(0px);}
.is-antigrav .agrav-slide.is-shown .agrav-veil img{transform:none;}
.is-antigrav .agrav-slide.is-shown .agrav-lead b,
.is-antigrav .agrav-slide.is-shown .agrav-lead span{opacity:1;transform:none;}

/* 6 — and the way out of the band.
   The button is inline-level, so it needs no alignment of its own: it takes
   the column’s, which is right-aligned on the even products that sit on the
   other side of the zigzag.

   ⚠️ translate:, NOT transform:, FOR THE ARRIVAL, and the button’s own
     transitions are restated in the same declaration. .btn animates transform,
     box-shadow, background-color, border-color and colour for its hover; a
     bare `transition:opacity,transform` here would replace that list wholesale
     and the hover would land instantly for the rest of the page’s life. The
     arrival rides `translate` so the two never share a property.

   ⚠️ .86s IS THE NEXT BEAT, not a pause: the name arrives at .5s and the
     line under it at .68s. */
.agrav-inq{margin-top:clamp(14px,1.6vw,20px);}
.is-antigrav .agrav-inq{
  opacity:0;
  translate:0 14px;
  transition:opacity .95s var(--agrav-ease) .86s,
             translate .95s var(--agrav-ease) .86s,
             transform .35s var(--ease), box-shadow .35s var(--ease),
             background-color .3s var(--ease), border-color .3s var(--ease),
             color .3s var(--ease);
}
.is-antigrav .agrav-slide.is-shown .agrav-inq{opacity:1;translate:none;}

@media (max-width:900px){
  /* ⚠️ THE PHONE LAYOUT. A 390px screen cannot hold the artwork's two-across
     composition legibly — its body copy lands at about 5px — so each product
     gets a portrait window of its own and its benefits become real text under
     it. Same words, same typography, same reveal. */
  /* ⚠️ ONE PRODUCT PER SCREENFUL, ASKED FOR DIRECTLY: "every slide is one
     product only so that makapag focus sa isang product kada scroll."
     Each band is given the whole viewport and its content is centred in it,
     so scrolling moves from one product to the next rather than sliding two
     past each other. It used to be padding-sized — about 300px a band, so a
     844px screen held one product and most of the next.

     ⚠️ svh, NOT vh. On a phone `vh` is the LARGEST viewport — the one with
       the browser chrome retracted — so 100vh is taller than the screen
       actually shows until you scroll, and the first product would sit with
       its name pushed under the address bar. svh is the small viewport, which
       is the one that is true on arrival.

     ⚠️ min-height, NOT height. The name and its sub-line sit under the
       artwork; a fixed height would clip them on a short landscape phone
       instead of letting the band grow. The tallest arrangement here is about
       320px, so on any upright phone there is room to spare and the minimum
       is what applies.

     ⚠️ NO SIDE PADDING ON THE BAND. The artwork is the widest thing here and
       it is held to the frame's full width — 18px either side was costing it
       36px of a 390px screen, which is 9% of the only dimension it has. The
       words get their own padding below instead. */
  .agrav-slide{
    position:relative;              /* the background blooms hang off this */
    grid-template-columns:minmax(0,1fr);
    /* ⚠️ 72svh, AND IT WAS 100svh. A full screen per band left the product
       floating in about 300px of nothing above and below it — asked for as
       "gap is too long". 72 is not a taste, it is the smallest value that
       still keeps the next product off screen when this one is at rest:

         band centre is snapped to the viewport centre
         the artwork sits ~39px above that (the name and sub-line below it
           pull the centred content block down)
         so the NEXT artwork's top lands at bandH - 149 from centre
         it clears a 844px screen when bandH - 149 > 422  ->  bandH > 571px

       571px is 67.7svh; 72svh is 608px, which keeps 37px of margin for the
       products whose names wrap to two lines. Below about 68svh the next
       bottle starts peeking and the one-per-screen ask breaks. */
    /* ⚠️ TWO BEATS PER PRODUCT, SO THE BAND IS TALLER THAN THE SCREEN AGAIN —
       but for travel this time, not for empty space. Asked for: "kada scroll
       ang mauuna is the product itself, then second scroll is elements naman."

       150svh = one screen of pinning plus half a screen of travel. The hold
       below sticks for the first 100svh, so the product arrives bare and
       stays put while the remaining 50svh of scrolling brings its elements
       in. The band then releases and the next product comes up.

       ⚠️ align-content:start, NOT center. The hold is the thing that centres
         the product now; centring the hold inside a band half a screen taller
         than it would give it 25svh of dead run at each end before the sticky
         ever engages. */
    min-height:150svh;
    align-content:start;
    padding:0;
    justify-items:stretch;
  }

  /* the pinned pair */
  .agrav-hold{
    display:grid;
    position:sticky;
    top:0;
    height:100svh;
    align-content:center;
    justify-items:center;
    gap:clamp(14px,3.6vw,22px);
    padding:clamp(18px,5vw,30px) 0 clamp(24px,6vw,36px);
  }
  /* keeps the artwork above the blooms added below — inside one stacking
     context ::after paints AFTER the children, so without this the lower
     bloom would sit on top of the bottle */
  .agrav-scene{z-index:1;}
  .agrav-notes-slide{padding-inline:18px;}

  /* ⚠️ SNAP, BECAUSE "kada scroll" IS ABOUT WHERE SCROLLING STOPS.
     A 100svh band gives one product per screen AT REST — measured, [1,1,1,1]
     parked on each slide. But scanning every scroll position rather than just
     the flattering ones, the worst case is TWO: the gap between artworks is
     844 - 220 = 624px and the screen is 844, so mid-scroll the next one's
     edge is on screen. Height alone cannot fix that — one product at every
     position needs a band of viewport + artwork = about 126svh, which buys
     the guarantee with another 200px of empty screen per product.

     Snapping fixes the thing that actually matters instead: it makes the
     centred position the one scrolling settles on, so two are only ever
     visible in passing.

     ⚠️ proximity, NOT mandatory. Mandatory snap on the ROOT scroller governs
       the whole document, and a reader who wants to scroll past this section
       to the FAQ should not have to fight four magnets to do it. Proximity
       engages only when the scroll ends near a product and never traps.
       Say the word if a harder snap is wanted — it is one keyword.

     ⚠️ align:center, not start. The header is sticky and 75px tall; a band
       snapped to its start would sit with its top under the header. */
  html{scroll-snap-type:y proximity;}

  /* ⚠️ THE BAND CANNOT SNAP, AND scroll-snap-align:center ON IT WAS A NO-OP.
     A snap AREA taller than the snapport is only required to CONTAIN it, so
     every resting position inside a 150svh band already counts as aligned.
     Measured at 390x844: parking 15% and 62% of the way through a band moved
     the page 0px. It is explicitly none so nothing reads the old value as
     intent — the beats are handled in main.js, where the condition "the scroll
     stopped inside the products" can actually be expressed.

     ⚠️ AND IT IS NOT DONE IN CSS FOR A MEASURED REASON. One-screen anchors on
     the two beats plus scroll-snap-type:mandatory put every one of 132 tested
     positions on a beat — and broke the header: from the second product, #home
     landed on 4681 instead of 1022, back-to-top never left the section, and two
     more links were dragged back. scroll-snap-type belongs to the scroll
     container, which here is the document, so there is no scoping it to a
     section; and the browser applies the snap inside the scroll operation,
     before any frame of ours could take a class off. */
  .agrav-slide{scroll-snap-align:none;}

  /* ⚠️ THE GOLD CURVES COME OFF ON A PHONE, ASKED FOR DIRECTLY.
     .agrav-fil is four hairlines on a 1440x2400 viewBox with
     preserveAspectRatio:none — they are drawn against a wide, shallow section
     and stretched to a narrow, very tall one, which is why on a phone they
     stop reading as a sweep behind the products and become two near-vertical
     lines running down the outside of the screen. It is inline SVG, so
     hiding it costs no request and saves four scroll-driven dashoffset
     recalculations per frame. */
  .agrav-fil{display:none;}

  /* ⚠️ THE ARTWORK IS DRAWN AT FULL SIZE HERE. --veil-scale is 0.96 on a
     desktop so the outer bubbles do not sit against the edge of a scene that
     is inset in a wide column. On a phone the scene IS the screen, so there
     is no edge to stand off from — and 4% is the only enlargement available:
     all four veils were scanned column by column and the tightest margin
     before solid content is 0.2%, so the frame itself cannot be zoomed
     without clipping a callout. This recovers the inset instead. */
  .agrav-veil{--veil-scale:1;}

  /* ⚠️ BACKGROUND ONLY WHERE THE PRODUCT IS NOT, AND THAT IS A HARD RULE
     RATHER THAN A PREFERENCE. The field's own note in the markup says the
     cream immediately beside each bottle is matched to the photograph's own
     backdrop to within four or five levels and NOTHING may be laid over it,
     or the seam between artwork and page comes back. Asked for the same
     thing in the same words: "make sure na hindi mag-overlap sa product".

     So these live in the band's top and bottom fifths only. The artwork is
     220px of a 608px band — about 36% through the middle — and each bloom
     stops 8% clear of it at both ends. They also carry the section's own
     scroll parallax (--agrav-p, the same term .agrav-drops reads) at a
     shallower rate, so they drift behind the bubbles rather than with them,
     and they cost no animation: there are already 86 on this page. */
  /* ⚠️ ON THE HOLD, NOT THE BAND. These were anchored to .agrav-slide, which
     is now 150svh — their top and bottom fifths would sit half a screen away
     from the product they are meant to sit behind, and half of it would be
     off screen while the product is pinned. The hold is the box the product
     actually occupies, so they belong to it. */
  .agrav-hold::before,
  .agrav-hold::after{
    content:"";
    position:absolute;
    left:-6%;right:-6%;
    /* ⚠️ 17%, MEASURED BACK FROM THE CLEARANCE. At 21% and 3% inset the upper
       bloom finished 9px above the artwork — technically clear, and one
       product name wrapping to a second line would have closed it. 17% at 2%
       leaves 39px at both ends, which survives that. */
    height:17%;
    z-index:0;
    pointer-events:none;
    background:
      radial-gradient(52% 100% at 50% 50%, rgba(230,199,124,.26) 0%, rgba(230,199,124,0) 72%),
      radial-gradient(26% 60% at 22% 42%, rgba(198,164,85,.16) 0%, rgba(198,164,85,0) 76%),
      radial-gradient(22% 56% at 79% 58%, rgba(198,164,85,.13) 0%, rgba(198,164,85,0) 76%);
    transform:translate3d(0, calc((var(--agrav-p,.5) - .5) * -26px), 0);
  }
  .agrav-hold::before{top:2%;}
  .agrav-hold::after{bottom:2%;}
  /* they alternate their lean so four bands do not read as one repeated tile */
  .agrav-slide:nth-of-type(even) .agrav-hold::before,
  .agrav-slide:nth-of-type(even) .agrav-hold::after{scale:-1 1;}

  /* =======================================================
     BEAT TWO — the elements

     --beat is written per band by agravScroll in main.js: 0 while the band's
     top is at the top of the screen (product pinned, bare) and 1 by the time
     the band has travelled its extra half-screen.

     ⚠️ THIS OVERRIDES THE ENTRANCE, IT DOES NOT JOIN IT. The base rules fade
       the veil in with .is-shown the moment the band arrives, which is the
       thing being replaced — the product and its elements used to land
       together. Both the resting and the shown state are restated here or
       the .is-shown rule, at (0,3,0), would pin opacity back to 1 and the
       beat would drive nothing.

     ⚠️ AND THE TRANSITION GOES. A transition on opacity fights a value that
       is already changing every frame — it would lag the scroll by its own
       duration and never quite arrive.

     ⚠️ var(--beat, 1) DEFAULTS TO ONE, NOT ZERO. With no script, with the
       driver idle off-screen, or under reduced motion, the elements must be
       PRESENT. A default of 0 would mean a scriptless phone sees four
       products with no callouts at all. */
  .is-antigrav .agrav-slide .agrav-veil,
  .is-antigrav .agrav-slide.is-shown .agrav-veil{
    opacity:clamp(0, (var(--beat,1) - .12) * 2.6, 1);
    filter:none;
    transition:none;
  }
  /* the words belong to the first beat with the product, so they are left
     entirely alone — only the artwork waits */
  /* the alternation is a two-column idea; in one column it would only make
     every other product's words hug the wrong edge — so every item goes back
     into the single column and takes its own row */
  .agrav-slide:nth-of-type(even){grid-template-columns:minmax(0,1fr);}
  .agrav-scene,
  .agrav-slide:nth-of-type(even) .agrav-scene{grid-column:1;grid-row:auto;}
  .agrav-notes-slide,
  .agrav-slide:nth-of-type(even) .agrav-notes-slide{grid-column:1;grid-row:auto;text-align:center;}

  /* ⚠️ THE PHONE SHOWS THE DESKTOP COMPOSITION NOW, ASKED FOR DIRECTLY:
     "same as kung anong transitions meron sa POV sa PC — add the elements
     then remove the text here since hindi ganito ang ginawa natin."

     What used to be here was a portrait window — width:min(60vw,245px) and
     aspect-ratio:0.52 — a crop onto the middle of a 1672x941 landscape frame,
     because that is where the bottle is. That crop is why the veil could not
     come with it: the artwork's callouts sit to the LEFT and RIGHT of the
     bottle, which is exactly what a portrait window removes. Showing the veil
     inside the old crop would have drawn the bottle's own bubbles and nothing
     else. So the frame goes back first, and the elements follow.

     ⚠️ ONLY max-width IS OVERRIDDEN. width:100% and aspect-ratio:1672/941 are
       the base rule and are now correct again, so they are not restated — but
       the base caps the scene at min(58vw,840px), and 58vw on a 390px phone
       is 226px, smaller than the crop this replaces. The cap has to go or the
       full frame ends up narrower than the window it replaced.

     ⚠️ AND THE TWO MASK OVERRIDES WENT WITH IT. The desktop ellipses on
       .agrav-base are measured against the whole 1672px frame; they were
       cancelled here with !important precisely because the frame was not
       whole. It is whole again, so they are right again — and the phone's own
       two-gradient rectangle mask, which existed to soften the crop's four
       hard edges, has nothing left to soften. Both removed rather than left
       to fight the ellipses. */
  .agrav-scene{max-width:none;}
  /* the phone's own key, moved up with the ground it belongs to */
  .section.collections{background:var(--agrav-bg-sm);}
  .agrav-stage{
    padding:clamp(20px,6vw,34px) 0 clamp(28px,8vw,44px);
  }
  /* ⚠️ THE VEIL IS SHOWN HERE NOW. This line read "its only unique content is
     the text, and that text is unreadable here" and hid it, and that
     measurement has not changed — the artwork's body copy renders at about
     5px on a 390px screen. It was asked for anyway, with the text version
     removed, so the artwork is the section's content on a phone exactly as it
     is on a desktop. The README carries the measured size; this is a
     deliberate trade, not an oversight, and anyone reversing it should read
     that first rather than re-deriving it. */

  /* ⚠️ NO SIDES TO COME FROM. The entrance slides each scene in from its own
     side of the zigzag; here there is one column, so it rises instead. The
     landed rule outscores this one (0,3,0 against 0,2,0), so it still lands. */
  .is-antigrav .agrav-scene,
  .is-antigrav .agrav-slide:nth-of-type(even) .agrav-scene{translate:0 22px;}
  /* ⚠️ THE BLOOM IS BACK TOO, AND FOR THE SAME REASON AS THE MASKS. It was
     switched off because the cropped photograph covered the whole scene with
     no ellipse, so the glow behind it had nowhere to show. The ellipse is
     back, so it does. */

  /* ⚠️ .agrav-notes IS SCREEN-READER-ONLY AGAIN, WHICH IS THE BASE RULE.
     The phone used to un-clip it into a real two-column list with its own
     entrance — that was the "text here" that was asked to go, because the
     artwork now carries the same words on a phone as it does on a desktop
     and printing them underneath would be the same list twice. The base rule
     keeps them in the document for a screen reader; nothing needs restating
     here, so the whole reveal and the whole visible treatment are gone rather
     than overridden back. */

  .agrav-notes-slide{text-align:center;width:100%;}
  .agrav-lead b{font-size:clamp(21px,5.6vw,26px);}
  .agrav-lead span{
    margin-top:6px;
    font-size:clamp(10.5px,2.9vw,12px);
    letter-spacing:.22em;
    color:#5B7A54;
  }

  /* ⚠️ THE WHOLE VISIBLE TREATMENT OF .agrav-notes WAS HERE AND IS GONE:
     a two-column grid, a display-face heading with a rule under it, a body
     line, and a separate pill variant for the products whose benefits are
     single words. It was good work and it is deleted rather than commented
     out, because a phone that shows the artwork does not need the same words
     printed underneath it — that duplication is the thing that was asked to
     go. It is recoverable from the README if the artwork is ever hidden on a
     phone again, which is the condition it was written for. */
}

/* ⚠️ The 14px lift that used to be here is gone with the thing it corrected.
   .has-rule hangs its gold bar in 14px of padding under the title, and
   align-items:flex-end lined the link up with the bottom of that padding
   rather than with the words, so the link needed lifting by exactly the
   padding the rule occupied. This head no longer uses .has-rule — the rule is
   a real element now, with the sub under it — and the same 14px would push
   the link off the bottom of a block it is supposed to sit level with. */
.section-tight{padding-block:clamp(24px,3vw,40px);}
.section-head{
  display:flex;align-items:flex-end;justify-content:space-between;gap:24px;
  margin-bottom:36px;flex-wrap:wrap;
}
.section-head .eyebrow{margin-bottom:12px;}

/* ---------- Product hero — temporary vertical strip transition ----------
   Scene A and Scene B are each ONE complete full-bleed composition. The five
   strips are a temporary mask over Scene A: they slide off in alternating
   directions, then get hidden outright. The stage is never a column layout —
   at both ends it is a single seamless photograph.

   This first block is the resting state and also the no-script state: the two
   scenes stacked, one full page each, strips inert. That stacking is already
   the right shape without JS — the enhancement below keeps the two pages and
   adds the sticky screen between them. Everything under "with the
   script" is the enhancement. */
.ph-scene{margin:0;display:block;height:100svh;}
.ph-scene img{display:block;width:100%;height:100%;object-fit:cover;}

/* ⚠️ WHERE THE COVER CROP LOOKS, PER SCENE.

   Both photographs are about 1.469 wide and the stage is about 2.13, so cover
   scales to the width and throws a third of the height away — at 1600x1089
   delivered, 751 of 1089 rows survive on a 1533x720 screen. Centring that
   window is wrong for both pictures, because neither composes its products in
   the middle of the frame.

   Measured on the delivered files (scratchpad/prodbounds.js):

     Scene A  bottles from row 318,  1075px master
     Scene B  bottles rows 312..838, plinth foot at row 1020, 1518px master

   ⚠️ The window has to clear the HEADER as well as hold the content. The bar
   is 89px of frosted glass sitting over the top of the stage — 93 source rows
   — so anything in the first 89px is washed out. An early pass at these
   numbers centred the content and ignored the bar, which sliced the dropper
   caps off every bottle. The values below put the caps below it instead.

   ⚠️ Do not measure "where the content is" with edge energy on Scene B. The
   palm shadow on that wall carries enough texture to register as content all
   the way to row 10, which reports a content block taller than the window and
   an object-position that still cuts the caps. It has to be measured by
   colour: the bottles are the only near-neutral things in a wholly warm frame.

   ⚠️ --pos-b is 58%, not the 70% the previous photograph used, and it was
   chosen at the SHORTEST wide viewport rather than the average one, because
   that is the case that fails. Swept across viewport heights:

     1533x720   caps clear the bar by 23px, plinth foot cut 73px
     1533x820   caps clear by 84px,         plinth foot cut 29px
     1533x920   caps clear by 144px,        plinth foot held
     1920x950   caps clear by 65px,         plinth foot cut 56px

   A trimmed plinth reads as the photograph continuing off-screen. Sliced
   dropper caps do not — so when the two compete, the caps win.

   ⚠️ --pos-b feeds Scene B AND every panel from one value, because the panels
   are cut from Scene B. That is deliberate: the whole seamless-assembly
   guarantee rests on the clones sharing that scene's scale AND position, and
   two separate declarations could drift apart in a later edit. --pos-a is
   Scene A's alone. If the direction of the transition is ever reversed so the
   panels carry Scene A again, this pairing has to move with it — ?ph=assemble
   is what will tell you. */
.prodhero{--pos-a:36%; --pos-b:58%;}
.ph-scene--a img,
.ph-slide img{object-position:50% var(--pos-a);}
/* ⚠️ THE PANELS FOLLOW SCENE B, BECAUSE THE PANELS ARE CUT FROM SCENE B.

   ⚠️ THIS PAIRING MUST FOLLOW WHATEVER main.js CLONES. The strips carry Scene
   A, so .ph-slide img is grouped with .ph-scene--a. When the direction was
   briefly inverted and the panels carried Scene B, leaving this line alone
   cropped every panel 22% away from the photograph behind it — the four
   agreed with each other perfectly and were all wrong against the scene they
   were landing on. ?ph=assemble caught it as a byte difference across the
   whole frame.

   Whatever the strips clone, this value and that scene MUST be the same value
   from the same custom property: the seamless-assembly guarantee rests on the
   clones sharing the scene's scale AND position, and two separate
   declarations could drift apart in a later edit. */
.ph-scene--b img{object-position:50% var(--pos-b);}
/* .ph-strips does not exist in the markup at all — main.js builds it when
   the transition starts and removes it when the transition ends. */

/* ---- with the script ---- */

/* TWO PAGES, ONE SCREEN IN THE MIDDLE.

   Each photograph gets a full viewport of scrolling to itself, and the two of
   them share one screen only for the length of the transition:

     pinned, 0 -> 100svh     Scene A, held for a full page
     at 100svh               the strips run, A -> B, one screen
     pinned, 100 -> 200svh   Scene B, held for a full page
     200 -> 300svh           the stage releases and scrolls away

   ⚠️ The height is 300svh, not 200svh, and the arithmetic is the reason. A
   sticky child pins for (section height - stage height), so a 200svh section
   pins for exactly ONE viewport — which has to cover Scene A's hold, the
   transition and Scene B's hold all together. Measured on that version: Scene
   A held for 216px and Scene B for 320px, both under half a screen. Two pages
   of hold need two viewports of pinning, so the section is three viewports:
   two pinned, one to scroll out on.

   ⚠️ The stage pins; the page does not jump and the header does not move.
   Sticky costs no scroll position — the reader keeps scrolling normally and
   the stage simply stops travelling while it is pinned. Measured: the header
   holds one Y value for the whole run and scrollWidth never exceeds
   clientWidth. */
.is-prodhero .prodhero{
  position:relative;
  /* One viewport of pinning = one viewport of scrolling to complete the
     transition. It was 300svh when the transition ran on a timer and each
     photograph needed its own held page; now that the reader drives it, the
     hold is whatever they choose to give it by not scrolling, and a 1440px
     scrub just meant a lot of wheel. */
  height:200svh;
}
.is-prodhero .ph-stage{
  position:sticky;
  top:0;
  width:100%;
  height:100svh;
  overflow:hidden;
}
.is-prodhero .ph-scene{position:absolute;inset:0;height:auto;}
.is-prodhero .ph-scene--b{z-index:0;}
.is-prodhero .ph-scene--a{z-index:1;}
.ph-strips{position:absolute;inset:0;z-index:2;}
/* ⚠️ The layer is built BEFORE the scrub starts, while the section is still
   approaching, so that the first notch of the wheel costs nothing. Until it is
   armed those four full-stage clones would otherwise be composited on top of a
   Scene A they match pixel for pixel — invisible, and paid for on every frame.
   Hidden until armed, they cost nothing and still exist. */
.prodhero:not(.is-armed) .ph-strips{visibility:hidden;}

/* ⚠️ WHICH SCENE IS HIDDEN, AND WHEN.

   The strips carry SCENE A and they LEAVE. Scene B is the floor: whole, still
   and visible for the entire scrub, uncovered from the top of each strip as
   the blue slides down past it.

   So Scene A hands over to the strips the moment they exist. They are clones
   of it, pixel for pixel, so that swap cannot flash — and it has to happen, or
   the untouched Scene A underneath would sit behind the strips and nothing
   would ever be uncovered.

   Once the strips are gone, Scene A stays hidden and Scene B is on its own. */
.prodhero.is-armed .ph-scene--a{visibility:hidden;}
.prodhero.is-settled .ph-scene--a{visibility:hidden;}

/* Every strip is the FULL stage box showing the FULL photograph — only the
   clip differs. Identical boxes mean identical object-fit:cover geometry, so
   nothing can drift out of register. The clip sits on the strip, which never
   moves; the slide inside it is what travels. */
.ph-strip{
  position:absolute;
  inset:0;
  /* --l and --r are written by main.js: the strip's own window onto the
     photograph — four equal quarters, in whole pixels. Every strip is the
     FULL stage box holding the FULL image at the same scale and position;
     only the window differs, which is why the four of them cannot drift out
     of register with one another. */
  clip-path:inset(0 var(--r) 0 var(--l));
}
/* ⚠️ main.js writes --l and --r in WHOLE PIXELS. A fractional clip edge is
   antialiased, so the boundary pixel gets painted by both neighbouring bands;
   two composites of the same colour round 1/255 away from the one composite a
   single image makes, and the assembly stops being byte-identical. Integer
   edges give each pixel to exactly one band — no partial coverage, no double
   paint, no hairline, and no overlap needed to hide one. */
/* ⚠️ NO TRANSITION HERE ON PURPOSE. main.js writes transform on every scroll
   frame, so the strips are wherever the wheel says they are. A CSS transition
   on top of that would lag a fixed time behind every write — the strips would
   trail the scroll rather than follow it, and would keep drifting after the
   reader stopped. The easing did not go away; it moved into JS as a
   cubic-bezier(.76,0,.24,1) evaluated per strip against scroll progress. */
.ph-slide{
  position:absolute;inset:0;
  /* translateY(0) is the RESTING state — the strip where Scene A drew it.
     main.js drives it from eased * 100%, always positive, so the strip travels
     DOWN off the bottom and the scrub ends with all four a full stage away.
     This is also the state ?ph=assemble holds, which is why that rig tests the
     start of the transition. */
  transform:translateY(0);
}
.ph-slide img{display:block;width:100%;height:100%;object-fit:cover;}

/* All four strips travel DOWN, B and D ahead of A and C. The timing lives in
   main.js: with a scrub it is arithmetic on a scroll position rather than an
   end state for CSS to transition to. */
.prodhero.is-moving .ph-slide{will-change:transform;}

/* ⚠️ Reduced motion is handled in main.js by standing the hero down entirely:
   is-prodhero is never added, so there is no pin and no scrub, just the two
   photographs stacked as two plain pages. A scrubbed transition cannot be made
   "instant" — it IS the scroll — so honouring the preference means not
   pinning at all rather than shortening anything. */

/* Below 4:3 the cover crop starts eating the end bottles, so the scenes keep
   their own shape and the whole thing stands down in main.js as well. */
@media (max-aspect-ratio:4/3){
  .ph-scene{height:auto;}
  /* the DELIVERED ratio, not either master's: the two masters are 1075x732 and
     1518x1036 but both ship as 1600x1089, and this box has to match the file
     the browser actually gets or the image letterboxes inside it */
  .ph-scene img{height:auto;aspect-ratio:1600 / 1089;}
}

/* ---------- Collections ---------- */
.collection-grid{
  display:grid;
  grid-template-columns:repeat(6,minmax(0,1fr));
  gap:18px;
}
.col-card{
  position:relative;
  display:flex;flex-direction:column;
  width:100%;
  text-align:left;
  font:inherit;color:inherit;
  padding:18px 18px 22px;
  background:var(--white);
  border:1px solid var(--line);
  border-radius:var(--r-md);
  cursor:pointer;
  overflow:hidden;
  transition:box-shadow .4s var(--ease), border-color .4s var(--ease);
}
.col-card:hover{
  box-shadow:var(--shadow-md);
  border-color:var(--gold-soft);
}
.col-ico{
  display:grid;place-items:center;
  width:34px;height:34px;
  border-radius:50%;
  background:var(--gold-tint);
}
.col-ico svg{
  width:16px;height:16px;
  fill:none;stroke:var(--gold-ink);stroke-width:1.5;
  stroke-linecap:round;stroke-linejoin:round;
}
.col-stage{
  display:grid;place-items:center;
  height:132px;margin:4px 0 16px;
}
.col-stage svg{
  height:100%;width:auto;
  filter:drop-shadow(0 14px 18px rgba(30,24,12,.1));
  transition:transform .55s var(--ease), filter .55s var(--ease);
}
.col-card:hover .col-stage svg,
.col-card:focus-visible .col-stage svg{
  transform:translateY(-8px) scale(1.06);
  filter:drop-shadow(0 22px 26px rgba(30,24,12,.16));
}
.col-name{
  display:block;
  font-family:var(--f-body);
  font-size:14.5px;font-weight:600;letter-spacing:-.005em;
  margin-bottom:6px;
}
.col-desc{
  display:block;
  font-size:12.5px;line-height:1.55;color:var(--ink-3);font-weight:300;
  margin-bottom:18px;
}
.col-cta{
  margin-top:auto;
  display:flex;align-items:center;justify-content:space-between;gap:8px;
}
.col-cta-text{
  font-size:11.5px;font-weight:500;letter-spacing:.06em;text-transform:uppercase;
  color:var(--gold-ink);
  opacity:0;transform:translateX(-6px);
  transition:opacity .35s var(--ease), transform .35s var(--ease);
}
.col-card:hover .col-cta-text,
.col-card:focus-visible .col-cta-text{opacity:1;transform:none;}
.col-arrow{
  margin-left:auto;
  color:var(--gold-ink);font-size:15px;
  transition:transform .3s var(--ease);
}
.col-card:hover .col-arrow{transform:translateX(4px);}

.tick-list{display:grid;gap:10px;margin-bottom:26px;}
.tick-list li{
  position:relative;
  padding-left:28px;
  font-size:13.5px;color:var(--ink-2);
}
.tick-list li::before{
  content:"";
  position:absolute;left:0;top:3px;
  width:17px;height:17px;border-radius:50%;
  background:var(--gold);
}
.tick-list li::after{
  content:"";
  position:absolute;left:5px;top:8.5px;
  width:7px;height:3.5px;
  border-left:1.6px solid #fff;border-bottom:1.6px solid #fff;
  transform:rotate(-45deg);
}

/* ---------- Story (the introduction) ---------- */
/* ---- #about SITS ON TOP OF THE HERO ----
   ⚠️ THE SHADOW IS THE EFFECT. On a full-bleed panel the rounded corners only
   show in the last few pixels at either edge, so they alone are far too quiet
   to separate two sections. What the eye actually reads is the darkening
   thrown UP onto the hero's ground just above the seam — a negative y offset,
   wide blur, heavy negative spread so it stays a bloom and never a band.
   ⚠️ overflow:hidden is what clips the silk to the radius; the box-shadow is
   NOT clipped by it, which is why the shadow can fall outside the panel. */
.story{
  position:relative;
  overflow:hidden;
  z-index:3;
  margin-top:calc(-1 * var(--lift-rise));
  border-radius:clamp(20px,2.4vw,42px) clamp(20px,2.4vw,42px) 0 0;
  box-shadow:
    0 -26px 64px -34px rgba(120,96,44,.50),
    0 -3px 14px -8px rgba(120,96,44,.16);
}
/* the lit top edge. A hairline inside the panel, brighter than the silk it
   sits on, so the boundary is a real edge rather than where one colour stops
   and another starts. It follows the radius because it is drawn as a border
   on the same box. */
.story::before{
  content:"";
  position:absolute;inset:0 0 auto;
  height:1px;
  z-index:4;
  background:linear-gradient(90deg,
    rgba(255,255,255,0) 0%,
    rgba(255,255,255,.95) 18%,
    rgba(255,255,255,.95) 82%,
    rgba(255,255,255,0) 100%);
  pointer-events:none;
}
.story > .shell{position:relative;z-index:2;}

/* Ivory silk, per the comp: light pooling top-left and bottom-right, with
   folds running across it. Two repeating gradients at different angles and
   periods beat against each other, so the folds never fall into an obvious
   stripe. All gradient — no image, no extra request. */
.story-silk{
  position:absolute;inset:0;
  z-index:0;
  pointer-events:none;
  background:
    radial-gradient(54% 48% at 16% 10%, rgba(255,255,255,.92) 0%, rgba(255,255,255,0) 64%),
    radial-gradient(48% 46% at 90% 76%, rgba(255,253,247,.82) 0%, rgba(255,253,247,0) 66%),
    repeating-linear-gradient(114deg,
      rgba(206,189,153,0)    0px,
      rgba(202,183,144,.13)  74px,
      rgba(255,255,255,.46)  148px,
      rgba(206,189,153,0)    246px),
    repeating-linear-gradient(72deg,
      rgba(206,189,153,0)    0px,
      rgba(206,189,153,.05)  120px,
      rgba(255,255,255,.20)  214px,
      rgba(206,189,153,0)    372px),
    linear-gradient(158deg, #FCF8F0 0%, #F4EBDB 46%, #FDFAF4 100%);
}

/* Oversized quote mark bled off the left edge — the section's watermark.
   It sits on the silk, under the copy. */
.story-mark{
  position:absolute;left:-1.5%;top:2%;
  z-index:1;
  font-family:var(--f-display);
  font-size:clamp(190px,26vw,380px);
  line-height:.78;
  /* ⚠️ --gold-ink, AND IT WAS --taupe. Taupe is a NEUTRAL, so at any opacity
     low enough to stay behind the copy it reads as a smudge rather than as a
     mark — raising it from .05 to .16 bought 1.085:1 against the ground and
     the answer was still "I cannot see it". The colour was the problem, not
     the amount of it. --gold-ink is the deepest gold in the token block and it
     is the page own precedent for a quote glyph (.rdr-quote takes --gold).
     No new colour: BRAND.md forbids it and none was needed. */
  color:var(--gold-ink);
  /* ⚠️ .05 WAS EFFECTIVELY NOTHING, AND IT WAS ASKED TO BE CLEARER. Taupe at
     five per cent over this section ground is about one level of difference —
     the mark was in the markup, drifting on a 40s animation, and visible to
     nobody. .16 makes it read as a deliberate watermark.
     ⚠️ IT SITS BEHIND THE COPY, so raising it darkens the ground under any
     text that crosses it. Every text element in #about was measured against
     the RENDERED pixels after this change, not reasoned about — that is the
     same mistake .rt-glow made in routine.css, where a decorative bloom took
     one step label to 4.43:1 against the 4.5 it owed. */
  opacity:.30;
  pointer-events:none;
  user-select:none;
}
.story-top{
  position:relative;
  display:grid;
  grid-template-columns:minmax(0,.86fr) minmax(0,1.14fr);
  gap:clamp(28px,4vw,64px);
  align-items:center;
  margin-bottom:clamp(34px,4.5vw,58px);
}

/* Eyebrow with a rule running out of it on both sides */
.rule-eyebrow{
  display:flex;align-items:center;gap:12px;
  margin-bottom:16px;
  font-size:11px;font-weight:600;
  letter-spacing:.22em;text-transform:uppercase;
  /* ⚠️ #6E5514, not --gold-ink. --gold-ink measured 4.41:1 at 390 against the
     4.5 this size owes; it clears at 1440 because the silk is paler there, and
     a colour that passes on a desktop and fails on a phone has not passed.
     Same value the question below uses and .hero-badge strong already used, so
     it is not a new colour on the page. */
  color:#6E5514;
}
/* ⚠️ THE FLEX GAP IS WHY THE WORDS ARE WRAPPED. Two text children would take
   the row's 12px gap between them as well as around them. One child, and the
   pair inside it spaces itself. */
.re-txt{display:inline-flex;align-items:baseline;gap:.32em;}
/* The script accent. Asked for after the head read as one uniform block:
   three sans blocks of near enough the same size stacked centrally, with
   nothing to catch on. This is the page's third face, not a new one, and it
   is the same Parisienne + cursive stack .pf-script uses in #proof.
   ⚠️ --gold-ink, not --gold-accent. The README records --gold-accent as never
   having cleared 3:1 anywhere on this page, and this is real text at a real
   size, not an ornament. */
.re-txt em{
  font-family:"Parisienne", "Segoe Script", "Brush Script MT", cursive;
  font-style:normal;
  /* ⚠️ text-transform is inherited from .rule-eyebrow and a script face in
     caps is unreadable. The word has to opt out. */
  text-transform:none;
  font-size:1.75em;
  font-weight:400;
  letter-spacing:0;          /* .22em tracking pulls a joined script apart */
  line-height:1;
  color:#6E5514;             /* same deeper gold as the caps beside it */
  /* the script sits large next to 11px caps, so it needs its own baseline
     nudge or it rides high on the cap line */
  transform:translateY(.06em);
}
.rule-eyebrow::before,
.rule-eyebrow::after{content:"";height:1px;flex:none;}
.rule-eyebrow::before{width:26px;background:linear-gradient(90deg, rgba(198,164,85,0), var(--gold));}
.rule-eyebrow::after{width:26px;background:linear-gradient(90deg, var(--gold), rgba(198,164,85,0));}

.story-title{
  font-family:var(--f-display);
  font-weight:400;
  font-size:clamp(30px,3.8vw,50px);
  line-height:1.1;letter-spacing:-.02em;
  margin-bottom:18px;
}
/* No underline: the comp leaves the gold word to carry itself, and a rule
   under a display serif this large reads as a correction mark. */
.story-title em{
  font-style:normal;
  color:var(--gold-accent);
}
.story-lede{
  font-size:14px;font-weight:300;line-height:1.7;
  /* ⚠️ --ink-2, and it was --ink-3. Measured off the rendered silk rather
     than computed against a flat colour: --ink-3 came out 4.2:1 at 390 and
     4.3 at 1440, under the 4.5 a 14px 300-weight line owes. That is the same
     shortfall .pf-sub carries and which is on the open list, so it
     is not new and it was not introduced here; it is fixed here because this
     head was being rewritten line by line and leaving one failing line in the
     middle of it would have been a choice.
     The trust band is the precedent, in both directions: it took --ink-2 for
     the same reason and its note is the one worth repeating — a subtitle
     should be smaller and lighter, not harder to read. The size and the 300
     weight still say secondary without the colour having to.
     ⚠️ .pf-sub is STILL on the open list. This fixes one line, not the class
     of fault. (.pf-note was the third name here; the line it described was
     removed from #proof on request, so it is no longer an example of
     anything.) */
  color:var(--ink-2);
  max-width:33ch;margin-bottom:24px;
}
/* One phrase carries the lede's point, so one phrase is marked. The same
   highlighter device the three cards use, so the section has one way of
   marking a word rather than two.
   ⚠️ The colour goes UP to --ink-2 as well as gaining the band. A wash of
   gold behind --ink-3 lifts the background toward the text and takes contrast
   off a 300-weight line that had none to spare. */
.story-lede b{
  font-weight:500;
  color:var(--ink-2);
  background:linear-gradient(rgba(198,164,85,.20), rgba(198,164,85,.20))
             left bottom / 100% .34em no-repeat;
}
.story-ask{
  position:relative;
  padding-left:46px;
  font-size:14.5px;line-height:1.7;color:var(--ink-2);
  max-width:42ch;
}
.story-ask::before{
  content:"";
  position:absolute;left:0;top:4px;bottom:4px;
  width:2px;border-radius:2px;
  background:linear-gradient(180deg, var(--gold), var(--gold-soft));
}
/* Sits on the rule, not beside it — the badge is what starts the line. */
.ask-badge{
  position:absolute;left:-13px;top:1px;
  display:grid;place-items:center;
  width:28px;height:28px;
  border-radius:50%;
  background:linear-gradient(135deg, #DCC486, var(--gold));
  box-shadow:0 6px 14px -6px rgba(160,124,40,.85), 0 0 0 4px rgba(253,251,247,.9);
}
.ask-badge svg{
  width:14px;height:14px;
  fill:none;stroke:#fff;stroke-width:2.2;
  stroke-linecap:round;stroke-linejoin:round;
}
/* The lead-in becomes a label rather than a sentence. It was body copy at the
   same size and weight as the lede above it, which is most of why the head
   read as one block: lede, lead-in and question were three sans paragraphs of
   near enough one size.
   ⚠️ --ink-2, not --ink-3. --ink-3 computes 4.41:1 on this page's warm ground
   against the 4.5 that small text owes; the trust band hit the same wall and
   the note there is the precedent. Size and weight carry the hierarchy, not a
   colour that is hard to read. 11px matches .rule-eyebrow, which is the
   page's existing micro-label size. */
.ask-label{
  display:block;
  margin-bottom:8px;
  font-size:11px;font-weight:600;
  letter-spacing:.2em;text-transform:uppercase;
  color:var(--ink-2);
}
/* The question is the section's hook, so it is set like one: the display face
   again, italic, and large enough to be the thing the eye lands on. Serif,
   sans, micro-caps, serif italic is a rhythm; four sans paragraphs is not.
   ⚠️ Playfair italic 500 is already in the font request (ital,wght 1,400;1,500)
   so this adds no weight to the page. */
.story-ask strong{
  display:block;
  margin-top:0;
  font-family:var(--f-display);
  font-style:italic;font-weight:500;
  font-size:clamp(17px,1.5vw,22px);
  line-height:1.32;letter-spacing:-.01em;
  /* ⚠️ NOT --gold-ink, AND THIS IS THE SECOND TIME THIS PAGE HAS HIT IT. The
     gold ramp's lighter half cannot carry small text: --gold-ink measured
     4.32:1 at 390 and 4.29 at 1440 on this ground, under the 4.5 a 17px
     non-bold line owes. It fails at 22px too, because "large" starts at 24px
     and the clamp MINIMUM is what decides, exactly as .pf-script records.
     ⚠️ It failed before this rewrite as well: the question was --gold-ink at
     13.5px/600 and measured the same. Not introduced here, but not left.
     #6E5514 is not a new colour on the page — .hero-badge strong already
     uses it — and it measures 6.1:1 here while still reading as gold. It is
     a literal rather than a token because the token block is restricted by
     BRAND.md; flagged rather than quietly added. */
  color:#6E5514;
}


/* Three cards, each led by its own accent */
.story-cards{
  position:relative;
  display:grid;
  grid-template-columns:repeat(3,minmax(0,1fr));
  gap:clamp(16px,2vw,26px);
  margin-bottom:clamp(30px,4vw,48px);
}
/* ⚠️ SUPERSEDES "the comp runs all three in the same champagne". That note
   was right about the comp it was written against; the supplied reference for
   this section runs THREE accents, one per card, and the emphasis is carried
   by the order of the colours rather than by one card standing proud. Asked
   for directly, with the reference on screen. The featured card — its lift,
   its gold plate, its corner spark — is gone with it.

   Every card reads its own --ac from the markup. Nothing below names a colour
   of its own: the disc, the top thread, the rule and the mark are all
   rgb(var(--ac)) or a trace of it, so one attribute recolours a whole card. */
.scard{
  /* Fallback for a card added without --ac: ink, which is a real colour and
     not a broken one. An inline style always wins, so this affects nothing
     that is written. */
  --ac:14,14,14;
  position:relative;
  overflow:hidden;                 /* the top thread is drawn to the edge */
  padding:clamp(26px,2.6vw,34px) clamp(18px,2vw,26px) clamp(24px,2.4vw,30px);
  text-align:center;
  border-radius:var(--r-md);
  /* The card's own colour pooling at the top, behind the disc. On the
     background rather than a pseudo — it does not move, and both pseudos are
     spoken for: ::after is the thread, ::before is the pointer glow that
     [data-spotlight] supplies. */
  background:
    radial-gradient(124% 46% at 50% -8%, rgba(var(--ac),.10), rgba(var(--ac),0) 66%),
    linear-gradient(180deg, rgba(255,255,255,.96) 0%, rgba(252,250,246,.92) 100%);
  border:1px solid var(--line);
  box-shadow:var(--shadow-sm);
}

/* ⚠️ THE CARD'S LIFT USES `translate`, NOT `transform`, AND THE PRESS USES
   `scale`. THIS IS A FIX, NOT A STYLE CHOICE — the old translateY(-5px) hover
   never once ran.

     [data-reveal]{ transform:translateY(34px) }
     [data-reveal].is-in{ opacity:1; transform:none }      ← line 2710

   `[data-reveal].is-in` is (0,2,0) and so is `.scard:hover`. Equal
   specificity, and .is-in is 900 lines further down the same file, so it wins
   every time — the moment a card finished revealing, `transform:none` sat on
   top of the hover lift for the life of the page. Measured with :hover forced
   through the debugger: the card reported transform:none while its thread,
   icon, mark and divider all answered correctly. It looked like a hover that
   worked, because four fifths of it did.

   `translate` and `scale` are their own properties, not parts of `transform`,
   so the reveal cannot reach them and no specificity war is needed.

   ⚠️ The transition has to be rescued the same way. `.scard` is (0,1,0),
   `[data-reveal]` in interactive.css is also (0,1,0) and loads later, so the
   card's own `transition` was being replaced wholesale — box-shadow and
   border-color were snapping rather than easing. `.scard[data-reveal]` is
   (0,2,0) and wins wherever it sits, and it has to re-state the three reveal
   properties or the entrance loses its own easing.

   ⚠️ Every delay is written INSIDE the shorthand. A separate transition-delay
   below it would apply to all seven and put the reveal's stagger on the hover
   — card three would lift 190ms after you reached it. Same shorthand trap
   this file documents twice already. */
.scard[data-reveal]{
  transition:
    opacity .9s var(--ease)      calc(var(--d,0) * 95ms),
    transform .9s var(--ease)    calc(var(--d,0) * 95ms),
    filter .8s var(--ease-out)   calc(var(--d,0) * 95ms),
    translate .45s var(--ease),
    scale .25s var(--ease),
    box-shadow .45s var(--ease),
    border-color .4s var(--ease);
}

/* The thread along the very top edge, in the card's colour. It rests part
   drawn and runs out to full width on hover.

   ⚠️ THIS IS NOT AN AFFORDANCE. The three cards are not links and have
     nowhere to go — no cursor:pointer, no role, nothing that says press me.
     The response is there because a card that answers the pointer reads as
     part of a live page; it is decoration, and it must not grow into a
     promise the card cannot keep. */
.scard::after{
  content:"";
  position:absolute;left:0;right:0;top:0;height:3px;
  background:rgb(var(--ac));
  border-radius:0 0 3px 3px;
  transform:scaleX(.34);transform-origin:center;
  transition:transform .5s var(--ease);
}

/* ⚠️ ::before belongs to [data-spotlight] in interactive.css, which is why
   each card carries that attribute — it buys the pointer-follow glow AND,
   through the thumb driver in main.js, the same glow travelling through the
   card as it scrolls past on a phone. Both drivers are already written and
   already documented; this only recolours the light to the card's own --ac
   instead of the page's champagne. */
.scard[data-spotlight]::before{
  background:radial-gradient(150px circle at var(--mx,50%) var(--my,50%),
             rgba(var(--ac),.20) 0%, rgba(var(--ac),0) 66%);
}

.scard-ico{
  display:grid;place-items:center;
  width:46px;height:46px;
  margin:0 auto 16px;
  border-radius:50%;
  /* Filled disc in the card's colour, white glyph — the reference's shape.
     ⚠️ The --ac values in the markup are the contrast-safe shades for exactly
     this: a white glyph on the disc owes 3:1 and raw brand seafoam gives 2.1.

     ⚠️ THE RAMP HAS SINCE CHANGED AND THE RULE HAS NOT. Seafoam is gone —
       the three are now a warm ramp (deep gold / warm brown / espresso) asked
       for against a reference, measured at 3.19 / 5.29 / 14.31. The reference
       gold itself, --gold #C6A455, measures 2.40 and is the one value that
       could not be used as drawn. If these are ever re-picked, re-measure:
       the lighter half of this page's gold ramp cannot carry white. */
  background:rgb(var(--ac));
  box-shadow:0 14px 26px -15px rgba(var(--ac),.85);
  transition:transform .5s var(--ease-back), box-shadow .4s var(--ease);
}
.scard-ico svg{
  width:20px;height:20px;
  fill:none;stroke:#fff;stroke-width:1.6;
  stroke-linecap:round;stroke-linejoin:round;
}

.scard-title{
  font-family:var(--f-display);
  font-weight:500;
  font-size:clamp(16px,1.3vw,19px);
  line-height:1.32;letter-spacing:-.01em;
  margin-bottom:14px;
}
/* The mark. A background rather than an underline, so it can grow from a
   thick rule into a full highlight without the text moving — box-shadow and
   text-decoration can only change weight, not height. */
.scard-title b{
  font-weight:inherit;
  background:linear-gradient(rgba(var(--ac),.18), rgba(var(--ac),.18))
             left bottom / 100% .18em no-repeat;
  transition:background-size .45s var(--ease);
}
.scard-div{
  display:block;width:26px;height:2px;margin:0 auto 14px;
  border-radius:2px;
  background:rgb(var(--ac));
  opacity:.55;
  transition:width .45s var(--ease), opacity .45s var(--ease);
}
.scard-say{font-size:13px;font-weight:300;line-height:1.6;color:var(--ink-3);}
.scard-say b{font-weight:600;color:var(--ink);}

/* ---- The response ----
   ⚠️ Gated on (hover:hover) like the reference, because a phone resolves
   :hover on tap and then keeps it until you touch something else — the card
   would stay lifted after your finger had gone. What a phone gets instead is
   the scroll-driven glow above, and the :active press below. */
@media (hover:hover){
  .scard:hover{
    translate:0 -6px;
    border-color:rgba(var(--ac),.38);
    box-shadow:0 34px 54px -30px rgba(var(--ac),.45);
  }
  .scard:hover::after{transform:scaleX(1);}
  .scard:hover .scard-ico{
    transform:translateY(-4px) scale(1.07);
    box-shadow:0 24px 36px -14px rgba(var(--ac),.9);
  }
  .scard:hover .scard-title b{background-size:100% 1.02em;}
  .scard:hover .scard-div{width:44px;opacity:1;}
}
/* The press. ⚠️ :active ALONE IS NOT ENOUGH and that was measured, not
   assumed: on touch a dispatched touchStart left :active false and every rule
   here at rest, because Chromium only resolves :active on touch for elements
   it treats as activatable and these are <li> with no href and no role — which
   is the point, they are not links. main.js adds .is-pressed from the touch
   events instead. :active stays for a mouse held down, where it does work. */
.scard:active,
.scard.is-pressed{scale:.985;border-color:rgba(var(--ac),.38);}
.scard:active::after,
.scard.is-pressed::after{transform:scaleX(1);}
.scard:active .scard-title b,
.scard.is-pressed .scard-title b{background-size:100% 1.02em;}
.scard:active .scard-div,
.scard.is-pressed .scard-div{width:44px;opacity:1;}

/* Closing band — the hand-off line for this section */
.story-band{
  position:relative;
  display:flex;align-items:center;flex-wrap:wrap;
  gap:clamp(14px,1.9vw,26px);
  /* ⚠️ This is a hand-off line, not a panel. It was 1180x130 — 68px of
     padding around a 62px disc — and next to the three cards above it that
     black slab was the heaviest thing in the section. The copy is what
     sets the height now, at about 90px total. */
  padding:clamp(14px,1.6vw,22px) clamp(18px,2.4vw,32px);
  border-radius:var(--r-lg);
  /* ⚠️ --espresso, NOT --black, ASKED FOR DIRECTLY. And the page had already
     made this exact decision once: the token block says of --espresso "the
     closing CTA band, which is deliberately NOT --black", with white on it
     measured at 14.94:1. This band is the same kind of object — a dark
     hand-off strip on an ivory page — and it was the only one still using
     flat black.

     It also settles a second thing for free. The third story card directly
     above now carries --ac 51,36,23, which IS --espresso: the card's disc
     and the band beneath it are the same colour, so the two read as one
     idea instead of as a brown ramp that ends and a black slab that starts.

     ⚠️ THE TWO GOLD WASHES STAY AS THEY ARE. They are laid over the base,
       so a lighter base makes them read lighter too — checked rather than
       assumed, because a gold glow tuned against black can disappear into
       brown. Measured after: white on the band's lightest corner still
       clears its ratio. */
  background:
    radial-gradient(64% 128% at 10% 110%, rgba(198,164,85,.28) 0%, rgba(198,164,85,0) 62%),
    radial-gradient(56% 120% at 95% -10%, rgba(198,164,85,.22) 0%, rgba(198,164,85,0) 64%),
    var(--espresso);
  color:#fff;
  overflow:hidden;
}
/* Fine gold linework, masked to the two ends so the copy stays on plain
   espresso rather than on the linework */
.story-band::after{
  content:"";
  position:absolute;inset:0;
  z-index:0;
  pointer-events:none;
  background:repeating-linear-gradient(122deg,
    rgba(198,164,85,0) 0px, rgba(198,164,85,.16) 1px,
    rgba(198,164,85,0) 3px, rgba(198,164,85,0) 24px);
  -webkit-mask:linear-gradient(90deg, #000 0%, transparent 24%, transparent 76%, #000 100%);
          mask:linear-gradient(90deg, #000 0%, transparent 24%, transparent 76%, #000 100%);
}
.story-band > *{position:relative;z-index:1;}

/* The comp leads the band with the thing it is asking you to do */
.sb-play{
  display:grid;place-items:center;
  /* 44, not 62 — at 62 the disc was taller than the copy beside it and so
     it, not the words, decided how tall the band was. */
  width:44px;height:44px;flex:none;
  border-radius:50%;
  background:radial-gradient(circle at 50% 50%, rgba(198,164,85,.24) 0%, rgba(198,164,85,0) 72%);
  border:1.5px solid rgba(198,164,85,.85);
  box-shadow:0 0 0 5px rgba(198,164,85,.07), inset 0 0 14px rgba(250,238,200,.14);
}
.sb-play svg{width:18px;height:18px;fill:var(--gold);}
.sb-copy{flex:1 1 320px;min-width:0;}
.sb-title{font-size:clamp(14px,1.15vw,16px);font-weight:600;margin-bottom:2px;}
.sb-sub{font-size:12.5px;font-weight:300;color:rgba(255,255,255,.62);}
.sb-cta{flex:none;padding:12px 24px;font-size:13px;}





/* ---------- The RDR quote card (section 2) ---------- */
.rdr-card{
  display:grid;
  grid-template-columns:minmax(0,.82fr) minmax(0,1.18fr);
  align-items:stretch;
  border-radius:var(--r-lg);
  overflow:hidden;
  border:1px solid var(--line);
  box-shadow:0 34px 70px -36px rgba(20,30,36,.44);
}
.rdr-photo{
  position:relative;
  overflow:hidden;          /* the portrait is scaled inside this box */
  min-height:300px;
  /* Same silk as the section, folded tighter to fit the plate */
  background:
    radial-gradient(64% 54% at 24% 16%, rgba(255,255,255,.8) 0%, rgba(255,255,255,0) 68%),
    repeating-linear-gradient(112deg,
      rgba(196,176,136,0)   0px,
      rgba(196,176,136,.10) 62px,
      rgba(255,255,255,.28) 124px,
      rgba(196,176,136,0)   214px),
    linear-gradient(160deg, #F3EADA 0%, #E3D8C0 56%, #F6F0E4 100%);
}
.rdr-photo img{
  position:absolute;inset:0;
  width:100%;height:100%;
  object-fit:cover;

  /* The supplied portrait is a full-length editorial shot; at plate size the
     face lands about 40px tall and the quote card stops being a portrait.
     Framing it here rather than cropping the master keeps the original
     intact and makes re-framing two numbers.

     transform-origin is the face's position in the PLATE after cover has
     done its work — not its position in the file. Move that to re-aim,
     change scale to zoom. */
  object-position:50% 50%;
  transform-origin:62% 24%;

  /* ⚠️ THE ZOOM IS A VARIABLE BECAUSE TWO THINGS SET IT. This declaration is
     what a still portrait gets; interactive.css runs rdrPush on the same
     property, and an animation beats a normal declaration in the cascade. So
     both read --rdr-z and there is exactly one number per breakpoint. Change
     it here, or per breakpoint below, and the drift follows. */
  --rdr-z:1.85;
  scale:var(--rdr-z);
}
.rdr-photo img.is-missing{display:none;}
/* Shows only when the portrait has not been dropped in yet */
/* Shown until the portrait lands. The comp treats it as a monogram plate —
   letters over a gold rule — rather than as a missing image. */
.rdr-photo-fallback{
  position:absolute;inset:0;
  display:flex;flex-direction:column;
  align-items:center;justify-content:center;gap:20px;
  font-family:var(--f-display);
  font-size:clamp(38px,3.4vw,50px);
  letter-spacing:.2em;text-indent:.2em;   /* letter-spacing pads the last letter */
  color:rgba(138,107,31,.42);
}
.rdr-photo-fallback::after{
  content:"";
  width:clamp(96px,8vw,132px);height:1.5px;
  background:linear-gradient(90deg, rgba(198,164,85,0), rgba(170,133,44,.95) 50%, rgba(198,164,85,0));
}
.rdr-photo img:not(.is-missing) ~ .rdr-photo-fallback{display:none;}

.rdr-panel{
  position:relative;
  display:flex;flex-direction:column;justify-content:center;gap:18px;
  padding:clamp(26px,3.2vw,44px);
  /* ⚠️ A SHADE OF THE STORY CARD'S TAUPE, NOT --black. rgb(124,104,86) is
     the --ac the second .scard carries — the taupe disc with the shield on it
     — so this panel is now the same colour as something already on the page
     rather than a second dark.
     ⚠️ A SHADE, WHICH IS THE COLOUR MIXED DOWN, NOT THE COLOUR ITSELF. Flat
     rgb(124,104,86) puts --gold at 2.22:1 and the attribution line would be
     unreadable on it. Laid at .26 over --espresso it computes to rgb(70,54,39),
     where --gold measures 4.89:1 — above the 4.5 that 14px owes, with room to
     spare. Both ingredients are on the page already; no new colour.
     The radial still lifts the top-right corner the way it did, in the same
     taupe rather than in a lighter black. */
  background:
    radial-gradient(80% 90% at 82% 0%, rgba(124,104,86,.38) 0%, rgba(124,104,86,0) 62%),
    linear-gradient(rgba(124,104,86,.26), rgba(124,104,86,.26)),
    var(--espresso);
  color:rgba(255,255,255,.86);
}
/* Dot field in the panel's top corner, from the reference. Gold on black
   rather than the reference's white on navy, and masked to fade out into the
   panel so it reads as texture and not as a pasted-on block.
   ⚠️ Absolute. The panel is a flex column with an 18px gap, so a dot field in
   the flow would take a row and a gap of its own. */
.rdr-dots{
  position:absolute;top:12px;right:12px;
  width:clamp(74px,20vw,104px);height:clamp(44px,12vw,62px);
  pointer-events:none;
  background:radial-gradient(rgba(233,205,140,.30) 1.3px, transparent 1.4px) 0 0 / 11px 11px;
  -webkit-mask-image:linear-gradient(232deg, #000 8%, transparent 76%);
          mask-image:linear-gradient(232deg, #000 8%, transparent 76%);
}

/* ⚠️ NO ELEMENT USES THIS YET, ON PURPOSE. The mark is commented out in the
   markup because the line it would sit above is third person and is not
   attributed to anyone. Kept whole and ready so turning it on is one
   uncomment plus real quoted copy, which is the trade the markup spells out. */
.rdr-quote{
  display:block;
  font-family:var(--f-display);
  font-size:clamp(54px,5.6vw,84px);line-height:.4;
  color:var(--gold);
}
.rdr-text{
  font-size:clamp(14.5px,1.15vw,17px);
  font-weight:300;line-height:1.66;
  color:#fff;
}
.rdr-by{
  display:flex;flex-direction:column;gap:2px;
  padding-top:16px;
  border-top:1px solid rgba(198,164,85,.5);
}
.rdr-by strong{font-size:14px;font-weight:600;color:var(--gold);}
.rdr-by span{font-size:11.5px;font-weight:300;color:rgba(255,255,255,.6);}

/* ---------- Why choose us ---------- */
.why-stats{display:flex;align-items:center;gap:clamp(16px,2vw,28px);}
.stat{flex:1;}
.stat + .stat{padding-left:clamp(16px,2vw,28px);border-left:1px solid var(--line);}
.stat-num{
  font-family:var(--f-display);
  font-weight:400;
  font-size:clamp(30px,3.4vw,42px);
  line-height:1.1;
  color:var(--gold-accent);
  letter-spacing:-.02em;
}
.stat-cap{font-size:12px;line-height:1.45;color:var(--ink-3);margin-top:6px;}

/* The value section lives in its own file — see why.css. It is a whole
   composition built to a reference comp, not a handful of component rules,
   the same reason titlecard.css and hero.css are their own files. */

/* ---------- Process ---------- */
.process-head{
  display:flex;align-items:center;justify-content:space-between;
  gap:20px;flex-wrap:wrap;
  margin-bottom:30px;
}
.process-head .eyebrow{margin-bottom:0;}
/* The four-across routine list lived here. It is a stepper now, and the
   whole of it — rail, dots, cards, ghost numerals — is in routine.css. */


/* Phone mockup */
.phone{
  position:relative;
  width:100%;aspect-ratio:210/430;
  background:#0A0A0A;
  border:7px solid #191919;
  border-radius:34px;
  box-shadow:0 40px 70px -30px rgba(0,0,0,.65), 0 0 0 1px rgba(255,255,255,.07) inset;
  overflow:hidden;
}
.phone-notch{
  position:absolute;top:0;left:50%;transform:translateX(-50%);
  width:44%;height:16px;
  background:#191919;
  border-radius:0 0 12px 12px;
  z-index:3;
}
.phone-screen{
  position:absolute;inset:0;
  background:#0A0A0A;          /* only ever seen while the screenshot loads */
  overflow:hidden;
}
.ps-shot{
  display:block;
  width:100%;height:100%;
  /* the capture was taken at the mock-up's own 210/430, so cover crops nothing
     — object-position pins it to the top in case that ever drifts */
  object-fit:cover;
  object-position:top center;
}
/* ⚠️ THE DRAWN HOMEPAGE'S OWN RULES CAME OUT HERE — .ps-bar, .ps-logo,
   .ps-menu, .ps-eyebrow, .ps-title, .ps-sub, .ps-btn and .ps-products, about
   thirty lines of them. They styled a hand-made approximation of
   cinemaskinfx.com inside the phone: a 6px eyebrow, a 6px sub-line and three
   vector bottles. The phone shows a screenshot of the real homepage now, so
   none of it matches anything in the markup. Recorded in README.md. */

/* ---------- Credibility / social proof ----------
   Lives in proof.css. The section was rebuilt to a comp and is a whole
   composition rather than a handful of component rules, the same reason
   hero.css, why.css, titlecard.css and results.css are their own files.
   The rules that used to be here styled .proof-claims / .proof-figures /
   .proof-note, and no element carries those class names any more. */

/* ---------- FAQ ----------
   Native <details>, so it opens with JS disabled — and now a real accordion
   with no script at all: name="faq" makes the browser close the others.

   Two columns. The rail carries the eyebrow, the heading, one line of copy
   and one way out; the questions stack beside it. */
.faq-grid{
  --faq-pad:clamp(18px,2.1vw,30px);
  --faq-gap:clamp(14px,1.6vw,22px);
  /* ⚠️ THE NUMERAL TRACK GREW WITH THE NUMERAL. It was 24px against 11px of
     Outfit; the numbers are Playfair now and run to 20px, and "06" needs
     about 24px of that on its own. A track narrower than its content does not
     overflow here — the grid just squeezes the question column instead, and
     the fault shows up as questions wrapping a word early. */
  --faq-num:clamp(26px,2.4vw,36px);
}
@media (min-width:900px){
  .faq-grid{
    display:grid;
    grid-template-columns:minmax(0,.84fr) minmax(0,1.16fr);
    gap:clamp(34px,4.4vw,84px);
    align-items:start;
  }
  /* ⚠️ THE RAIL USED TO BE position:sticky HERE, AND IT IS NOT ANY MORE.
     The note read: "STICKY, AND IT IS THE REASON FOR THE SPLIT. Six questions
     is a long scroll; without this the heading and the 'Ask us' button are
     gone by the second row and the reader who still has a question has
     nothing to reach for."

     The rail was behaving correctly — across a scroll it pinned at top 132
     (the header's 89 plus the offset), stayed inside its own section, and
     released at the section's foot. What was wrong was the premise. Measured
     at both desktop sizes this block applies to:

         1440x900   section 710px closed, 805px with the longest answer open
         1024x768   section 631px closed, 747px with the longest answer open

     The whole section fits inside the viewport in every case, so the heading
     and the button were never going anywhere and the pin bought nothing. What
     it did produce was about 370px of held rail, reported as "nag sstay sya
     and hindi sumasabay sa scroll".

     ⚠️ IF THE FAQ EVER GROWS PAST ONE SCREEN, this is the thing to reconsider
     — and the old warning still applies to whatever replaces it: sticky dies
     silently against an ancestor scroll container, so nothing in this section
     may be given overflow:hidden. The clip on .faq-atmos is `clip`, which
     does not create one. */
}

/* ---------- the rail ---------- */
.faq-eyebrow{
  display:flex;align-items:center;gap:12px;
  font-size:11px;font-weight:600;
  letter-spacing:.28em;text-transform:uppercase;
  color:var(--gold-ink);
}
.faq-eyebrow i{
  flex:none;
  width:26px;height:2px;border-radius:2px;
  background:var(--gold);
}
.faq-title{
  margin-top:clamp(14px,1.7vw,22px);
  font-family:var(--f-display);
  font-weight:400;
  font-size:clamp(34px,4.8vw,64px);
  line-height:1.02;
  letter-spacing:-.032em;
  color:var(--ink);
  text-wrap:balance;
}
/* ⚠️ THE MARKER IS THE EMPHASIS, AND THE INK STAYS BLACK.
   Every other heading on this page sets its emphasis word in --gold-accent,
   and that ink cannot reach 3:1 anywhere here — measured, it tops out at
   2.95:1 on pure white. Champagne behind near-black type carries the same
   emphasis and measures over 15:1.

   It is a BACKGROUND on the <em>, not a pseudo-element: a background paints
   behind its own text with no z-index to negotiate, and inside a section that
   isolates, a ::before at z-index -1 would have gone behind the section's own
   ground instead. */
.faq-title em{
  font-style:italic;
  color:var(--ink);
  background-image:linear-gradient(180deg,var(--champagne),var(--champagne));
  background-repeat:no-repeat;
  background-position:0 84%;
  background-size:0% 36%;
  transition:background-size .9s .3s var(--ease-out);
}
/* it draws itself in as the heading lands — .is-in is what the reveal
   observer already adds, so this costs no new script */
.faq-title.is-in em{background-size:100% 36%;}

.faq-sub{
  margin-top:clamp(14px,1.7vw,20px);
  font-size:clamp(13.5px,1.02vw,15.5px);
  font-weight:300;
  line-height:1.72;
  color:var(--ink-2);
  max-width:34ch;
}
.faq-ask{margin-top:clamp(20px,2.4vw,30px);}

/* ⚠️ STACKED, THE RAIL AND THE LIST WERE TOUCHING AT EXACTLY 0px. Above 900
   the two are grid columns and the column gap keeps them apart; below it they
   are plain blocks in flow, and nothing had ever put a gap between the "Ask
   us" button and the first question. Measured at 899, 720, 560, 390 and 320:
   the button's bottom edge and the first row's top edge are THE SAME NUMBER
   at every one of them.

   Zero is worse than tight here, because both carry a drop shadow: the
   button's falls straight onto the card and the two read as one overlapping
   thing. It also breaks the rhythm — the rows are 10px apart, so the step
   between the heading block and the list was smaller than the step between
   two questions.

   Written as a margin on the RAIL rather than on the list, so it survives
   .faq-grid ever becoming a flex or grid container at this width. */
@media (max-width:899px){
  .faq-rail{margin-bottom:clamp(28px,7vw,44px);}
}

/* ---------- the list ---------- */
.faq-list{
  display:grid;
  gap:10px;
  /* ⚠️ interpolate-size is what lets block-size animate TO auto below. It is
     set here rather than on :root so it cannot reach anything else. */
  interpolate-size:allow-keywords;
}
.faq-item{
  position:relative;
  overflow:clip;                 /* keeps the growing rail inside the radius */
  background:var(--white);
  border:1px solid var(--line);
  border-radius:var(--r-md);
  transition:border-color .35s var(--ease),
             box-shadow .35s var(--ease),
             background-color .35s var(--ease),
             translate .35s var(--ease-out);
}
/* the rail that grows down the open row.
   ⚠️ scale, not transform — nothing else here animates either, but scale is
   the independent property and it composes rather than replacing. */
.faq-item::before{
  content:"";
  position:absolute;left:0;top:0;bottom:0;
  width:2px;
  background:linear-gradient(180deg,var(--gold) 0%,rgba(198,164,85,.2) 100%);
  scale:1 0;
  transform-origin:50% 0;
  transition:scale .5s var(--ease-out);
}
.faq-item[open]::before{scale:1 1;}
/* ⚠️ EVERY HOVER AFFORDANCE ON A ROW IS GATED ON A REAL POINTER, AND THE
   PHOTOGRAPH IS WHY. On a touch screen the last element tapped KEEPS :hover
   until something else is tapped, so after opening and closing a question
   that row sat there wearing the pointer's state: gold text, gold chevron,
   and — the visible part — shifted 3px to the right of every other row in
   the list. Measured at 390: rows at 20..370 and the stuck one at 23..373.

   A list whose rows do not line up reads as broken, and nothing had gone
   wrong except that a phone was given a rule written for a mouse. There is
   a :active state for touch at the end of this group instead. */
@media (hover:hover){
  .faq-item:hover{border-color:var(--gold-soft);}
}
/* ⚠️ While one row is open the others step back, so the answer you asked for
   is the thing on the page rather than one of six identical bars. .72 was
   chosen against the type, not by eye: the question text still measures 6.9:1
   through it, well over the 4.5 it owes. Hovering a dimmed row brings it
   straight back, so nothing is ever hard to read on the way to clicking it. */
.faq-list:has(.faq-item[open]) .faq-item:not([open]){
  opacity:.72;
  /* ⚠️ translate is repeated here because this list REPLACES the one on
     .faq-item — without it a dimmed row would jump sideways on hover
     instead of sliding, and only while another row was open. */
  transition:opacity .35s var(--ease), border-color .35s var(--ease),
             box-shadow .35s var(--ease), background-color .35s var(--ease),
             translate .35s var(--ease-out);
}
.faq-list:has(.faq-item[open]) .faq-item:not([open]):hover,
.faq-list:has(.faq-item[open]) .faq-item:not([open]):focus-within{opacity:1;}

.faq-item[open]{
  border-color:rgba(198,164,85,.5);
  background:var(--gold-tint);
  box-shadow:
    0 1px 2px rgba(120,92,30,.1),
    0 16px 30px -20px rgba(120,92,30,.45);
}

.faq-q{
  display:grid;
  grid-template-columns:var(--faq-num) minmax(0,1fr) auto;
  align-items:center;
  gap:var(--faq-gap);
  padding:clamp(16px,1.9vw,22px) var(--faq-pad);
  cursor:pointer;
  list-style:none;
}
.faq-q::-webkit-details-marker{display:none;}
.faq-q::marker{content:"";}
.faq-q:focus-visible{
  outline:2px solid var(--gold-ink);
  outline-offset:-3px;
  border-radius:var(--r-md);
}
/* ⚠️ THE NUMERALS ARE THE EDITORIAL PART OF THIS SECTION, and they were 11px
   of Outfit doing nothing. Playfair at up to 20px gives the list a spine: a
   serif gold numeral against a serif ink question is the pairing the rest of
   the page already uses for a chapter mark. They are aria-hidden, so nothing
   is read out twice. */
.faq-n{
  font-family:var(--f-display);
  font-size:clamp(15px,1.35vw,20px);
  font-weight:400;
  line-height:1;
  letter-spacing:0;
  font-variant-numeric:lining-nums tabular-nums;
  /* ⚠️ --gold-ink, not --gold-accent. The accent measured 4.36:1 here against
     the 4.5 small type owes — it is the token that cannot reach its threshold
     anywhere on this page, and there was no reason to reach for it: gold-ink
     clears on the white row and on the warm open one. */
  color:var(--gold-ink);
  /* ⚠️ ONE transition DECLARATION, AND THIS RULE BRIEFLY HAD TWO. The second
     one silently replaced the first, taking `translate` with it — the numeral
     kept its colour fade and stopped moving, with nothing in the file looking
     wrong. A shorthand resets every longhand it covers, including the ones it
     never names. */
  transition:color .3s var(--ease), translate .4s var(--ease-out);
}
/* the open row's number goes to ink — the gold has moved to the rail, the
   chevron and the ground by then, and three golds in one row is one too many */
.faq-item[open] .faq-n{color:var(--ink);}
/* ⚠️ THE QUESTIONS ARE SERIF NOW, AND THE ANSWERS ARE NOT — that split is the
   change. Six rows of 15px Inter 600 is what an accordion looks like
   everywhere; a short question is the same kind of object as .pf-label, which
   is Playfair 400 at this size and reads as written rather than as UI. The
   answer copy underneath stays Inter, so opening a row moves from the voice
   to the explanation. */
.faq-t{
  font-family:var(--f-display);
  font-size:clamp(15.5px,1.32vw,20px);
  font-weight:400;
  line-height:1.32;
  letter-spacing:-.012em;
  color:var(--ink);
  transition:color .25s var(--ease);
}
@media (hover:hover){
  .faq-item:hover .faq-t{color:var(--gold-ink);}
  /* the numeral steps aside as the row answers the pointer */
  .faq-item:hover .faq-n{translate:2px 0;}
}

/* a drawn chevron, not a plus — it turns over rather than crossing out */
.faq-mark{
  position:relative;flex:none;
  width:32px;height:32px;
  border-radius:50%;
  background:var(--gold-tint);
  border:1px solid rgba(198,164,85,.28);
  transition:background-color .3s var(--ease), border-color .3s var(--ease);
}
.faq-mark::before{
  content:"";
  position:absolute;left:50%;top:44%;
  width:8px;height:8px;
  border-right:1.7px solid var(--gold-ink);
  border-bottom:1.7px solid var(--gold-ink);
  translate:-50% -50%;
  rotate:45deg;
  transition:rotate .45s var(--ease-out);
}
@media (hover:hover){
  .faq-item:hover .faq-mark{
    background:var(--gold-soft);
    border-color:rgba(198,164,85,.5);
  }
}
/* ⚠️ A TAP STILL HAS TO SHOW IT LANDED. :active DOES fire here — <summary> is
   an activatable element, unlike the story cards that needed a class from JS
   for the same job. Colour only: the 3px slide is the thing that broke the
   list and it is not coming back on touch. */
@media (hover:none){
  .faq-item:has(.faq-q:active){border-color:var(--gold-soft);}
  .faq-item:has(.faq-q:active) .faq-mark{
    background:var(--gold-soft);
    border-color:rgba(198,164,85,.5);
  }
}
.faq-item[open] .faq-mark,
.faq-item[open]:hover .faq-mark{
  background:var(--gold);
  border-color:rgba(198,164,85,.75);
}
.faq-item[open] .faq-mark::before{
  rotate:225deg;
  border-color:#17130A;
}

/* ---------- the answer ----------
   ⚠️ <details> has never been able to animate open, and it can now:
   ::details-content is the box the browser wraps everything after the
   <summary> in, and with interpolate-size on the list its block-size can
   transition to auto. content-visibility has to travel with it as a discrete
   transition or the content vanishes on frame one of the close.

   Guarded, because where it is not understood the row must still open —
   instantly, as it always did. */
@supports selector(::details-content){
  .faq-item::details-content{
    block-size:0;
    overflow:clip;
    transition:block-size .45s var(--ease-out),
               content-visibility .45s allow-discrete;
    transition-behavior:allow-discrete;
  }
  .faq-item[open]::details-content{block-size:auto;}
}
.faq-a{
  /* indented past the number so the answer lines up with the question, not
     with its numeral */
  padding:0 var(--faq-pad) clamp(18px,2.2vw,24px)
          calc(var(--faq-pad) + var(--faq-num) + var(--faq-gap));
  font-size:13.5px;font-weight:300;line-height:1.75;
  color:var(--ink-2);
  max-width:70ch;
  /* it arrives just behind the box that is opening, so the words are not
     dragged up by their own container */
  opacity:0;
  translate:0 -6px;
  transition:opacity .3s .1s var(--ease), translate .38s .1s var(--ease-out);
}
.faq-item[open] .faq-a{opacity:1;translate:0 0;}
.faq-a a{
  color:var(--gold-ink);font-weight:500;
  border-bottom:1px solid var(--gold-soft);
  transition:border-color .25s var(--ease);
}
.faq-a a:hover{border-color:var(--gold);}

@media (prefers-reduced-motion:reduce){
  .faq-title em{transition:none;background-size:100% 36%;}
  .faq-list:has(.faq-item[open]) .faq-item:not([open]){transition:none;}
  .faq-item::before{transition:none;}
  .faq-mark::before{transition:none;}
  .faq-a{transition:none;opacity:1;translate:none;}
  @supports selector(::details-content){
    .faq-item::details-content{transition:none;}
  }
}

/* ---------- THE FAQ GROUND, AND IT MOVES ----------

   The section was six white bars on the page's shared ambient: nothing of its
   own, nothing moving, nothing that answered being used. It gets a ground, a
   ripple, a pool that drifts, and two things that respond — one to the
   pointer, one to the accordion.

   ⚠️ .section.faq, TWO CLASSES, AND THAT IS LOAD-ORDER INSURANCE.
     atmosphere.css carries `.section{background:transparent}` at (0,1,0) so
     every section shows the fixed ambient through it, and it loads AFTER this
     file. A single class here scores the same and loses on source order; two
     classes score (0,2,0) and win wherever the <link> tags end up.

   ⚠️ NOTHING IN THIS SECTION MAY EVER GET overflow:hidden. .faq-rail is
     position:sticky and sticky resolves against the nearest ancestor scroll
     container — overflow:hidden makes one, and the rail would stop sticking
     with no error and no clue. The clipping below is on .faq-atmos alone,
     which is not an ancestor of the rail, and it is `clip`, which clips
     without creating a scroll container. */
.section.faq{
  position:relative;
  /* ⚠️ The first stop is translucent on purpose. The scene seam bleeds up to
     220px of #process over the top of this section, so an opaque first stop
     would cut that dissolve into a hard line. */
  /* ⚠️ THE TOP HALF IS BRIGHTER THAN IT LOOKS IT SHOULD BE, AND .faq-eyebrow
     IS WHY. It is 11px of --gold-ink, which measured 4.66:1 on the page's
     shared ambient — 0.16 over the 4.5 it owes, and small gold on a light
     ground has nowhere else to go. The first draft of this ground was a
     quarter of a level darker through the top and took it to 4.59: still a
     pass, but it spent margin that was not mine to spend on an element that
     had almost none. Lifted until the eyebrow measured back at 4.66. The
     warmth still arrives, lower down, where nothing small and gold sits. */
  background:
    linear-gradient(180deg,
      rgba(254,253,250,.50) 0%,
      rgba(254,252,248,.94) 22%,
      rgba(251,247,239,1) 62%,
      rgba(247,242,232,1) 100%);
}

.faq-atmos{
  position:absolute;
  inset:0;
  z-index:-2;                    /* under the pointer glow at -1, under content */
  pointer-events:none;
  overflow:clip;                 /* ⚠️ clip, never hidden — see above */
  contain:layout paint style;
}
.faq-atmos > *{position:absolute;}

/* --- the aperture ---
   Three rings opening out of the heading and fading, one loop staggered into
   thirds so the ripple never stops and never doubles up. The brand's own mark
   is a lens aperture, an open ring; this is that, at the scale of a section.

   ⚠️ ONE @keyframes AND TWO NEGATIVE DELAYS, not three animations at three
     durations. Negative delays start the second and third rings already a
     third and two thirds through, so the three are permanently evenly spaced
     — and they stay evenly spaced if the duration is ever changed, which
     three hand-picked durations would not. */
.fq-iris{
  left:var(--fq-cx,50%);
  top:var(--fq-cy,46%);
  width:min(74vw,720px);
  aspect-ratio:1;
  translate:-50% -50%;
  opacity:.85;
  transition:opacity .9s var(--ease);
}
@media (min-width:900px){
  /* on two columns it opens out of the rail, where the heading is */
  .fq-iris{--fq-cx:27%;--fq-cy:42%;}
}
.fq-iris i{
  position:absolute;
  inset:0;
  border-radius:50%;
  border:1px solid rgba(198,164,85,.34);
  scale:.40;
  opacity:0;
  animation:fqIris 13.5s var(--ease-soft) infinite;
}
.fq-iris i:nth-child(2){animation-delay:-4.5s;}
.fq-iris i:nth-child(3){animation-delay:-9s;}
@keyframes fqIris{
  0%  {scale:.40; opacity:0;}
  16% {           opacity:.9;}
  100%{scale:1.22; opacity:0;}
}

/* --- the pool ---
   ⚠️ THE LOOP IS ON `transform` AND THE STATE IS ON `scale`, ON PURPOSE.
     They are separate properties and they compose. Put the open-row response
     on `transform` too and the running animation would simply win — a filled
     animation beats a plain declaration on the same property, every time. */
.fq-pool{
  right:-8%;
  bottom:-22%;
  width:min(66vw,840px);
  aspect-ratio:1.15;
  border-radius:50%;
  background:radial-gradient(circle at 50% 50%,
    rgba(250,238,200,.60) 0%,
    rgba(250,238,200,.16) 46%,
    rgba(250,238,200,0) 70%);
  animation:fqPool 47s var(--ease-soft) infinite;
  transition:scale 1.1s var(--ease-out), opacity .9s var(--ease);
}
@keyframes fqPool{
  0%,100%{transform:translate3d(0,0,0) scale(1);}
  50%    {transform:translate3d(-5%,-4%,0) scale(1.10);}
}

/* --- the weave ---
   Static, and the cheapest thing here: a 1-in-11 diagonal hairline so the
   ground has a surface instead of being a flat wash. Masked to the middle so
   it never reaches an edge and reads as a seam. */
.fq-weave{
  inset:0;
  background:repeating-linear-gradient(128deg,
    rgba(138,107,31,.05) 0 1px,
    rgba(138,107,31,0) 1px 11px);
  -webkit-mask-image:radial-gradient(115% 88% at 50% 50%, #000 0%, rgba(0,0,0,.35) 58%, transparent 82%);
          mask-image:radial-gradient(115% 88% at 50% 50%, #000 0%, rgba(0,0,0,.35) 58%, transparent 82%);
}

/* --- it answers the pointer ---
   [data-spotlight] in interactive.css already writes --mx / --my on this
   section and paints a plain filled glow. This replaces the key with two
   gradients in ONE background — a warm core, and a ring further out with
   nothing in the middle of it. The ring is the aperture again, this time
   following the cursor, and two stops in one background is still one layer. */
.faq[data-spotlight]::before{
  background:
    radial-gradient(230px circle at var(--mx,50%) var(--my,50%),
      rgba(250,238,200,.52) 0%,
      rgba(250,238,200,.13) 48%,
      rgba(250,238,200,0) 72%),
    radial-gradient(460px circle at var(--mx,50%) var(--my,50%),
      rgba(198,164,85,0) 50%,
      rgba(198,164,85,.14) 59%,
      rgba(198,164,85,0) 69%);
}

/* --- it answers the accordion ---
   ⚠️ NO SCRIPT, AND NO NEW CLASS TO KEEP IN SYNC. :has() reads the open row
     straight off the <details> element the browser is already toggling, so
     the ground responds to the same state the accordion runs on rather than
     to a copy of it that something has to remember to update.
   The room warms and opens up while a question is being answered, and settles
   back when it is closed. Transitions only — retiming a running animation
   from a state change makes it jump. */
.faq:has(.faq-item[open]) .fq-pool{scale:1.14;opacity:.9;}
.faq:has(.faq-item[open]) .fq-iris{opacity:.45;}

/* --- the row answers back ---
   One pass of light across a row as it opens. Not a loop: a loop on six rows
   is a permanent repaint for an effect nobody watches twice.
   ⚠️ ::after, because ::before on .faq-item is already the gold rail. */
.faq-item::after{
  content:"";
  position:absolute;
  inset:0;
  pointer-events:none;
  opacity:0;
  background:linear-gradient(104deg,
    rgba(255,255,255,0) 34%,
    rgba(255,255,255,.72) 50%,
    rgba(250,238,200,.5) 60%,
    rgba(255,255,255,0) 70%);
  translate:-150% 0;
}
.faq-item[open]::after{animation:fqPass 1.05s var(--ease-out) 1;}
@keyframes fqPass{
  0%  {translate:-150% 0;opacity:0;}
  16% {opacity:.9;}
  100%{translate:150% 0; opacity:0;}
}

/* the row steps toward the reader rather than lifting off the page — six rows
   all rising is a wobble, one row sliding is a list responding.
   ⚠️ `translate` was added to BOTH existing .faq-item transition lists up in
   the FAQ block rather than redeclared here: a second `transition` on the same
   selector replaces the first outright, and this section already has one rule
   that overrides that list for dimmed rows. Two lists, both edited, no third. */
@media (hover:hover){
  .faq-item:hover{translate:3px 0;box-shadow:0 10px 24px -20px rgba(120,92,30,.5);}
}

/* ⚠️ REDUCED MOTION. Everything above is decoration and stops; the resting
   state of each is the visible one, except the ring, which rests invisible
   because its whole existence is the ripple — so it goes. */
@media (prefers-reduced-motion:reduce){
  .fq-iris,
  .fq-pool{animation:none !important;}
  .fq-iris{display:none;}
  .faq-item[open]::after{animation:none;}
  .faq-item{transition:none;}
  .faq-item:hover{translate:none;}
}

/* ---------- Closing CTA ---------- */
.cta-band{
  position:relative;
  /* ⚠️ THIS IS THE FALLBACK, NOT WHAT YOU SEE. atmosphere.css writes
     background-color and background-image as longhands on
     .cta-band[data-scene], which is later in the cascade and more specific,
     so it wins wherever the scene system is on. This shorthand is what shows
     if [data-scene] is ever taken off — the two are kept in step on purpose,
     because a fallback that disagrees with the real ground is a black flash
     waiting to happen. */
  background:
    radial-gradient(70% 120% at 22% 100%, rgba(198,164,85,.26) 0%, rgba(198,164,85,0) 62%),
    var(--sand);
  color:var(--ink);
  overflow:hidden;
}
.cta-grid{
  display:grid;
  /* the products column gained a little on the copy: a drawn bottle reads at
     90px and a photograph of one does not */
  grid-template-columns:minmax(0,.95fr) minmax(0,1.2fr) minmax(0,.66fr);
  gap:clamp(20px,3vw,40px);
  align-items:center;
  min-height:300px;
  padding-block:clamp(40px,5vw,64px);
}
/* ⚠️ THE PLINTH AND THE THREE .cp RULES CAME OUT WITH THE DRAWN BOTTLES.
   .cta-plinth was a blurred gold ellipse standing the silhouettes on a
   surface, and .cp positioned them absolutely with a hard black drop-shadow.
   Neither survives contact with a photograph that carries its own ground and
   its own light. Recorded in README.md — there is no version control here. */
.cta-products{
  position:relative;
  align-self:center;
  display:flex;
  align-items:flex-end;
  justify-content:center;
  /* ⚠️ A GAP, AND THE CARDS DO NOT OVERLAP. The drawn bottles they replaced
     were transparent silhouettes, so a -9% margin let them sit in front of one
     another and nothing was lost. A photograph is opaque: the same overlap put
     card 2 across the right of "Line Silencer" and the left of "Bright Rx",
     and both labels were cut by their neighbour rather than by the crop. */
  /* ⚠️ A SMALL OVERLAP IS SAFE NOW, AND IT WAS NOT BEFORE. With the crop
     finally in effect each card shows 45% of its frame, and the bottle sits in
     the middle 26-72% of that — roughly a quarter of the card is ground at
     each edge. A 7% overlap eats ground. The earlier -9% was over a card whose
     bottle filled it edge to edge, so it ate labels. */
  gap:0;
  min-height:200px;
}
.cpx{
  position:relative;
  margin:0;
  flex:0 0 auto;
  width:37%;
  border-radius:var(--r-md);
  overflow:hidden;
  background:var(--sand);
  border:1px solid rgba(198,164,85,.34);
  box-shadow:
    0 26px 46px -26px rgba(60,44,16,.5),
    0 2px 8px -4px rgba(60,44,16,.2),
    0 1px 0 rgba(255,255,255,.7) inset;
}
.cpx img{
  display:block;
  width:100%;
  /* ⚠️ height:auto IS WHAT MAKES THE aspect-ratio BELOW DO ANYTHING. The
     markup carries width="900" height="506" — and those attributes are
     PRESENTATIONAL HINTS that set the width and height properties. With no
     author height, height:506px was in force, both dimensions were definite,
     and aspect-ratio is ignored the moment that is true. The box measured
     136x511 instead of 136x170, so cover was showing 15% of the frame's width
     rather than 45% and the widest product ran out of the card at both edges.
     Widening the ratio from 3/4 to 4/5 changed nothing, twice, because the
     ratio was never in effect. */
  height:auto;
  /* ⚠️ 4/5, AND A CROP RATHER THAN contain. The files are 16:9 with the product
     centred and empty ground either side, so a centre crop is right — but 3/4
     keeps only 42% of the frame's width and the widest of the three, the
     Bright Rx jar, ran out of it. 4/5 keeps 45%.
     contain was the other option and is worse here: the ground baked into
     these files is a GRADIENT, not a flat colour — measured #f6e0c8 to #f9e3cb
     across product 1 and #cfad91 to #e3c5a9 across product 2 — so there is no
     single letterbox colour that could ever match it, and a 16:9 picture
     contained in a portrait box is mostly letterbox anyway. */
  aspect-ratio:4 / 5;
  object-fit:cover;
}
/* ⚠️ rotate AS ITS OWN PROPERTY, NEVER THE transform SHORTHAND. main.js writes
   transform on [data-parallax] and translate on [data-float]; a transform
   shorthand here would be wiped by the first and would wipe the tilt. */
.cpx-1{rotate:-2.6deg;z-index:1;margin-right:-7%;margin-bottom:6%;}
.cpx-2{rotate:0deg;   z-index:3;width:40%;}
.cpx-3{rotate:2.6deg; z-index:2;margin-left:-7%; margin-bottom:6%;}
.cta-copy h2{
  font-family:var(--f-display);
  font-weight:400;
  font-size:clamp(26px,3.2vw,40px);
  line-height:1.16;
  letter-spacing:-.015em;
  margin-bottom:14px;
}
.cta-copy p{
  font-size:14px;font-weight:300;line-height:1.7;
  /* ⚠️ --ink-2, not --ink-3, and this band is 300 weight on a warm ground —
     the same pairing that has measured 4.2-4.4 against 4.5 everywhere else on
     this page. */
  color:var(--ink-2);
  margin-bottom:26px;
}
.cta-actions{display:flex;flex-wrap:wrap;gap:12px;}
.cta-phone{justify-self:center;width:min(190px,100%);}
.cta-phone .phone{box-shadow:0 40px 70px -30px rgba(60,44,16,.65), 0 0 0 1px rgba(255,255,255,.1) inset;}

/* ---------- Footer ---------- */
.site-footer{
  background:var(--black);
  color:rgba(255,255,255,.62);
  padding-top:clamp(44px,5vw,64px);
}
.footer-grid{
  display:grid;
  grid-template-columns:minmax(0,1.5fr) repeat(4,minmax(0,1fr));
  gap:clamp(20px,3vw,40px);
  padding-bottom:clamp(32px,4vw,48px);
}
.footer-brand .logo-mark{width:40px;height:40px;}
.footer-tag{
  font-size:12.5px;font-weight:300;line-height:1.65;
  max-width:30ch;margin:16px 0 20px;
}
.footer-store{
  font-size:12px;font-weight:300;line-height:1.7;
  color:rgba(255,255,255,.5);
  max-width:30ch;margin-bottom:20px;
}
.footer-store a{
  color:var(--champagne);
  border-bottom:1px solid rgba(250,238,200,.35);
  transition:border-color .25s var(--ease);
}
.footer-store a:hover{border-color:var(--champagne);}
.socials{display:flex;gap:10px;}
.socials a{
  display:grid;place-items:center;
  width:34px;height:34px;border-radius:50%;
  border:1px solid rgba(255,255,255,.16);
  color:rgba(255,255,255,.72);
  transition:border-color .3s var(--ease), color .3s var(--ease), background-color .3s var(--ease);
}
.socials a:hover{border-color:var(--champagne);color:var(--champagne);background:rgba(250,238,200,.08);}
.socials svg{
  width:15px;height:15px;
  fill:none;stroke:currentColor;stroke-width:1.5;
  stroke-linecap:round;stroke-linejoin:round;
}
.footer-col h4{
  font-size:10.5px;font-weight:600;letter-spacing:.18em;
  text-transform:uppercase;color:var(--champagne);
  margin-bottom:16px;
}
.footer-col li{margin-bottom:9px;}
.footer-col a,
.footer-col li{font-size:12.5px;font-weight:300;line-height:1.6;}
.footer-col a{transition:color .25s var(--ease);}
/* ⚠️ The address is 166px of unbreakable word and the column is 155px at
   320px wide, so without this the FOOTER drags the whole page sideways —
   measured, the page slid 24px. An email has no space in it to break at, so
   the break has to be allowed anywhere. */
/* ⚠️ THE EMAIL IS THE ONE STRING HERE WITH NO SPACE IN IT. In a 165px column
   overflow-wrap:anywhere broke it as "skincare@cinemaski / om" — a 64px,
   two-line block with an orphan of two letters. The address is allowed to set
   its own column's width instead: min-content on a word this long is the word,
   so the column widens to hold it and the break never happens. The grid track
   is minmax(0,1fr), so it can give. */
/* ⚠️ THE ADDRESS HAS NOWHERE TO BREAK, so it was breaking ANYWHERE —
   "skincare@cinemaski / om", two lines with an orphan of two letters. It sets
   166px at 12.5px, and a half-shell column is 168 at 390, 153 at 360 and 133
   at 320: no amount of column tuning covers every phone.
   A <wbr> after the @ in the markup gives it ONE place to break, and it is the
   place a reader would break it themselves. overflow-wrap stays as the last
   resort for anything narrower than these.
   ⚠️ TRIED AND BACKED OUT: giving the block the full width below 380. It
   fixed the address and broke the row above it — Contact leaving the two-up
   grid put Products back beside Browse, and the footer went from 833px to
   1084. The narrow-screen fix cost more than the thing it fixed. */
.footer-contact a{overflow-wrap:anywhere;}
.footer-contact li{min-width:min-content;}
.footer-col a:hover{color:var(--champagne);}/* ---- the RDR partnership card ----
   Built to a supplied reference: crest tile, a line naming the partnership, a
   second line offering the feature, and a chevron. The reference is blue on
   blue; this footer is near-black with champagne, so only the construction
   crossed over.
   ⚠️ IT POINTS AT #about, WHICH IS WHERE THE FEATURE ACTUALLY IS. That section
   is RDR's account of visiting the brand and it carries the video. The hero's
   "Learn More About RDR Vlog" already goes there, so this is the same
   destination by the same name rather than a second one. */
.rdr-partner{
  display:grid;
  grid-template-columns:auto minmax(0,1fr) auto;
  align-items:center;
  gap:clamp(11px,2.6vw,15px);
  margin-bottom:clamp(14px,2vw,20px);
  padding:clamp(12px,2vw,15px) clamp(13px,2.4vw,17px);
  border:1px solid rgba(255,255,255,.13);
  border-radius:var(--r-md);
  background:rgba(255,255,255,.04);
  transition:background .3s var(--ease), border-color .3s var(--ease);
}
/* the tile: cream card, navy crest, the way the reference carries it */
.rp-mark{
  width:clamp(36px,9vw,42px);
  aspect-ratio:1;
  flex:none;
  display:grid;place-items:center;
  border-radius:calc(var(--r-md) * .62);
  background:var(--cream);
}
.rp-mark::before{
  content:"";
  width:74%;height:74%;
  background-color:var(--rdr-navy);
}
@supports ((-webkit-mask-image:url(a.png)) or (mask-image:url(a.png))){
  .rp-mark::before{
    -webkit-mask:var(--rdr-crest) center/contain no-repeat;
    mask:var(--rdr-crest) center/contain no-repeat;
  }
}
.rp-text{display:grid;gap:3px;min-width:0;}
.rp-line{
  font-size:clamp(12px,1vw,13px);
  font-weight:300;
  line-height:1.35;
  color:rgba(255,255,255,.74);
}
.rp-line strong{font-weight:600;color:#FFFFFF;}
/* ⚠️ CHAMPAGNE IS FINE HERE AND WOULD NOT BE UPSTAIRS. The rule this page
   works to is that small gold type must not sit on a LIGHT ground; this one is
   on near-black, where the same colour has contrast to spare. */
.rp-cta{
  font-size:clamp(12px,.95vw,12.5px);
  font-weight:400;
  line-height:1.35;
  color:var(--champagne);
}
.rp-arrow{
  width:18px;height:18px;flex:none;
  fill:none;stroke:rgba(255,255,255,.55);stroke-width:2;
  stroke-linecap:round;stroke-linejoin:round;
  transition:stroke .3s var(--ease), translate .3s var(--ease);
}
@media (hover:hover){
  .rdr-partner:hover{background:rgba(255,255,255,.07);border-color:rgba(255,255,255,.22);}
  .rdr-partner:hover .rp-arrow{stroke:var(--champagne);translate:3px 0;}
}
@media (hover:none){
  .rdr-partner:active{background:rgba(255,255,255,.09);border-color:rgba(255,255,255,.26);}
}
@media (prefers-reduced-motion:reduce){
  .rdr-partner,.rp-arrow{transition:none;}
}

.footer-bottom{
  display:flex;align-items:center;justify-content:space-between;
  gap:16px;flex-wrap:wrap;
  padding-block:20px;
  border-top:1px solid rgba(255,255,255,.1);
  font-size:11.5px;font-weight:300;
}
.footer-legal{display:flex;gap:12px;}
.footer-legal a:hover{color:var(--champagne);}

/* ---------- Reveal animation ---------- */
/* The state everything starts in, plus the stagger. The transition itself is
   restated in interactive.css, which loads after this and wins — change the
   timing THERE, not here, or you will edit a declaration that never applies.
   Only transition-delay survives from this rule. */
[data-reveal]{
  opacity:0;
  transform:translateY(34px);
  transition:opacity .9s var(--ease), transform .9s var(--ease);
  /* ⚠️ THIS ONE IS OVERRIDDEN AND CANNOT WORK FROM HERE. interactive.css
     loads later and re-declares `transition` as a shorthand, which resets
     transition-delay to 0s. The line that actually staggers the page is the
     identical one in interactive.css, written BELOW that shorthand. Kept here
     so the no-interactive.css fallback still staggers. */
  transition-delay:calc(var(--d,0) * 95ms);
}
[data-reveal].is-in{opacity:1;transform:none;}

/* =========================================================
   Responsive
   ========================================================= */
@media (max-width:1180px){
  .collection-grid{grid-template-columns:repeat(3,minmax(0,1fr));}
  .why-stats{max-width:520px;}
}

@media (max-width:1024px){
  .nav,
  .header-actions .link-quiet{display:none;}
  .nav-toggle{display:flex;}

  /* ⚠️ THE "VISIT THE SHOP" BUTTON COMES OFF THE PHONE HEADER, ASKED FOR
     against a screenshot of that header on a phone. Hidden here rather than
     deleted from the markup because above 1024 the nav is a row of links and
     this button is the only thing in the bar that is not one — it is the
     header's whole call to action there, and nothing in the ask reached the
     desktop.

     ⚠️ IT IS THE ONLY SHOP ROUTE IN THE HEADER, and the nav it collapses
       into has no shop link either — Home, The Brand, Products, Why Cinema,
       Results, Routine, FAQ, Contact. So on a phone the header stops
       offering the shop at all. The page still does, three times: the hero's
       "Shop Products", the collection's "See all products on
       cinemaskinfx.com", and the closing band. Recorded because it is a
       conversion route being closed, not a decoration being tidied. */
  .header-actions{display:none;}

  /* ⚠️ THE HEADER STOPS BEING THE PANEL'S CONTAINING BLOCK WHILE THE MENU
     IS OPEN, and the full-screen menu is impossible without this.

     The <nav> lives inside <header>. A fixed element is fixed to the
     viewport ONLY if no ancestor creates a containing block for it, and
     this header creates one in BOTH of its states:

       docked  .site-header  backdrop-filter:blur() saturate()
       cover   .site-header.is-cover  translate:0 0   (titlecard.css)

     ⚠️ translate:0 0 COUNTS. Any value other than "none" builds the
       containing block — identity included. Measured, not assumed: a test
       div at position:fixed;inset:0 dropped inside this header came back
       390x75, the header's own box, while the same div on <body> came back
       the full screen.

     So the panel could never be sized by its edges; it had to guess its
     height from a viewport unit, and on the reader's phone that unit came
     up about 90px short. Take the containing block away for the moment the
     menu is open and inset:0 means the screen again.

     ⚠️ THE SPECIFICITY IS DELIBERATE. titlecard.css loads AFTER this file
       and sets translate on .site-header.is-cover — two classes. A single
       extra class here would tie and lose, so the cover case is spelled out
       with three.

     ⚠️ NOTHING IS LOST BY SWITCHING THEM OFF. The blur frosts the page
       behind the bar and the translate is identity; while the menu is open
       the bar is underneath a full-screen opaque panel, so neither is on
       screen. Both come straight back when .has-nav-open goes.

     ⚠️ IT IS ITS OWN CLASS, NOT body.is-locked. The inquiry dialog sets
       is-locked too, and that dialog has no business changing the header. */
  .site-header.has-nav-open,
  .site-header.is-cover.has-nav-open{
    backdrop-filter:none;
    -webkit-backdrop-filter:none;
    translate:none;
  }

  /* ---------- The menu is a screen ----------
     Built to the supplied comp: a ground of its own, a plate at the top with
     the lockup and the close, the destinations numbered beneath it, and the
     two actions on the floor.

     ⚠️ THIS IS THE FALLBACK COPY. interactive.css carries the same panel
       with its slide and its stagger and loads after this file, so what
       ships is that one. Both are kept in step on purpose: with
       interactive.css missing the menu still has to be a menu, and it used
       to fall back to a white dropdown that looked nothing like it. */
  /* ⚠️ inset:0, NOT height:100dvh — the note on the same block in
       interactive.css carries the reasoning and the evidence. Kept in step
       here because this copy is what ships if interactive.css is missing,
       and a menu that does not cover the screen is the bug being fixed. */
  .nav{
    position:fixed;inset:0;
    height:auto;
    flex-direction:column;align-items:stretch;gap:0;
    padding:calc(14px + env(safe-area-inset-top)) 18px calc(20px + env(safe-area-inset-bottom));
    background:
      radial-gradient(120% 74% at 14% 0%, rgba(198,164,85,.20) 0%, rgba(198,164,85,0) 60%),
      linear-gradient(180deg, #151209 0%, #0A0906 100%);
    border-bottom:0;
    box-shadow:none;
    overflow-y:auto;
    overscroll-behavior:contain;
    z-index:120;
    counter-reset:navitem;
  }
  .nav.is-open{display:flex;}

  /* the plate */
  .nav-head{
    display:flex;align-items:center;justify-content:space-between;gap:12px;
    flex:none;
    padding:9px 10px 9px 14px;
    border-radius:var(--r-pill);
    background:linear-gradient(180deg, rgba(255,253,248,.97) 0%, rgba(246,241,231,.97) 100%);
    box-shadow:0 14px 34px -20px rgba(0,0,0,.9), inset 0 1px 0 rgba(255,255,255,.9);
    margin-bottom:clamp(16px,3.6vh,30px);
  }
  .nav-brand{display:inline-flex;align-items:center;gap:2px;}
  .nav-brand-mark{width:38px;height:38px;flex:none;margin-right:-4px;}
  .nav-brand-type{display:flex;flex-direction:column;line-height:.98;padding-top:2px;}
  .nav-brand-1{
    font-family:var(--f-logo);font-weight:300;font-size:19px;
    letter-spacing:.01em;color:var(--gold-accent);
  }
  .nav-brand-2{
    font-family:var(--f-display);font-weight:500;font-size:16.5px;
    color:var(--gold-accent);padding-left:12px;
  }
  .nav-close{
    width:44px;height:44px;flex:none;
    display:grid;place-items:center;
    border-radius:50%;
    color:var(--ink);
    background:rgba(14,14,14,.06);
    transition:background .2s var(--ease), scale .18s var(--ease);
    -webkit-tap-highlight-color:transparent;
  }
  .nav-close svg{width:20px;height:20px;fill:none;stroke:currentColor;stroke-width:1.7;}
  .nav-close:active{scale:.9;background:rgba(14,14,14,.12);}

  /* ⚠️ THE NUMBERS ARE A COUNTER, NOT MARKUP. The comp numbers the rows
     01-07; typing them into the list would put the count in two places, and
     the count is a logged decision that has already changed once — it went
     from eight to six. A counter renumbers itself. */
  /* ⚠️ THE ROWS SHARE WHAT IS LEFT, they do not sit in a stack at the top.
     The comp fills its screen with seven; this list is six, and pinned to
     their own heights they left a third of the screen empty between the last
     destination and the buttons. flex lets them take the slack evenly, with
     a floor that keeps every row over the 44px touch guideline and a ceiling
     so a tall tablet does not turn six links into six billboards. */
  .nav > a{
    counter-increment:navitem;
    flex:1 1 auto;
    min-height:52px;max-height:88px;
    display:flex;align-items:center;gap:clamp(12px,3.4vw,18px);
    padding:clamp(10px,1.6vh,16px) 2px;
    border-bottom:1px solid rgba(250,238,200,.16);
    font-family:var(--f-display);font-style:italic;font-weight:500;
    font-size:clamp(21px,6.1vw,30px);
    line-height:1.12;
    letter-spacing:.005em;
    color:var(--champagne);
  }
  .nav > a::before{
    content:counter(navitem,decimal-leading-zero);
    flex:none;
    font-family:var(--f-body);font-style:normal;font-weight:600;
    font-size:11px;letter-spacing:.16em;
    color:var(--gold);
  }
  .nav > a::after{display:none;}
  .nav > a.is-active{color:#FFFFFF;}
  .nav > a.is-active::before{color:var(--champagne);}

  /* the floor */
  .nav-foot{
    display:flex;flex-direction:column;gap:10px;
    margin-top:auto;
    padding-top:clamp(16px,3vh,26px);
  }
  /* ⚠️ BOTH OF THESE NAME .nav-foot, AND THAT IS THE WHOLE REASON THEY
     WORK. .nav a sets colour for the six links in the bar and is (0,1,1);
     a lone .nav-cta-quiet is (0,1,0) and loses to it, so the buttons came
     out in --ink-2 on a near-black ground — measured, and the Inquire label
     was all but invisible. Two classes beat a class and a type. */
  .nav-foot .btn{width:100%;justify-content:center;flex:none;}
  .nav-foot .btn-gold{color:#17130A;}
  .nav-foot .nav-cta-quiet{
    background:transparent;
    border-color:rgba(250,238,200,.5);
    color:var(--champagne);
  }
  .nav-foot .nav-cta-quiet:active{background:rgba(250,238,200,.10);}



  .story-top{grid-template-columns:1fr;}
  .rdr-card{grid-template-columns:1fr;}
  /* Stacked, the plate spans the card. A fixed min-height turns it into a
     letterbox as the viewport widens — at 774px wide and 300 tall it cropped
     the portrait at the mouth. An aspect-ratio keeps the frame the same shape
     at every width, which is what makes one object-position work for all of
     them; a height that changes with width would need a different aim at
     every step. */
  .rdr-photo{min-height:0;aspect-ratio:5/4;}
  /* No zoom here: at 5/4 cover already crops hard enough, and the 1.85 that
     frames the face in the tall plate takes the head clean off in this one.

     ⚠️ THIS RULE SAID scale:1 AND IT HAD NEVER ONCE APPLIED. rdrPush in
     interactive.css animates scale from 1.85 to 1.95, and a running animation
     outranks a normal declaration, so every stacked width has been showing the
     TALL PLATE'S zoom on a wide frame since the day the rule was written. The
     measured value at 390 was 1.85, not the 1 written right here. It is set
     through --rdr-z now, which both the animation and the still state read. */
  /* ⚠️ AND AT scale:1 THERE IS NO HORIZONTAL CROP AT ALL, WHICH IS WHY THE
     SUBJECT LOOKED OFF-CENTRE THE MOMENT THE ZOOM CAME OFF. The portrait is
     1149x1999 and every stacked frame is wider than it is tall, so cover
     matches the WIDTH and crops the height only: object-position's X does
     nothing, and he sits where he sits in the file, at about 65% across.
     A small zoom is what buys the horizontal slack back, and the origin is
     what spends it. At 1.4 the window is the rightmost 71% of the file and
     92% puts him at 54% of the frame, which is where the reference has him. */
  /* ⚠️ The vertical aim had to come DOWN with the zoom. 12% was set against
     a 1.85 that cropped the wall away for it; at 1.12 the same aim left 45%
     of this 5/4 frame as empty wall above his head. 28% puts the headroom at
     about a quarter of the frame, which is where the reference has it. The
     16/10 frame below keeps its own aim for the same reason. */
  .rdr-photo img{--rdr-z:1.12;transform-origin:100% 44%;object-position:52% 28%;}

  /* ---- The angled cut, built to the reference ----------------------------
     Stacked, the photo and the panel were two rectangles meeting on a
     straight line, which reads as two things stacked rather than one card.
     The reference cuts the join on a slope so the panel slides up into the
     photo on the right.

     ⚠️ THE PHOTO IS CLIPPED, NOT THE PANEL, and that is the whole trick. Clip
     the panel and the triangle you open shows the CARD's background, not the
     photograph. Clipping the photo and sliding the panel up under it means
     the triangle is filled by the panel itself, which is what makes the two
     read as one shape.

     Three things have to agree and they are all --rdr-cut:
       · the photo's clip takes --rdr-cut off its bottom-RIGHT corner
       · the panel comes up by --rdr-cut so it sits under that corner
       · the panel's top padding grows by --rdr-cut so the first line of type
         does not ride into the slope
     Change the one number and the shape stays consistent. */
  .rdr-card{--rdr-cut:clamp(18px,6vw,34px);}
  .rdr-photo{
    /* ⚠️ z-index, and without it the panel's negative margin paints the panel
       OVER the photo's bottom-left and the cut disappears. The photo has to
       sit above the panel for its own clip to be what you see. */
    z-index:1;
    clip-path:polygon(0 0, 100% 0, 100% calc(100% - var(--rdr-cut)), 0 100%);
  }
  /* ⚠️ THE PADDING COMPENSATION BELONGS HERE, WITH THE CUT. It was written
     only in the 720 block at first, so between 721 and 1024 the slope existed
     and nothing made room for it: measured at 760, the first line of the panel
     sat in the diagonal with the words crossing the edge of the photograph.
     A rule that pays for a shape has to live at the same breakpoint the shape
     does.
     ⚠️ padding-top, not padding. The base rule sets all four sides with a
     shorthand and a shorthand here would reset the other three. */
  .rdr-panel{
    margin-top:calc(-1 * var(--rdr-cut));
    padding-top:calc(clamp(26px,3.2vw,44px) + var(--rdr-cut));
  }

  /* .value-grid responds in why.css */



  .cta-grid{grid-template-columns:minmax(0,1fr) minmax(0,1fr);min-height:0;}
  .cta-products{display:none;}
  .cta-phone{width:min(170px,100%);}

  .footer-grid{grid-template-columns:repeat(2,minmax(0,1fr));}
  .footer-brand{grid-column:1 / -1;}
}

@media (max-width:860px){

}

@media (max-width:720px){
  /* the mark is larger relative to the screen here, so it needs less */
  .story-mark{opacity:.26;}

  /* ---- Step 2 on a phone, built to the supplied reference ----------------
     The reference centres the whole head, keeps the three cards in a row
     rather than stacking them, and puts the band's disc beside its copy with
     the button full width underneath. Stacked, this section measured 1927px
     on a 390 screen and 731 of that was three cards one under another. */

  /* ⚠️ THE SECTION IS SCROLLED PAST, NOT READ IN ONE PIECE, and on a phone
     that is the thing to design for. Asked for after the arrangement above
     landed: "this part is too big — make sure kahit konting scroll kita na
     lahat." At 390 it measured 1377px against an 844px viewport with 75 of
     that under the header, so it took the better part of two screens.

     Every number below is a trim, not a redesign — the arrangement is the
     one the reference set and it does not change. The only floor that is
     not negotiable is the type: 12px for body-weight text on a phone,
     established when the hero's fact bar was audited at 390x844 and its
     10.5px was ruled too small. That bar is gone and the rule it proved
     is not, so .scard-say and .rdr-by both sit at 12 or above
     even where a smaller number would have bought height. */
  .story{padding-block:34px;}

  /* Centred head. The eyebrow is a flex row with a rule at each end, so it
     centres by justify-content, not text-align. */
  .story-copy{text-align:center;}
  .rule-eyebrow{justify-content:center;margin-bottom:10px;}
  /* Still a display serif carrying the section — 25px keeps two lines and
     the same voice; the clamp hands it back to the base 30px by 720. */
  .story-title{font-size:clamp(25px,6.4vw,30px);margin-bottom:10px;}
  /* ⚠️ margin-inline, not `margin:0 auto` — the shorthand would silently
     reset the margin-bottom the lede sets in the base rule. */
  .story-lede{max-width:none;margin-inline:auto;font-size:13px;line-height:1.55;margin-bottom:14px;}
  /* 33ch is a desktop measure sitting in a 350px column; centred it left a
     ragged 285px block inside a full-width one. */

  .story-ask{padding-left:0;max-width:none;text-align:center;font-size:13.5px;line-height:1.55;}
  /* The gold rail and the badge that sits on it are a left-edge device and
     there is no left edge to hang them on once the line is centred. The
     reference marks the question with a short rule underneath instead. */
  .story-ask::before,
  .ask-badge{display:none;}
  /* ⚠️ margin-top only. The base rule now sets the question's family, style,
     size and colour, and a shorthand here would have taken them with it. */
  .story-ask strong{margin-top:2px;}
  .story-ask strong::after{
    content:"";
    display:block;
    width:52px;height:2px;
    margin:8px auto 0;
    border-radius:2px;
    background:linear-gradient(90deg, var(--gold-soft), var(--gold) 50%, var(--gold-soft));
  }

  /* The gaps between the four blocks. 28/34/30 at 390 was 92px of the
     section spent on nothing. */
  .story-top{gap:18px;margin-bottom:20px;}

  /* The portrait was the single largest thing in the section at 278px of a
     1377px total. ⚠️ The aspect ratio is what frames the face — the 5/4 in
     the block above was chosen so one object-position works at every width,
     and a shorter frame crops from both edges, so the aim is re-set here
     rather than inherited. */
  .rdr-photo{aspect-ratio:16/10;}
  .rdr-photo img{object-position:52% 16%;}
  /* ⚠️ The top padding carries the cut. Written as a calc rather than a
     bigger number so it stays tied to --rdr-cut; a literal here goes stale
     the first time the slope changes. */
  .rdr-panel{padding:calc(18px + var(--rdr-cut)) 18px 20px;gap:12px;}
  .rdr-text{font-size:13.5px;line-height:1.5;}
  .rdr-by{padding-top:10px;}
  .rdr-by strong{font-size:13px;}
  /* 11.5px in the base rule is under the phone floor. Up, not down. */
  .rdr-by span{font-size:12px;line-height:1.35;}

  /* Three across, the way the reference has them.
     ⚠️ Longhands, not `gap:8px`. The row gap is read by the subgrid below as
     the gutter BETWEEN a card's own parts, so an 8 here would open 8px under
     the icon, the title, the divider and the lead as well. */
  .story-cards{
    grid-template-columns:repeat(3,minmax(0,1fr));
    column-gap:clamp(8px,2vw,16px);row-gap:0;
    margin-bottom:18px;
  }

  /* ⚠️ In a row the three titles wrap to different line counts, so the
     divider and the paragraph each landed at three different heights —
     measured 16.9px apart at 390, 360 and 320. The cards themselves were
     already the same height; it is the parts inside them that were ragged,
     which only a reading across the row shows.
     subgrid hands all three the row's tracks, so each part sits on one line
     across the row however the title above it wrapped. Guarded because
     without subgrid the cards must stay plain blocks, not a broken grid.
     ⚠️ FOUR ROWS, NOT FIVE — the lead and the sub used to be two paragraphs
     and are one marked paragraph now. The span has to match the count of
     children or the row runs out of tracks. */
  @supports (grid-template-rows:subgrid){
    .scard{display:grid;grid-template-rows:subgrid;grid-row:span 4;}
  }
  /* A 390 screen gives each card about 111px, a 720 one about 215. Everything
     inside is sized against the column it actually gets — ⚠️ fixed values
     tuned at 390 are 13px type in a 215px card at the top of this range,
     which is how a phone layout ends up looking broken on a small tablet.
     The low end of every clamp is the 390 value, the high end is the desktop
     one it hands back to at 720. */
  .scard{
    padding:clamp(12px,3.1vw,26px) clamp(7px,2vw,18px) clamp(14px,3.6vw,24px);
    border-radius:clamp(14px,3.6vw,var(--r-md));
  }
  .scard::after{height:2.5px;}
  .scard-ico{
    width:clamp(30px,7.7vw,46px);height:clamp(30px,7.7vw,46px);
    margin-bottom:clamp(8px,2.3vw,16px);
  }
  .scard-ico svg{width:clamp(15px,3.85vw,20px);height:clamp(15px,3.85vw,20px);}
  .scard-title{font-size:clamp(13px,3.33vw,16px);line-height:1.25;margin-bottom:clamp(8px,2.3vw,14px);}
  .scard-div{width:clamp(18px,4.6vw,26px);margin-bottom:clamp(8px,2.3vw,14px);}
  /* ⚠️ 12px is the floor and this line was under it at 11. Height bought
     below the floor is height bought from the reader. */
  .scard-say{font-size:12px;line-height:1.4;}
  /* On a 111px column the press has to be visible without a pointer, and 44px
     is wider than the card's own content box. */
  .scard:active .scard-div,
  .scard.is-pressed .scard-div{width:clamp(28px,7vw,44px);}

  /* The band: disc beside the copy on one row, button across both under it.
     ⚠️ Grid rather than the old flex column, which put the disc on a line of
     its own. It also retires a trap: .sb-copy carries flex:1 1 320px for the
     desktop row, and in a flex COLUMN that 320px became a minimum HEIGHT —
     the band measured 335x517 on a 390 screen. Under grid the flex basis is
     inert, so the height is the copy's again. */
  .story-band{
    display:grid;
    grid-template-columns:auto minmax(0,1fr);
    align-items:center;
    gap:10px 12px;
    padding:12px 14px;
    text-align:left;
  }
  .sb-play{width:38px;height:38px;}
  .sb-play svg{width:16px;height:16px;}
  .sb-title{font-size:13.5px;}
  .sb-sub{font-size:12px;line-height:1.4;}
  /* ⚠️ 44px is the touch floor and this is the one control in the section.
     The trimmed padding lands it at 44.6 — true, but true by 0.6px of a font
     metric. min-height states the floor instead of leaving it to arithmetic,
     so a fallback face with different metrics cannot quietly drop it under. */
  .sb-cta{
    grid-column:1 / -1;justify-content:center;
    padding:11px 20px;font-size:12.5px;
    min-height:44px;
  }
  /* ⚠️ The old .rdr-panel padding used to be repeated here, BELOW the
     compaction block above, and being later in the file it quietly won. The
     18/18/20 up there did nothing until this line went. One selector, one
     place. */

  /* The number goes on a phone — 24px of numeral plus its gap is a tenth of a
     360px row and the question is what people are reading — and the answer
     loses its indent with it.

     ⚠️ THE COLUMN TEMPLATE HAS TO GO WITH IT, and this is what broke: hiding
       .faq-n with display:none takes it out of the GRID, so the question
       moved into track 1 and the chevron into track 2. Track 1 was the
       numeral's, set to 0px — so every question wrapped one word per line and
       the chevron printed on top of the words. Two tracks now, because there
       are two items.

     ⚠️ Every contrast number at 390 passed while this was on screen. They
       sample beside the type, not the shape of the row; only the screenshot
       caught it. */
  .faq-grid{--faq-pad:16px;--faq-gap:12px;}
  .faq-n{display:none;}
  .faq-q{grid-template-columns:minmax(0,1fr) auto;padding:15px 16px;}
  .faq-t{font-size:13.5px;}
  .faq-a{padding:0 16px 18px;font-size:13px;}
  .faq-mark{width:30px;height:30px;}

  :root{--hdr-h:74px;}   /* the title card reads this too */
  .logo-mark{width:38px;height:38px;}
  .rdr-mark{width:35px;height:36px;}
  .rdr-line-1{font-size:15px;}
  .logo-line-1{font-size:19px;}
  .logo-line-2{font-size:16.5px;padding-left:12px;}



  .collection-grid{grid-template-columns:repeat(2,minmax(0,1fr));gap:14px;}
  .col-stage{height:104px;}


  .why-stats{gap:14px;}
  .stat + .stat{padding-left:14px;}




  .cta-grid{grid-template-columns:1fr;text-align:left;}
  .cta-phone{display:none;}
  .cta-actions .btn{flex:1 1 auto;justify-content:center;}

  /* ⚠️ THE TAP TARGETS ARE NOT THE THING TO CUT. Every footer link is a 68x44
     box and that 44 is deliberate — measured, and raised from 40 for the
     client's phone number; the note is further down this file. What follows
     takes back the space that is NOT a target.

     ⚠️ THE LONG LISTS ARE PAIRED WITH EACH OTHER. A two-up grid makes every
     row as tall as its tallest column, and the DOM order put Products (6
     links) beside Browse (3) and Company (3) beside Contact (5). Browse was
     therefore a 3-link list standing in a 301px box, and Company a 3-link list
     in a 269px box — 2 whole link-rows of nothing, twice. Pairing 6 with 5 and
     3 with 3 makes the rows as short as the content allows.

     ⚠️ order, WHICH MOVES THE PICTURE AND NOT THE READING. A screen reader
     still meets these four groups in their written order; only the eye sees
     the swap. That is a real mismatch and worth stating — it is mild here
     because the four are independent link groups with no sequence between
     them, and the alternative was moving them in the DOM, which would have
     reordered the desktop row as well. */
  .footer-grid{grid-template-columns:repeat(2,minmax(0,1fr));gap:18px 14px;padding-bottom:22px;}
  /* ⚠️ :nth-child, AND THE INDICES START AT 2. The first go used
     :nth-of-type(1..4) and moved nothing at all: nth-of-type counts siblings
     of the same ELEMENT TYPE, and .footer-brand is a <div> like the four
     columns are, so it occupies index 1 and every number was out by one.
     .footer-col:nth-of-type(1) matched nothing whatever. Measured after that
     attempt: Products still 291px beside Browse 291px, exactly as before.
     nth-child counts children, which is what "the brand block then the four
     lists" actually is. */
  .footer-col:nth-child(2){order:1;}   /* Products — 6 links */
  .footer-col:nth-child(5){order:2;}   /* Contact  — 5 links */
  .footer-col:nth-child(3){order:3;}   /* Browse   — 3 links */
  .footer-col:nth-child(4){order:4;}   /* Company  — 3 links */
  .footer-col h4{margin-bottom:6px;}
  .site-footer{padding-top:clamp(26px,4vw,64px);}
  /* ⚠️ THE CORNER IS RESERVED FOR THE BACK-TO-TOP BUTTON. It is
     position:fixed at the bottom right, so nothing in the flow can get out of
     its way — measured at 390, "Store Policies" ended at 344 and the button
     began at 334, printing the two on top of each other. 320 and 1440 both
     reported clean, because at 320 the row has already wrapped and at 1440
     there is half a screen between them; 390 is the width in between and the
     one the page is most looked at on.
     This row already wraps, so reserving the corner puts the last item on its
     own line rather than underneath the button. */
  .footer-bottom{
    justify-content:flex-start;
    padding-right:clamp(52px,14vw,62px);
  }
}

@media (max-width:460px){
  .header-inner{gap:10px;}
  .header-actions .btn-sm{padding:10px 15px;font-size:12px;gap:7px;}
  /* ⚠️ was width:36px. The burger is the only way to the nav on a phone and
     36x44 is under the 44x44 every touch guideline asks for. The width was
     bought to make room that is not needed: at 393 the bar carries a 102px
     logo, a ~140px pill and two 10px gaps, which is 306px inside a 353px
     content box. Keep the 44 and narrow the padding instead, so the three
     rules stay the same length they are now. */
  .nav-toggle{width:44px;padding:0 10px;}


  .col-card{padding:14px 14px 18px;}
  .col-stage{height:92px;margin-bottom:12px;}
  .col-card p{margin-bottom:14px;}


}

@media (max-width:360px){
  .header-actions .btn-sm{display:none;}
  .collection-grid,

}

/* ---------- Motion / print ---------- */
@media (prefers-reduced-motion:reduce){
  html{scroll-behavior:auto;}
  *,*::before,*::after{
    animation-duration:.01ms !important;
    animation-iteration-count:1 !important;
    transition-duration:.01ms !important;
  }
  [data-reveal]{opacity:1;transform:none;}
  [data-stage] > *{opacity:1;translate:none;}
}

@media print{
  .site-header,.cta-phone,.to-top,.inq-fab{display:none;}
  body{background:#fff;}
  [data-reveal]{opacity:1;transform:none;}
  [data-stage] > *{opacity:1;translate:none;}
}


/* ---------- Idle sections ----------
   86 CSS animations were running from the moment the page loaded, most of them
   looping backgrounds on sections far below the fold — noirBreathe alone
   repaints background-position across 1533x517 and 1533x392. All of that
   competes with the hero film's decoder for the same frame budget, and the
   visitor cannot see any of it.

   main.js marks a block idle while it is more than 300px from the viewport.
   Paused, not disabled: an animation resumes exactly where it stopped, so
   nothing restarts or jumps when it scrolls back in.

   ⚠️ animation-play-state only, never display or visibility. Transitions are
   untouched (the reveal system runs on those), layout is untouched, and the
   scrollbar does not move — which is the trap with content-visibility. */
/* =========================================================
   SKIPPING WHAT IS NOT ON SCREEN

   Measured over one full scroll of this page at 390x844,
   using Performance.getMetrics rather than frame intervals —
   a frame interval quantises to whole vsyncs once a page is
   slow and can only tell you "2.5 frames" or "3 frames":

       total main thread     ~10,800ms
       of which RecalcStyle   ~8,600ms   (80%)
       frames over 33ms        188 of 240

   ⚠️ NOTHING ELSE MOVED IT, including several things that
     looked certain. Disabling every CSS animation, every
     filter, every box-shadow, every backdrop-filter and the
     entire fixed atmosphere layer each left the page within
     noise of where it started, and `contain: layout paint`
     made it slightly worse. A deliberately plain page in the
     same harness runs at 6.9ms a frame with no slow frames, so
     the instrument was never the problem.

   content-visibility:auto lets the engine skip style, layout
   and paint for a section that is off screen. That is what
   .is-idle below has been approximating and cannot achieve: a
   paused animation is still a styled element, and recalculating
   its style is the 80%.

       with it              frames over 33ms: 45, worst 111ms

   ⚠️ IT MOVES THE DOCUMENT UNLESS EVERY SECTION IS TOLD ITS OWN
     HEIGHT. With a guessed 900px the page grew 3,110px as
     sections resolved; with each section's measured height it
     stays put. main.js measures them — see contentVisibility()
     there — and re-measures on resize. Nothing here guesses.

   ⚠️ .cv-on IS ADDED BY SCRIPT AND BY NOTHING ELSE. Without it
     this rule does not apply and the page is what it was.
   ========================================================= */
.cv-on section[data-cv]{
  content-visibility:auto;
  /* auto means "use this until you have seen the real one, then
     remember the real one" — the var is the measured height */
  contain-intrinsic-size:auto var(--cv-h, 900px);
}

.is-idle,
.is-idle *,
.is-idle *::before,
.is-idle *::after{animation-play-state:paused !important;}

/* ---------- Ambience yields to the film ----------
   html.is-film-live means the hero video is decoding RIGHT NOW. While it is,
   the six largest ambient layers hold still: three atmosphere orbs, the light
   beam and both fronds. Between them that is roughly 3.5 million pixels of
   composited, drifting layer sitting over a video that is already asking for
   24 frames a second.

   Paused, not hidden — they stay exactly where they are, so nothing on screen
   moves or disappears when the class goes on or off. The small sparkles and
   motes keep running; they cost almost nothing and they are the part you
   actually notice.

   ⚠️ This only ever engages while the film plays. Reduced motion, a narrow
   window and a failed load all leave the film off, and in every one of those
   cases the ambience runs exactly as it always did. */
.is-film-live .atmos-orb,
.is-film-live .atmos-beam,
.is-film-live .tc-frond{animation-play-state:paused !important;}

/* ---------- Phone legibility ----------
   Audited at 390x844: these were the runs of type that fell under 11.5px, and
   the links whose tappable box was under 30px tall. Nothing here touches the
   desktop — the floors only apply where the screen is small enough to need
   them. */
@media (max-width:700px){
  /* 11px of letterspaced uppercase is fine on a laptop and thin on a phone.
     The tracking comes down with the size going up, or the label runs wider
     than the column it labels. */
  .eyebrow{font-size:12px;letter-spacing:.16em;}
  .rule-eyebrow{font-size:12px;letter-spacing:.18em;}
  .footer-col h4{font-size:12.5px;letter-spacing:.14em;}

  /* ⚠️ TAP TARGETS, not type size. A footer link was a 15px-tall line box:
     the words were readable, the target was not. This pads the inline box and
     takes the list's own margin back out so the footer does not grow by the
     same amount twice.
     ⚠️ 7px, AND IT HAS BEEN 10 AND 12 BEFORE THIS. The history matters here
     because the number keeps moving for good reasons that point opposite ways.
     15px line box was unhittable; 10 took it to 40; 12 took it to 44, which is
     WCAG 2.5.5 at AAA. At 44 the footer read as a list of widely spaced
     strangers — measured, 44px from one link to the next with 12.5px of type
     in it, so 24 of every 44 was empty, and it was asked to come in.
     ⚠️ 34px IS STILL A TARGET. WCAG 2.5.8, the AA rule, asks 24x24; this is
     half again that, and a deliberate trade of the AAA nicety for the density
     that was asked for. Do not read it as an oversight and "restore" it to 44
     without asking — that is what was just undone. */
  .footer-col a{display:inline-block;padding-block:7px;}
  .footer-col li{margin-bottom:0;}
  .link-arrow{padding-block:9px 6px;}
  .footer-legal a{display:inline-block;padding-block:10px;}

  /* ⚠️ The four links left under 30px after this are inline links inside
     running prose — "the main store", "cinemaskinfx.com". They are NOT padded
     on purpose: giving an inline link a 44px box inside a paragraph pushes the
     lines around it apart and makes the prose look broken. A target inside a
     sentence is the standing exception to the minimum, and this is that. */
}
