/* ============================================================================
   delEat — marketing site
   ========================================================================= */

/* ---------------------------------------------------------------- nav ---- */
:root { --nav-h: 74px; }
/* The nav sits in flow, so the space it occupies would paint in the body's bone
   above a coloured opening section — an off-white strip over the hero. The
   opening sections that carry a colour slide up underneath it instead, with a
   spacer inside them so their own ground reaches the top of the page. */
.hero, .pagehead, .mod-hero { margin-top: calc(-1 * var(--nav-h)); }
.hero::before, .pagehead::before, .mod-hero::before { content: ""; display: block; height: var(--nav-h); }
.nav {
  /* A floating pill rather than a full-width bar — the shape Fauna Robotics
     uses, and the one the type rail and the palette switch already are, so the
     page now has one idea about what a control looks like. It is still the dark
     variant, so it still needs its own ground: the body behind it is bone, and
     bone type on bone is nothing at all. */
  position: sticky; z-index: 60;
  /* Round 53k: the sticky box starts below the status bar (see .local-nav in
     css/apple.css for the filmed reason). Zero on desktop. */
  top: env(safe-area-inset-top, 0px);
  padding: var(--s-3) clamp(10px, 2vw, 20px) 0;
  background: transparent; border: 0;
  transition: padding var(--d-3) var(--glide);
}
.nav-in {
  display: flex; align-items: center; gap: var(--s-5);
  height: 62px; padding-inline: var(--s-4);
  border-radius: 999px;
  background: color-mix(in srgb, var(--ground) 82%, transparent);
  -webkit-backdrop-filter: saturate(1.7) blur(20px);
  backdrop-filter: saturate(1.7) blur(20px);
  border: 1px solid color-mix(in srgb, var(--bone) 12%, transparent);
  box-shadow: inset 0 1px 0 color-mix(in srgb, var(--bone) 14%, transparent);
  transition: box-shadow var(--d-3) var(--glide), border-color var(--d-3) var(--glide);
}
.nav.is-stuck { padding-top: var(--s-2); }
.nav.is-stuck .nav-in {
  box-shadow: inset 0 1px 0 color-mix(in srgb, var(--bone) 14%, transparent),
              0 14px 34px -18px rgba(0,0,0,.7);
  border-color: color-mix(in srgb, var(--bone) 18%, transparent);
}

/* ---- Round 53: the pill over a BLACK ground -----------------------------
   The pill is `var(--ground)` at 82%, and --ground is #000 on every page, so
   over the black hero it was a black pill on black: only the logo, the link
   text and the two buttons were visible at all. Over bone (Pricing, the
   module and solution pages) the same pill is high contrast and deliberate,
   so it stays exactly as it was.
   Where the nav sits over a dark ground it takes the treatment the local nav
   already uses when it slides in on scroll -- the same white glass, blur,
   saturation and orange wash -- and with it the ink wordmark and ink type,
   because bone on that glass would be the same invisibility the other way
   round. js/site.js decides, by asking which [data-ground] section the pill's
   bottom edge is actually over, so this is right on every page and stays
   right while a sticky pill travels between sections. -------------------- */
/* Round 63: the nav carries the pill (the bone key with the bite) and nothing
   else, at every scroll position and over dark or light sections. The plain
   wordmark that used to swap in over dark sections is gone from the markup, so
   the logo can never change width and no nav item can shift. */

.nav.over-dark .nav-in {
  background:
    radial-gradient(72% 88% at 14% 8%, rgba(255,107,61,.10), rgba(255,107,61,0) 62%),
    rgba(255,255,255,.55);
  -webkit-backdrop-filter: blur(18px) saturate(150%);
          backdrop-filter: blur(18px) saturate(150%);
  border: 1px solid rgba(255,255,255,.40);
  box-shadow: 0 12px 40px rgba(0,0,0,.12);
}

.nav.over-dark .nav-in,
.nav.over-dark .nav-trigger,
.nav.over-dark .nav-links a { color: var(--n-900); }
.nav.over-dark .nav-trigger .nl-t { color: inherit; }

/* the accessibility fallbacks above take the glass away; keep them winning */
@media (prefers-reduced-transparency: reduce), (prefers-contrast: more) {
  .nav.over-dark .nav-in { background: var(--n-0); backdrop-filter: none; -webkit-backdrop-filter: none; }
}
@media (prefers-contrast: more) {
  .nav.over-dark .nav-in { border: 2px solid var(--n-900); }
}
/* Round 22, Apple Design audit section 14: the two accessibility signals the
   site had never answered. Reduced transparency drops the blur and takes the
   background to the theme's own solid ground (already a token, so this reads
   identically in the dark nav and the light module header below); contrast
   more adds a fully opaque, clearly bordered edge on top of that. */
@media (prefers-reduced-transparency: reduce), (prefers-contrast: more) {
  .nav-in { background: var(--ground); backdrop-filter: none; -webkit-backdrop-filter: none; }
}
@media (prefers-contrast: more) {
  .nav-in { border: 2px solid var(--bone); }
}
/* the mega menu hangs off the pill, so it needs its own offset */
.nav .nav-menu { top: calc(100% + 10px); }
@media (max-width: 620px) { .nav { padding-inline: 8px; } .nav-in { padding-inline: var(--s-3); } }
/* nav-in's own 24px gap still falls on both sides of the zero-width
   spacer between brand and .nav-cta -- at 320-360px that alone was most
   of the header's remaining overflow, so it tightens along with the
   pill's own padding/gap below. */
@media (max-width: 360px) { .nav-in { gap: 12px; } }
.nav-cta .btn {
  border-radius: 999px; height: 42px; padding-inline: 18px;
  box-shadow: none; transform: none;
}
/* the ghost button is already display:none by 1020px, but the remaining
   primary pill ("Start free trial") plus the 42px hamburger still overran
   a 320px viewport by ~18px -- gap and padding both come down a little,
   pixels the pill can spare, so the header never forces the page wider
   than the narrowest supported phone. (The gap half of this lives further
   down, after the base `.nav-cta` rule it overrides -- same specificity,
   so it has to win on source order, not just the media condition.) */
@media (max-width: 360px) {
  .nav-cta .btn { padding-inline: 10px; }
}
.nav-cta .btn::after { display: none; }        /* no keycap bottom edge here */
.nav-cta .btn-ghost { box-shadow: inset 0 0 0 1.5px color-mix(in srgb, var(--bone) 34%, transparent); }
.nav-cta .btn:hover { transform: translateY(-1px); }
.nav-cta .btn:active { transform: translateY(0); }
.brand { display: flex; align-items: center; gap: 10px; flex: none; }
.brand img { height: 24px; width: auto; }
.brand .mk { height: 30px; width: 30px; }
/* Round 52: the bitten capsule replaces the old mark-plus-wordmark lockup.
   The bar is 62px around a 42px content row, so 38px is the tallest the
   capsule goes without touching the bar's own rounding or out-weighing the
   CTA. The footer has the room to run it at the pack's stated 140px minimum.
   Specificity: .brand .cap-lg (0,2,0) beats .brand img (0,1,1). */
.brand .cap-lg { height: 38px; width: auto; }
.foot-brand .cap-lg.cap-foot { height: 52px; width: auto; }

.nav-links { display: flex; align-items: center; gap: 2px; margin-left: var(--s-3); }

/* Direct children only. The mega-menu links live inside .nav-links too, and
   the old descendant selector was painting them bone-on-white on hover —
   which is why a hovered item lost its title. */
.nav-links > a, .nav-trigger {
  font-size: var(--t-sm); font-weight: 500; color: var(--ink-2);
  padding: 9px 13px; border-radius: var(--r-2);
  display: inline-flex; align-items: center; gap: 5px;
  transition: color var(--d-1) var(--snap), background var(--d-1) var(--snap);
}
.nav-links > a:hover, .nav-trigger:hover,
.has-menu:hover .nav-trigger, .has-menu:focus-within .nav-trigger {
  color: var(--ink); background: rgba(255,255,255,.07);
}
.nav-links > a[aria-current="page"] { color: var(--ink); font-weight: 600; }
.nav-trigger .cv { transition: transform var(--d-2) var(--snap); }
.has-menu:hover .nav-trigger .cv, .has-menu:focus-within .nav-trigger .cv { transform: rotate(180deg); }
.nav-spacer { flex: 1; }
.nav-cta { display: flex; align-items: center; gap: var(--s-3); flex: none; }
@media (max-width: 360px) { .nav-cta { gap: 8px; } }
/* mobile-only echo of .nav-cta's two buttons, living inside #nav-links so
   the slide-out sheet carries them too -- the header's own .nav-cta keeps
   the primary button visible at all times, but "Book a demo" was dropped
   entirely below 1020px (.btn-ghost display:none) with nowhere else to go. */
.nav-links-cta { display: none; }
/* round 30, item 5: the phone sheet's row glyphs; the desktop bar is words */
.nav-links .nl-ic { display: none; }

/* ---- mega menu ------------------------------------------------------------
   Sapaad's shape, because it is the right one for eighteen modules: a rail of
   categories on the left, and the cards for the hovered category on the right.
   The rail means nobody reads eighteen labels to find one.                  */
.has-menu { position: static; }
.nav-menu {
  position: absolute; left: 0; right: 0; top: 100%;
  background: var(--n-0); color: var(--n-900);
  border-top: 1px solid var(--n-200);
  box-shadow: 0 24px 48px rgba(0,0,0,.28);
  opacity: 0; visibility: hidden; transform: translateY(-8px);
  transition: opacity var(--d-2) var(--glide), transform var(--d-2) var(--glide), visibility var(--d-2);
}
.has-menu:hover > .nav-menu, .has-menu:focus-within > .nav-menu {
  opacity: 1; visibility: visible; transform: none;
}
.nav-menu-in {
  width: 100%; max-width: var(--page-wide); margin-inline: auto;
  padding: var(--s-6) var(--gutter) var(--s-7);
  display: grid; grid-template-columns: 232px minmax(0, 1fr); gap: var(--s-8);
}
.mm-rail { display: grid; gap: 2px; align-content: start; border-right: 1px solid var(--n-200); padding-right: var(--s-5); }
.mm-rail button {
  position: relative; text-align: left; width: 100%;
  padding: 11px 14px 11px 0;
  font-family: var(--font-display); font-size: var(--t-sm); font-weight: 700;
  color: var(--n-400);
  transition: color var(--d-1) var(--snap);
}
.mm-rail button::after {
  content: ""; position: absolute; right: calc(-1 * var(--s-5) - 1px); top: 8px; bottom: 8px;
  width: 3px; background: var(--o-500); border-radius: 2px;
  opacity: 0; transition: opacity var(--d-1) var(--snap);
}
.mm-rail button[aria-selected="true"] { color: var(--n-900); }
.mm-rail button[aria-selected="true"]::after { opacity: 1; }
.mm-rail button:hover { color: var(--n-700); }

.mm-panel { display: none; }
.mm-panel.is-on { display: block; animation: mmIn var(--d-2) var(--glide) both; }
@keyframes mmIn { from { opacity: 0; transform: translateX(6px); } to { opacity: 1; transform: none; } }
.mm-cards { display: grid; grid-template-columns: repeat(3, minmax(0,1fr)); gap: var(--s-3); }
@media (max-width: 1180px) { .mm-cards { grid-template-columns: repeat(2, minmax(0,1fr)); } }
.mm-card {
  display: grid; grid-template-columns: 22px minmax(0,1fr); gap: var(--s-3);
  align-items: start;
  padding: var(--s-4); border-radius: var(--r-3);
  background: var(--n-50);
  transition: background var(--d-1) var(--snap), transform var(--d-1) var(--snap);
}
.mm-card:hover { background: var(--o-50); transform: translateY(-1px); }
/* Round 25, item 13: the Solutions menu drew its glyphs in 40px white
   rounded plates with a shadow and an orange ink, while the Product menu
   beside it drew naked 22px ink glyphs on the same panel. Two treatments,
   two themes, one bar of the nav apart. Round 14 took the tile off every
   glyph on the site and this rule was the one place it never reached. */
.mm-card .ic {
  width: 22px; height: 22px; display: grid; place-items: center;
  border-radius: 0; background: none; color: #000000; box-shadow: none;
  stroke-width: 1.75; stroke-linecap: round; stroke-linejoin: round;
}
/* These MUST set their own colour — .nav-links > a never matches them. */
.mm-card b { display: block; font-family: var(--font-display); font-size: var(--t-sm);
  font-weight: 700; color: var(--n-900); line-height: 1.3; }
.mm-card span { display: block; font-size: var(--t-xs); color: var(--n-500); margin-top: 2px; line-height: 1.45; }

/* the promoted strip that sits under one category, as Sapaad does */
.mm-feature {
  margin-top: var(--s-5); padding: var(--s-5);
  border-radius: var(--r-4); background: var(--n-900); color: var(--bone);
  display: flex; align-items: center; gap: var(--s-6); flex-wrap: wrap;
}
.mm-feature h5 { font-family: var(--font-display); font-size: var(--t-base); font-weight: 700; }
.mm-feature p { font-size: var(--t-xs); color: #B5B2AE; margin-top: 3px; max-width: 46ch; }
.mm-feature .stats { display: flex; gap: var(--s-6); margin-left: auto; }
.mm-feature .stats div b { display: block; font-family: var(--font-mono); font-size: var(--t-md); color: var(--o-400); }
.mm-feature .stats div span { font-size: 10px; color: var(--ui-ink-3); text-transform: uppercase; letter-spacing: .07em; }

/* ---- mobile ---------------------------------------------------------------*/
.nav-toggle {
  display: none; width: 42px; height: 42px; border-radius: var(--r-2);
  place-items: center; color: var(--ink);
  transition: transform .1s ease-out;
}
/* Round 22, Apple Design audit section 1: the three bars already animate
   into a cross on open, but nothing acknowledged the press itself. */
.nav-toggle:active { transform: scale(.9); }
@media (prefers-reduced-motion: reduce) { .nav-toggle:active { transform: none; } }
.nav-toggle span, .nav-toggle span::before, .nav-toggle span::after {
  content: ""; display: block; width: 18px; height: 2px; background: currentColor; border-radius: 2px;
  transition: transform var(--d-2) var(--snap), opacity var(--d-1) var(--snap);
}
.nav-toggle span::before { transform: translateY(-6px); }
.nav-toggle span::after  { transform: translateY(4px); }
.nav.is-open .nav-toggle span { background: transparent; }
.nav.is-open .nav-toggle span::before { transform: rotate(45deg); }
.nav.is-open .nav-toggle span::after  { transform: rotate(-45deg) translateY(-2px); }

@media (max-width: 1020px) {
  .nav-links, .nav-cta .btn-ghost { display: none; }
  .nav-toggle { display: grid; }
  /* Round 30, item 4: the open menu is the top of the page. The whole header
     goes `fixed` at z-index 1000, above the local nav (55), the sticky CTA
     (55), the chapter sheet (301) and the boot veil (300), so nothing can
     sit over a link; and the pill drops its backdrop-filter while open,
     because a backdrop-filter makes its element the containing block of
     every `position: fixed` descendant, which is what had pinned the "fixed"
     sheet to a pill that scrolls away with the home page. The page itself is
     held by js/site.js's pageLock, not by `overflow: hidden`, which iOS
     ignores for touch. (`#nav.nav` outranks `body:has(#localnav) .nav`.) */
  #nav.nav.is-open { position: fixed; top: 0; left: 0; right: 0; z-index: 1000; }
  .nav.is-open .nav-in {
    -webkit-backdrop-filter: none; backdrop-filter: none;
    background: var(--ground);
  }
  .nav.is-open .nav-links {
    /* An explicit height in `dvh` tracks the iOS toolbar as it shows and
       hides, so the sheet is exactly as tall as what is visible and its last
       link is always reachable by scrolling the sheet itself; the `vh` line
       is the fallback for an engine without `dvh`. The sheet is its own
       scroller and `overscroll-behavior: contain` keeps a fling at either
       end from being handed to the page. */
    display: block; position: fixed; top: 70px; left: 0; right: 0; z-index: 1;
    height: calc(100vh - 70px); height: calc(100dvh - 70px);
    overflow-y: auto; overscroll-behavior: contain; -webkit-overflow-scrolling: touch;
    padding: var(--s-3) var(--gutter) calc(var(--s-9) + env(safe-area-inset-bottom, 0px));
    margin: 0;
    /* round 30, item 5: a light sheet, the desktop mega menu's own ground.
       The header is `.on-dark`, so the ink tokens are reset here for
       everything the sheet holds. */
    background: #F7F3EE; color: #000000;
    --ink: #000000; --ink-2: #3A3A3C; --ink-3: #6E6E73;
    --line: rgba(0,0,0,.10); --line-soft: rgba(0,0,0,.06); --surface: #FFFFFF;
    border-radius: 22px 22px 0 0;
    box-shadow: 0 -1px 0 rgba(0,0,0,.04);
  }
  .nav.is-open .nav-links > a, .nav.is-open .nav-trigger {
    display: grid; grid-template-columns: 22px minmax(0, 1fr) auto; align-items: center;
    column-gap: 14px; width: 100%; text-align: left;
    font-size: var(--t-md); font-weight: 600; padding: 15px 0; color: #000000;
    background: none; border-bottom: 1px solid rgba(0,0,0,.10); border-radius: 0;
  }
  .nav.is-open .nav-links > a:hover, .nav.is-open .nav-trigger:hover,
  .nav.is-open .has-menu:hover .nav-trigger, .nav.is-open .has-menu:focus-within .nav-trigger { background: none; color: #000000; }
  .nav.is-open .nav-links .nl-ic { display: grid; color: #000000; }
  /* the chevron states the accordion, not focus or hover */
  .nav.is-open .has-menu .nav-trigger .cv { transform: none; }
  .nav.is-open .has-menu.is-expanded .nav-trigger .cv { transform: rotate(180deg); }
  .nav.is-open .nav-links > a[aria-current="page"],
  .nav.is-open .nav-links > a[aria-current="page"] .nl-ic { color: #FF6B3D; }
  .nav.is-open .nav-links-cta {
    display: grid; gap: 10px; margin-top: var(--s-5);
  }
  .nav.is-open .nav-links-cta .btn { width: 100%; height: 46px; }
  .nav.is-open .has-menu { position: static; }
  .nav.is-open .nav-menu {
    position: static; opacity: 1; transform: none;
    background: transparent; border: 0; box-shadow: none;
    max-height: 0; overflow: hidden;
    /* collapsed panels are hidden from focus and from the tap as well as
       from sight; visibility waits for the close to finish */
    visibility: hidden;
    transition: max-height var(--d-3) var(--glide), visibility 0s linear var(--d-3);
  }
  .nav.is-open .has-menu.is-expanded .nav-menu { max-height: 2400px; visibility: visible; transition-delay: 0s; }
  .nav.is-open .nav-menu-in { display: block; padding: var(--s-2) 0 var(--s-4); max-width: none; }
  .nav.is-open .mm-rail { display: none; }
  .nav.is-open .mm-panel { display: block !important; animation: none; }
  .nav.is-open .mm-cards { grid-template-columns: 1fr; gap: 0; }
  .nav.is-open .mm-card { background: transparent; padding: 11px 0; grid-template-columns: 22px minmax(0, 1fr); column-gap: 14px; border-radius: 0; }
  .nav.is-open .mm-card:hover { transform: none; }
  .nav.is-open .mm-card .ic, .nav.is-open .mm-card .ic-tile { background: none; color: #000000; box-shadow: none; }
  .nav.is-open .mm-card b { color: #000000; }
  .nav.is-open .mm-card span { color: #A1A1A6; } /* night ink-2: 8.2:1, was ink-3 at 4.1 */
  .nav.is-open .mm-feature { display: none; }
  body.nav-locked { overflow: hidden; }
}

/* ------------------------------------------------------------- hero ------ */
.hero {
  position: relative; overflow: hidden; min-height: 100vh;
  display: flex; flex-direction: column; justify-content: center; gap: clamp(20px, 3vw, 40px);
  padding-block: clamp(26px, 3.2vw, 46px) clamp(28px, 3.6vw, 52px);
}
/* Film grain. The Astig poster is heavily grained and the ground reads flat
   without it — one inline SVG turbulence, tiled, at low opacity. */
.grain {
  position: absolute; inset: 0; z-index: 0; pointer-events: none;
  opacity: .28; mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23n)' opacity='.6'/%3E%3C/svg%3E");
}
.hero-in {
  position: relative; z-index: 1;
  display: grid; grid-template-columns: minmax(0,.84fr) minmax(0,1.16fr);
  gap: clamp(20px, 3.4vw, 52px); align-items: center;
}
@media (max-width: 1000px) { .hero-in { grid-template-columns: 1fr; } }

.hero h1 { margin-top: var(--s-3); }
/* The emphasis half of the headline. White is the brief, and on the accent band
   white alone is 3.0:1 on orange and 1.6:1 on lime, so it carries a hairline
   stroke painted BEHIND the fill — the letterform keeps its weight and still
   has an edge. The glitch is the one from thebuzzpeople.com, same shape and
   same timing: two coloured copies behind the text, clipped into bands and
   knocked sideways for 0.42s in two steps, the second running the sequence in
   reverse so the channels never agree. It fires on an event, not on a loop. */
.hero-copy h1 i {
  font-style: normal; position: relative; display: inline-block;
  color: #FFFFFF;
  -webkit-text-stroke: 1.5px rgba(20,16,8,.42);
  paint-order: stroke fill;
  text-shadow: 0 3px 16px rgba(20,16,8,.28);
}
.hero-copy h1 i::before,
.hero-copy h1 i::after {
  content: attr(data-glitch); position: absolute; inset: 0; z-index: -1;
  pointer-events: none; user-select: none;
  -webkit-text-stroke: 0; text-shadow: none;
}
.hero-copy h1 i::before { color: var(--o-500); }
.hero-copy h1 i::after  { color: #4B69F0; }
.hero-copy h1 i.is-glitching::before { animation: .42s steps(2) 1 buzzGlitch; }
.hero-copy h1 i.is-glitching::after  { animation: .42s steps(2) 1 reverse buzzGlitch; }
@keyframes buzzGlitch {
  0%   { clip-path: inset(0);            transform: translate(-2px, 0); }
  20%  { clip-path: inset(0 0 62%);      transform: translate(-6px, -2px); }
  40%  { clip-path: inset(52% 0 22%);    transform: translate(5px, 2px); }
  60%  { clip-path: inset(28% 0 46%);    transform: translate(-5px, 1px); }
  80%  { clip-path: inset(74% 0 8%);     transform: translate(4px, -1px); }
  100% { clip-path: inset(0);            transform: translate(-2px, 0); }
}
@media (prefers-reduced-motion: reduce) {
  .hero-copy h1 i.is-glitching::before,
  .hero-copy h1 i.is-glitching::after { animation: none; }
}

/* The product proposition, said plainly and immediately under the headline. */
.hero-prop {
  margin-top: var(--s-4);
  font-family: var(--font-display); font-weight: 800;
  font-size: var(--t-lg); letter-spacing: var(--tr-tight); line-height: 1.2;
  max-width: 20ch; color: var(--ink);
}
.hero-prop span { display: block; font-weight: 500; font-size: var(--t-sm);
                  letter-spacing: 0; color: var(--ink-2); margin-top: 6px; }
.hero .lede { margin-top: var(--s-4); font-size: var(--t-md); color: var(--ink-2); max-width: 44ch; }
.hero-actions { display: flex; flex-wrap: wrap; gap: var(--s-3); margin-top: var(--s-5); }
/* The three proof points, folded into one mono line under the buttons rather
   than three bulleted spans -- it reads like a status readout, not a list. */
.hero-note {
  margin-top: var(--s-4); font-family: var(--font-mono); font-size: var(--t-xs);
  letter-spacing: .01em; color: var(--ink-2);
}
/* The headline sits on a frosted plate now that the field drifts behind it --
   same recipe as .float-plate, left-aligned and sized to the copy column
   rather than centred like the band's own version. */
.hero-copy.hero-plate {
  position: relative; z-index: 1; display: block; text-align: left;
  padding: clamp(24px, 3vw, 44px); border-radius: 30px;
  background: color-mix(in srgb, var(--hero-mid) 42%, transparent);
  -webkit-backdrop-filter: blur(22px) saturate(1.4); backdrop-filter: blur(22px) saturate(1.4);
  border: 1px solid color-mix(in srgb, #FFFFFF 28%, transparent);
  box-shadow: inset 0 1px 0 color-mix(in srgb, #FFFFFF 38%, transparent), 0 30px 60px -30px rgba(58,16,4,.5);
}
.hero-copy.hero-plate h1 {
  font-size: clamp(56px, 5.6vw, 96px); line-height: 1.0; letter-spacing: var(--tr-tight);
  max-width: 16ch;
}
@media (max-width: 860px) { .hero-copy.hero-plate h1 { font-size: clamp(40px, 11vw, 64px); } }
/* The hero's own field: fewer, larger and slower than the band's version
   (see .float-field / .fo further down), pinned behind the copy and the
   machine instead of crossing them. */
.hero-field {
  top: 0; left: 0; right: 0; bottom: auto; height: 100vh; z-index: 0;
}

/* ---- the machine ----------------------------------------------------------
   The rig is a rendered plate (Higgsfield, from the Astig reference), cropped
   and feathered so it dissolves into the band. Only the SCREEN is live: an
   absolutely-positioned layer sitting exactly on the glass, measured off the
   plate, so it tracks at any width.                                        */
.machine {
  position: relative; width: min(100%, 660px); margin-inline: auto;
  /* The rig tilts toward the cursor as one object: plate, live screen and pool
     shadow at different depths. That needs preserve-3d, and a filter on this
     element would flatten it — so the contact shadow lives on the plates. */
  transform-style: preserve-3d;
  transform: rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg));
  will-change: transform;
}
.hero-in { perspective: 1400px; }
.machine-plate { filter: drop-shadow(0 26px 26px rgba(58,16,4,.42)); }
.machine[data-machine="picco"] .machine-plate { filter: drop-shadow(15px 19px 0 rgba(58,16,4,.20)); }
.machine::after {                    /* the pool the machine stands in */
  content: ""; position: absolute; left: 8%; right: 8%; bottom: -2%; height: 7%;
  background: radial-gradient(closest-side, rgba(58,16,4,.34), transparent 78%);
  filter: blur(6px); z-index: -1; pointer-events: none;
  transform: translateZ(-40px) translate(calc(var(--ry, 0deg) * -1.2px / 1deg), calc(var(--rx, 0deg) * 1.2px / 1deg));
}
.machine-plate { display: block; width: 100%; height: auto; }

/* Two machines, one rig. Each plate carries its own glass rectangle, measured
   off that plate by tools/mkmachine.py — swap a plate and those four numbers
   must be re-measured, never guessed. */
.machine { --gl: 18.55%; --gt: 6.59%; --gw: 45.16%; --gh: 40.38%; }
.machine[data-machine="picco"] {
  --gl: 20.09%; --gt: 7.07%; --gw: 48.01%; --gh: 37.66%;
}
.machine[data-machine="picco"]::after { display: none; }
.machine-plate[hidden] { display: none; }

.crt {
  position: absolute;
  left: var(--gl); top: var(--gt); width: var(--gw); height: var(--gh);
  transform: translateZ(34px);
}
/* A CRT is a curved tube. A rectangle of live HTML laid over the measured box
   spills onto the bezel at the corners and reads as stuck on rather than behind
   glass, so the layer takes the tube's corner rounding. */
.crt-screen {
  position: absolute; inset: 0; overflow: hidden;
  border-radius: 7% / 9%;
}
.crt-glass {
  position: absolute; inset: 0; overflow: hidden;
  container-type: inline-size;
  background: radial-gradient(120% 110% at 50% 42%, #14170F 0%, #0A0B08 62%, #060704 100%);
}
.crt-glass::before {                /* scanlines */
  content: ""; position: absolute; inset: 0; pointer-events: none; z-index: 4;
  background: repeating-linear-gradient(to bottom, rgba(0,0,0,.32) 0 1px, transparent 1px 3px);
  mix-blend-mode: multiply;
}
.crt-glass::after {                 /* the slow roll a real tube has */
  content: ""; position: absolute; left: 0; right: 0; height: 24%; z-index: 5;
  pointer-events: none;
  background: linear-gradient(to bottom, transparent, rgba(255,255,255,.055), transparent);
  animation: roll 7.5s linear infinite;
}
@keyframes roll { from { top: -30%; } to { top: 110%; } }
/* the curve of the glass, and the room reflected in it */
.crt-screen::after {
  content: ""; position: absolute; inset: 0; z-index: 6; pointer-events: none;
  border-radius: inherit;
  background:
    linear-gradient(122deg, rgba(255,255,255,.11) 0%, rgba(255,255,255,.035) 24%, transparent 44%),
    radial-gradient(120% 130% at 50% 50%, transparent 58%, rgba(0,0,0,.42) 100%);
}

.crt-term {
  /* One line, centred on the glass. The machine says its own name and then
     hands the screen to the product — nothing else belongs here. */
  position: absolute; inset: 0; z-index: 2;
  display: flex; flex-direction: column;
  justify-content: center; align-items: center; text-align: center;
  padding: 7%;
  font-family: var(--font-mono);
  color: #F7C79A;
  text-shadow: 0 0 10px rgba(242,140,60,.75), 0 0 30px rgba(255,107,61,.45);
  transition: opacity var(--d-3) var(--glide);
}
.crt-term .big {
  font-family: var(--font-display); font-weight: 800;
  font-size: clamp(16px, 11.4cqw, 56px);
  letter-spacing: -.032em; line-height: 1.14;
  color: #FFEBD6; min-height: 1.2em; opacity: 1;
  max-width: 9ch;
}
.crt-term .big i {
  font-style: normal; display: inline-block;
  width: .19em; height: .19em; background: var(--o-500);
  margin: 0 .16em 0 -.02em; vertical-align: baseline;
  text-shadow: none; box-shadow: 0 0 12px rgba(255,107,61,.85);
}
.caret {
  display: inline-block; width: .55em; height: .92em; vertical-align: -.12em;
  background: currentColor; margin-left: .1em;
  animation: blink 1.05s steps(1) infinite;
}
.crt-term .big .caret { width: .38em; height: .8em; background: var(--o-400); }
@keyframes blink { 0%,49% { opacity: 1; } 50%,100% { opacity: 0; } }

.crt-app {
  position: absolute; inset: 0; z-index: 3; overflow: hidden;
  opacity: 0; pointer-events: none;
  transition: opacity var(--d-4) var(--glide);
  background: var(--bone);
}
/* A monitor shows the WHOLE screen, small — not a 290px-wide crop of one. So
   the dashboard is laid out at a real desktop width and then scaled down to
   fit the glass. Container queries inside resolve against the unscaled 880px,
   which is the point: the rail keeps its labels and the bento keeps 4 columns.
   The factor comes from JS because CSS cannot turn a length into a number. */
/* Overscan. No tube ever showed the picture out to its own edge, and without
   it the dashboard's outer column runs under the rounded corner and gets cut.
   The inset is applied in JS, where the glass is measured. */
.crt-vp {
  position: absolute; top: 0; left: 0;
  width: var(--crt-w, 880px);
  height: var(--crt-vh, 720px);
  transform-origin: 0 0;
  transform: scale(var(--crt-scale, .33));
  container-type: inline-size;
  background: var(--bone);
}
.crt-vp .ui { height: 100%; min-height: 0; }
.crt.is-booted .crt-app  { opacity: 1; pointer-events: auto; }
.crt.is-booted .crt-term { opacity: 0; }

@media (max-width: 1000px) { .machine { width: min(100%, 540px); } }

/* the keys under the machine */
.keyrow {
  display: flex; gap: var(--s-3); justify-content: center; align-items: flex-end;
  margin-top: var(--s-6); flex-wrap: wrap;
}
.keyrow .plus { color: var(--ink-3); font-family: var(--font-mono); padding-bottom: 20px; }

/* Temporary: two candidate machines, switchable in place. Delete this block,
   the markup and the JS once one of them is chosen. */
.mswitch {
  display: flex; align-items: center; justify-content: center; gap: 6px;
  margin-top: var(--s-4); font-size: 12px;
}
.mswitch em {
  font-style: normal; font-family: var(--font-mono); font-size: 10.5px;
  letter-spacing: .1em; text-transform: uppercase; color: #201812; opacity: .62;
  margin-right: 4px;
}
.mswitch button {
  font: inherit; font-weight: 600; padding: 6px 13px; border-radius: 999px;
  color: #201812; background: transparent;
  box-shadow: inset 0 0 0 1.5px rgba(32,24,18,.34);
  transition: background var(--d-2) var(--glide), box-shadow var(--d-2) var(--glide);
}
.mswitch button:hover { background: rgba(32,24,18,.08); }
.mswitch button[aria-pressed="true"] {
  background: #201812; color: #FFF7F0; box-shadow: none;
}

/* ------------------------------------------------------- trust band ------ */
.trust { border-block: var(--hair); }
.trust-in { display: flex; align-items: center; gap: clamp(24px, 5vw, 64px); flex-wrap: wrap; padding-block: var(--s-6); }
.trust-lab { font-size: var(--t-xs); color: var(--ink-3); font-family: var(--font-mono); letter-spacing: var(--tr-caps); text-transform: uppercase; flex: none; }
.trust-logos { display: flex; align-items: center; gap: clamp(20px, 3.5vw, 44px); flex-wrap: wrap; }
.trust-logos span {
  font-family: var(--font-display); font-weight: 800; font-size: var(--t-sm);
  letter-spacing: -.01em; color: var(--ink-3); opacity: .85;
}

.statstrip { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1px; background: var(--line); border: var(--hair); border-radius: var(--r-5); overflow: hidden; }
@media (max-width: 760px) { .statstrip { grid-template-columns: repeat(2, 1fr); } }
.stat { background: var(--surface); padding: var(--s-6) var(--s-5); }
.stat b { display: block; font-family: var(--font-display); font-weight: 900; font-size: var(--t-xl); letter-spacing: var(--tr-tight); line-height: 1; }
.stat i { display: block; font-style: normal; font-size: var(--t-xs); color: var(--ink-3); margin-top: var(--s-2); line-height: 1.45; }

/* ---- Round 26, item 2: the strip is four black tiles, not a fused slab ---
   On `#onesystem` the strip inherited `--surface` (#111) inside a 1px
   `--line` rule, which made it the one black panel on the page that is not
   drawn the way every other black tile is. It takes the `.db` / `.offline-
   tile` recipe instead: `#1C1C1E` at `--r-lg` with a single inset white edge
   at 6 percent, 12px apart rather than fused by a 1px seam. The values carry
   the same orange gradient ink as every other number tile on the site, and
   the captions go from `--ink-3` (#6E6E73, 3.0:1 on #1C1C1E and set at 13px)
   to var(--nt-ink-2) at 15px, measured 10.1:1 on that ground. */
.on-dark .statstrip {
  gap: 12px; background: none; border: 0; border-radius: 0; overflow: visible;
}
.on-dark .stat {
  background: #1C1C1E; border-radius: var(--r-lg);
  box-shadow: inset 0 0 0 1px rgba(255,255,255,.06);
  padding: 24px 22px 22px;
}
.on-dark .stat b {
  font-family: var(--font-display); font-weight: 700; font-size: 40px;
  line-height: 1.1; letter-spacing: -0.03em; padding-bottom: .08em;
  background: var(--grad-text); -webkit-background-clip: text; background-clip: text;
  color: transparent; -webkit-text-fill-color: transparent; width: fit-content;
}
.on-dark .stat i {
  font-size: 15px; line-height: 1.42; color: var(--nt-ink-2); margin-top: 8px; max-width: 26ch;
}
@media (max-width: 900px) { .on-dark .stat b { font-size: 34px; } }
@media (max-width: 560px) {
  .on-dark .statstrip { gap: 10px; }
  .on-dark .stat { padding: 20px 18px; }
}

/* ------------------------------------------------- the system bento ------
   Round 11, item A. The radial diagram is gone. Nine equal tiles in the
   Apple M2 Max layout: eight white system tiles on eggshell around one
   orange cloud tile that sits in the middle of the 3 by 3 grid by DOM
   order alone (it is the fifth child). Tablet keeps three columns; the
   phone folds to two with the cloud tile spanning the top. The entrance is
   a 12px rise and a fade, staggered 40ms by the tile's own --i, fired once
   by the "system bento" block in js/site.js.
   ------------------------------------------------------------------------ */
.sysbento {
  position: relative;
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 12px;
}
.sb {
  position: relative; z-index: 1;
  background: var(--n-0); border-radius: var(--r-lg);
  padding: var(--s-5); min-height: 168px;
  display: flex; flex-direction: column; align-items: flex-start; gap: 10px;
  box-shadow: var(--lift-1), inset 0 0 0 1px rgba(0,0,0,.055);
  transition: opacity .5s var(--glide), transform .5s var(--glide);
  transition-delay: calc(var(--i, 0) * 40ms);
}
/* round 30: hidden only once js/site.js has armed the grid's observer */
.sysbento.is-armed:not(.is-in) .sb { opacity: 0; transform: translateY(12px); }
.sysbento.is-in .sb { opacity: 1; transform: none; }
.sb h3 {
  font-family: var(--font-display); font-weight: 700;
  font-size: 21px; line-height: 1.2; letter-spacing: var(--tr-tight);
  margin: auto 0 0;
}
.sb p { font-size: 15px; line-height: 1.4; color: var(--ink-3); margin: 0; }

/* ---- Round 14, item 4: cloud, connected --------------------------------
   Same grid, same nine tiles, same copy. What is new is that the section
   says what it means: the eight systems are wired to the middle one.

   The centre tile drops its orange ground. On black every tile is #1C1C1E,
   which is the brief's own rule, and the orange moves to where it now does
   work: the cloud GLYPH (the one primary icon in this group, .ic-key) with
   a soft halo that breathes behind it. An orange glyph on an orange plate
   would have been invisible.                                            */
.on-dark .sb {
  background: #1C1C1E;
  box-shadow: inset 0 0 0 1px rgba(255,255,255,.06);
}
.sb-core h3 { color: var(--ink); }
.sb-core p { font-weight: 500; }
.sb-core-ic {
  position: relative; width: 34px; height: 34px; flex: none;
  display: grid; place-items: center; color: #FF6B3D;
}
.sb-core-ic .ic { width: 34px; height: 34px; stroke-width: 1.75; stroke-linecap: round; stroke-linejoin: round; }
.sb-core-ic::before {
  content: ""; position: absolute; left: 50%; top: 50%; z-index: -1;
  width: 96px; height: 96px; margin: -48px 0 0 -48px; border-radius: 50%;
  background: radial-gradient(circle at center, rgba(255,107,61,.55) 0%, rgba(255,107,61,0) 68%);
}
@media (prefers-reduced-motion: no-preference) {
  .sb-core-ic::before { animation: sbHalo 4.2s var(--glide) infinite; }
}
@keyframes sbHalo {
  0%, 100% { transform: scale(.86); opacity: .55; }
  50%      { transform: scale(1.12); opacity: 1; }
}

/* the connector layer. One <svg> OVER the tiles: the bento gap is 12px and
   a line that only crossed it would read as a tick, so each wire runs 10px
   into the tile at either end rather than the grid being widened. Geometry comes from the real rects
   after layout, written by js/site.js -> sbNet(). */
.sb-net {
  position: absolute; inset: 0; z-index: 2;
  width: 100%; height: 100%; overflow: visible; pointer-events: none;
}
.sb-net .sbn-line {
  stroke: rgba(255,107,61,.42); stroke-width: 1.6; stroke-linecap: round; fill: none;
  stroke-dasharray: var(--len) var(--len);
  stroke-dashoffset: var(--len);
}
.sysbento.is-in .sb-net .sbn-line {
  transition: stroke-dashoffset .7s var(--glide);
  transition-delay: calc(var(--i, 0) * 70ms + 260ms);
  stroke-dashoffset: 0;
}
/* the pulse: a 14px dash chasing the same path on a 3s loop, one offset per
   line, so the eight arrivals are staggered rather than simultaneous. */
.sb-net .sbn-pulse {
  stroke: #FF6B3D; stroke-width: 3.4; stroke-linecap: round; fill: none;
  stroke-dasharray: 1 calc(var(--len) + 1);
  stroke-dashoffset: var(--len);
  opacity: 0;
}
@media (prefers-reduced-motion: no-preference) {
  .sysbento.is-in .sb-net .sbn-pulse {
    animation: sbPulse 3s linear infinite;
    animation-delay: calc(var(--i, 0) * 260ms + 900ms);
  }
}
@keyframes sbPulse {
  0%        { stroke-dashoffset: var(--len); opacity: 0; }
  6%        { opacity: 1; }
  40%       { stroke-dashoffset: 0; opacity: 1; }
  52%, 100% { stroke-dashoffset: 0; opacity: 0; }
}

/* the tick chip on each satellite, lit as its own pulse lands. */
.sb-sync {
  display: inline-flex; align-items: center; gap: 5px;
  margin-top: 2px; padding: 4px 8px 4px 6px; border-radius: 999px;
  font-family: var(--font-mono); font-size: 10px; letter-spacing: .06em;
  text-transform: uppercase; line-height: 1;
  background: rgba(255,255,255,.05); color: var(--ink-3);
  transition: background .35s var(--glide), color .35s var(--glide);
}
.sb-sync svg { width: 11px; height: 11px; }
@media (prefers-reduced-motion: no-preference) {
  .sysbento.is-in .sb .sb-sync {
    animation: sbSync 3s var(--glide) infinite;
    animation-delay: calc(var(--i, 0) * 260ms + 1980ms);
  }
}
@keyframes sbSync {
  0%, 96%, 100% { background: rgba(255,255,255,.05); color: var(--ink-3); }
  4%, 40%       { background: rgba(255,107,61,.16); color: #FF6B3D; }
}
.sb h3 { margin-top: auto; }
.sb p { margin-bottom: 0; }

@media (max-width: 1100px) { .sb { min-height: 156px; padding: var(--s-4); } }
@media (max-width: 900px)  { .sb h3 { font-size: 19px; } .sb p { font-size: 14px; } }
@media (max-width: 700px) {
  .sysbento { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
  .sb-core { grid-column: 1 / -1; order: -1; }
  .sb { min-height: 146px; }
  .sb-sync { font-size: 9px; padding: 3px 7px 3px 5px; }
}
@media (prefers-reduced-motion: reduce) {
  .sb { opacity: 1; transform: none; transition: none; }
  .sb-net .sbn-line { stroke-dashoffset: 0; }
  .sb-net .sbn-pulse { display: none; }
}

/* --------------------------------------------------- product showcase ---- */
.showcase { display: grid; grid-template-columns: 300px minmax(0,1fr); gap: clamp(28px, 4vw, 64px); align-items: start; }
@media (max-width: 1000px) { .showcase { grid-template-columns: 1fr; } }

.sc-tabs { position: sticky; top: 100px; display: grid; gap: 2px; }
/* 2026-09: a single-row horizontal chip strip, not a wrap -- this is a
   control (which screen to look at), not page content, so it is exempt
   from the "nothing rails except the three galleries" rule below (see the
   ".moment stacks, never rail" note further down for that rule's actual
   scope). Five short labels wrapped into an uneven two-column block on
   phones; nowrap + overflow-x keeps it one row, edge-faded like the
   highlight strip's own .hl-tabs. */
@media (max-width: 1000px) {
  .sc-tabs {
    position: static; display: flex; flex-wrap: nowrap; gap: var(--s-3);
    overflow-x: auto; scrollbar-width: none; -ms-overflow-style: none;
    -webkit-overflow-scrolling: touch; overscroll-behavior-x: contain;
    padding: 2px 2px 6px;
    -webkit-mask-image: linear-gradient(to right, transparent, #000 20px, #000 calc(100% - 20px), transparent);
    mask-image: linear-gradient(to right, transparent, #000 20px, #000 calc(100% - 20px), transparent);
  }
  .sc-tabs::-webkit-scrollbar { display: none; }
}
.sc-tab {
  display: block; text-align: left; width: 100%;
  padding: var(--s-4); border-radius: var(--r-3);
  border-left: 3px solid transparent;
  transition: background var(--d-2) var(--glide), border-color var(--d-2) var(--glide);
}
@media (max-width: 1000px) {
  .sc-tab {
    flex: none; width: auto; white-space: nowrap; border-left: 0;
    display: flex; align-items: center; height: var(--s-7); padding: 0 var(--s-4);
    border-radius: 999px; background: transparent;
    border: 1px solid color-mix(in srgb, var(--ink) 18%, transparent);
  }
}
.sc-tab b { display: block; font-family: var(--font-display); font-weight: 700; font-size: var(--t-base); }
@media (max-width: 1000px) { .sc-tab b { font-size: var(--t-sm); } }
.sc-tab span { display: block; font-size: var(--t-xs); color: var(--ink-3); margin-top: 3px; line-height: 1.45; }
@media (max-width: 1000px) { .sc-tab span { display: none; } }
.sc-tab[aria-selected="true"] { background: var(--surface); border-left-color: var(--o-500); }
/* the active chip reads as a dark, filled pill against its unfilled
   (outline-only) siblings -- not the shared border-left cue, which reads as
   a stray line once tabs are pills. var(--surface) is this on-dark
   section's raised panel tone, a shade lighter than the black page ground,
   so the filled chip actually pops instead of matching the page behind it
   the way var(--ground) would. */
@media (max-width: 1000px) {
  .sc-tab[aria-selected="true"] {
    background: var(--surface); border-color: var(--surface);
  }
  .sc-tab[aria-selected="true"] b { color: var(--ink); }
}
.sc-tab:hover { background: var(--surface); }

.sc-stage {
  position: relative;
  border-radius: var(--r-6);
  background: var(--n-900);
  padding: 12px;
  box-shadow: var(--lift-4);
}
.sc-bar { display: flex; align-items: center; gap: 7px; padding: 4px 6px 10px; }
.sc-bar i { width: 9px; height: 9px; border-radius: 50%; background: #3A3A3A; }
.sc-bar .u { margin-left: auto; font-family: var(--font-mono); font-size: 10px; color: #7A7773; letter-spacing: .04em; }
.sc-panel { display: none; border-radius: var(--r-4); overflow: hidden; background: var(--bone); container-type: inline-size; }
.sc-panel.is-on { display: block; animation: fadeUp var(--d-3) var(--glide) both; }
/* the tab switch cross-fades with a 2% scale rather than a plain fade --
   from { scale(.98) } reads as the incoming panel settling into place,
   the same idiom the dashboard frame's own entrance (motion() part d,
   js/apple.js) uses at a bigger scale delta. */
@keyframes fadeUp { from { opacity: 0; transform: translateY(10px) scale(.98); } to { opacity: 1; transform: none; } }
.sc-cap { margin-top: var(--s-4); display: flex; gap: var(--s-4); flex-wrap: wrap; }
.sc-cap p { font-size: var(--t-sm); color: var(--ink-2); max-width: 60ch; }

/* --------------------------------------------------------- timeline ------ */
.tl { position: relative; }
.tl-track {
  display: grid; grid-auto-flow: column; grid-auto-columns: minmax(230px, 1fr);
  gap: var(--s-5); overflow-x: auto; padding-bottom: var(--s-5);
  scroll-snap-type: x proximity;
}
.tl-track > * { scroll-snap-align: start; }
.tl-step { position: relative; padding-top: var(--s-7); }
.tl-step::before {
  content: ""; position: absolute; top: 13px; left: 0; right: 0; height: 2px;
  background: var(--line);
}
.tl-step::after {
  content: ""; position: absolute; top: 8px; left: 0; width: 12px; height: 12px;
  background: var(--o-500);
}
.tl-step:last-child::before { background: linear-gradient(to right, var(--line) 60%, transparent); }
.tl-time { font-family: var(--font-mono); font-size: var(--t-xs); font-weight: 700; color: var(--accent-text); letter-spacing: .04em; }
.tl-step h4 { margin-top: var(--s-2); font-size: var(--t-md); }
.tl-step p { margin-top: var(--s-2); font-size: var(--t-sm); color: var(--ink-2); }
.tl-step ul { margin-top: var(--s-3); padding: 0; list-style: none; display: grid; gap: 5px; }
.tl-step li { font-size: var(--t-xs); color: var(--ink-3); display: flex; gap: 7px; }
.tl-step li::before { content: "\203A"; color: var(--o-500); font-weight: 700; }

/* ---------------------------------------------------------- receipt ------ */
.receipt {
  --tear: 9px;
  position: relative;
  background: var(--n-0);
  padding: var(--s-6) var(--s-5) calc(var(--s-6) + var(--tear));
  font-family: var(--font-mono); font-size: var(--t-xs); line-height: 1.75;
  color: var(--n-800);
  box-shadow: var(--lift-3);
  max-width: 360px;
  --m: linear-gradient(#000 0 0) 0 var(--tear) / 100% calc(100% - var(--tear)*2) no-repeat,
       radial-gradient(circle var(--tear) at 50% 0, transparent 98%, #000) 50% 0 / 26px var(--tear) repeat-x,
       radial-gradient(circle var(--tear) at 50% 100%, transparent 98%, #000) 50% 100% / 26px var(--tear) repeat-x;
  -webkit-mask: var(--m); mask: var(--m);
}
.receipt hr { border: 0; border-top: 1px dashed var(--n-300); margin: var(--s-3) 0; }
.receipt .r { display: flex; justify-content: space-between; gap: var(--s-4); }
.receipt .r b { font-weight: 700; }
.receipt .hd { text-align: center; }
.receipt .hd b { font-family: var(--font-display); font-size: var(--t-md); letter-spacing: -.02em; display: block; }
.receipt .tot { font-size: var(--t-sm); font-weight: 700; }
.receipt .note { color: var(--n-500); }

.split { display: grid; grid-template-columns: minmax(0,1fr) auto; gap: clamp(32px, 6vw, 80px); align-items: center; }
/* an even 50/50 split, for the one .split instance (the outlet tree) whose
   second column is not intrinsically-sized content -- a class, not an inline
   style, so the mobile override below still wins at narrow widths instead of
   a same-element style attribute beating it outright. */
.split-even { grid-template-columns: minmax(0,1fr) minmax(0,1fr); }
@media (max-width: 900px) {
  .split, .split-even { grid-template-columns: 1fr; }
  .receipt { max-width: none; }
}

/* facts list — a definition list, not cards */
.facts { display: grid; gap: 0; margin-top: var(--s-6); }
.facts > div { display: grid; grid-template-columns: 190px minmax(0,1fr); gap: var(--s-5);
  padding: var(--s-4) 0; border-top: var(--hair); }
@media (max-width: 640px) { .facts > div { grid-template-columns: 1fr; gap: var(--s-1); } }
.facts dt { font-family: var(--font-mono); font-size: var(--t-xs); font-weight: 600;
  letter-spacing: .04em; text-transform: uppercase; color: var(--ink-3); padding-top: 3px; }
.facts dd { margin: 0; font-size: var(--t-sm); color: var(--ink-2); }
.facts dd b { color: var(--ink); font-weight: 650; }

/* --------------------------------------------------------- offline ------- */
.offline-band { position: relative; overflow: hidden; }
.offline-in { display: grid; grid-template-columns: minmax(0,1fr) minmax(0,420px); gap: clamp(32px, 6vw, 80px); align-items: center; }
@media (max-width: 900px) { .offline-in { grid-template-columns: 1fr; } }
.wifi {
  display: grid; gap: var(--s-3); padding: var(--s-6);
  border: 1px solid var(--line); border-radius: var(--r-5); background: var(--surface);
}
.wifi-row { display: flex; align-items: center; justify-content: space-between; gap: var(--s-4); font-size: var(--t-sm); }
.wifi-row .dot { width: 9px; height: 9px; border-radius: 50%; background: var(--ok); flex: none; }
.wifi.is-down .wifi-row .dot { background: var(--warn); }
.wifi-q {
  font-family: var(--font-mono); font-size: var(--t-xs); color: var(--ink-3);
  padding: var(--s-3); border-radius: var(--r-2); background: var(--surface-2);
  display: grid; gap: 4px;
}
.wifi-q b { color: var(--ink); }

/* -------------------------------------------------------- outlet tree ---- */
.tree { display: grid; gap: var(--s-3); }
.tree-hq {
  display: flex; align-items: center; gap: var(--s-4);
  padding: var(--s-5); border-radius: var(--r-4);
  background: var(--n-900); color: var(--bone);
}
.tree-hq b { font-family: var(--font-display); font-weight: 800; }
.tree-hq small { color: #B5B2AE; display: block; font-size: var(--t-xs); }
.tree-kids { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: var(--s-3); padding-left: var(--s-6);
  position: relative; }
.tree-kids::before {
  content: ""; position: absolute; left: 24px; top: calc(-1 * var(--s-3)); height: calc(var(--s-3) + 18px);
  width: 2px; background: var(--line);
}
.tree-out {
  position: relative;
  padding: var(--s-4); border: var(--hair); border-radius: var(--r-3); background: var(--surface);
}
.tree-out b { display: block; font-size: var(--t-sm); font-weight: 650; }
.tree-out small { display: block; font-size: var(--t-xs); color: var(--ink-3); margin-top: 2px; }
.tree-out .bar { display: block; height: 5px; border-radius: 2px; background: var(--surface-sunk); margin-top: var(--s-3); overflow: hidden; }
.tree-out .bar i { display: block; height: 100%; background: var(--o-500); }

/* --------------------------------------------------- module index -------- */
.index-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(270px, 1fr)); gap: 0 clamp(24px, 4vw, 56px); }
.index-item { padding: var(--s-4) 0; border-top: var(--hair); display: grid; grid-template-columns: 26px 1fr; gap: var(--s-3); align-items: start; }
.index-item .n { font-family: var(--font-mono); font-size: var(--t-xs); color: var(--o-600); font-weight: 600; padding-top: 4px; }
.index-item b { font-size: var(--t-base); font-weight: 650; font-family: var(--font-display); }
.index-item p { font-size: var(--t-xs); color: var(--ink-3); margin-top: 3px; line-height: 1.5; }

/* ---------------------------------------------------------- pricing ------ */
.plans { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: var(--s-5); align-items: start; }
.plan {
  position: relative; display: flex; flex-direction: column;
  background: var(--surface); border: var(--hair); border-radius: var(--r-5);
  padding: var(--s-6);
}
.plan.is-featured { border-color: var(--o-500); box-shadow: 0 0 0 1px var(--o-500), var(--lift-3); }
.plan-flag {
  position: absolute; top: -11px; left: var(--s-6);
  font-family: var(--font-mono); font-size: 10px; font-weight: 700; letter-spacing: .09em; text-transform: uppercase;
  background: var(--o-500); color: var(--n-900); padding: 4px 9px; border-radius: var(--r-1);
}
.plan h3 { font-size: var(--t-md); }
.plan .who { font-size: var(--t-xs); color: var(--ink-3); margin-top: 5px; min-height: 2.6em; line-height: 1.45; }
.plan .price { margin-top: var(--s-5); display: flex; align-items: baseline; gap: 6px; flex-wrap: wrap; }
.plan .price b { font-family: var(--font-display); font-weight: 900; font-size: var(--t-xl); letter-spacing: var(--tr-tight); line-height: 1; }
.plan .price em { font-style: normal; font-size: var(--t-xs); color: var(--ink-3); white-space: nowrap; }
.plan .cur { font-family: var(--font-mono); font-size: var(--t-sm); color: var(--ink-3); font-weight: 600; }
.plan .per { font-size: var(--t-xs); color: var(--ink-3); margin-top: 6px; }
.plan .btn { margin-top: var(--s-5); width: 100%; }
.plan ul { list-style: none; padding: 0; margin: var(--s-5) 0 0; display: grid; gap: 9px; border-top: var(--hair); padding-top: var(--s-5); }
.plan li { display: grid; grid-template-columns: 16px 1fr; gap: 9px; font-size: var(--t-xs); line-height: 1.5; color: var(--ink-2); }
.plan li svg { margin-top: 3px; color: var(--o-600); }
.plan li.off { color: var(--n-500); } /* Round 60 cleanup: 2.55 to 5.17:1 on white */
.plan li.off svg { color: var(--n-300); }

.ptable { width: 100%; border-collapse: collapse; font-size: var(--t-sm); }
.ptable th, .ptable td { padding: var(--s-3) var(--s-4); text-align: left; border-bottom: var(--hair); vertical-align: top; }
.ptable thead th { font-family: var(--font-mono); font-size: var(--t-xs); text-transform: uppercase; letter-spacing: var(--tr-caps); color: var(--ink-3); font-weight: 600; }
.ptable tbody th { font-weight: 600; color: var(--ink); }
.ptable td.mid, .ptable th.mid { text-align: center; }
.ptable tr.is-us { background: var(--o-50); }
.ptable tr.is-us th, .ptable tr.is-us td { color: var(--o-900); }

/* --------------------------------------------------------------- faq ----- */
.faq { display: grid; gap: 0; }
.faq details { border-top: var(--hair); }
.faq details:last-of-type { border-bottom: var(--hair); }
.faq summary {
  display: flex; align-items: center; justify-content: space-between; gap: var(--s-4);
  padding: var(--s-5) 0; cursor: pointer; list-style: none;
  font-family: var(--font-display); font-weight: 700; font-size: var(--t-base);
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary .pm { flex: none; width: 22px; height: 22px; position: relative; }
.faq summary .pm::before, .faq summary .pm::after {
  content: ""; position: absolute; inset: 50% 0 auto; height: 2px; background: var(--ink-3);
  transition: transform var(--d-2) var(--snap);
}
.faq summary .pm::after { transform: rotate(90deg); }
/* Round 54, item 9c: the plus turns into a cross (the whole .pm rotates 45deg,
   css/apple.css); the second bar stays where it is */
.faq details[open] summary .pm::after { transform: rotate(90deg); }
.faq details > div { padding-bottom: var(--s-5); font-size: var(--t-sm); color: var(--ink-2); max-width: 72ch; }

/* -------------------------------------------------------------- cta ------ */
.cta-band { position: relative; overflow: hidden; }
.cta-term {
  font-family: var(--font-mono); font-size: var(--t-sm); color: var(--o-400);
  display: inline-flex; align-items: center; gap: 8px;
}
.cta-term::before { content: "$"; color: var(--ink-3); }
/* the pricing section was removed from the home page (point 5) -- this one
   line, inside the CTA band, is the only pricing mention left here. */
.cta-price-line { margin-top: var(--s-5); font-size: var(--t-sm); color: var(--ink-2); }
.cta-price-line a { color: var(--accent-text); font-weight: 600; }
.cta-price-line a:hover { text-decoration: underline; }

/* ------------------------------------------------------------- footer ---- */
.foot { border-top: var(--hair); padding-block: var(--s-8) var(--s-5); }
.foot-in { display: grid; grid-template-columns: minmax(0,1.4fr) repeat(3, minmax(0,1fr)); gap: var(--s-7); }
@media (max-width: 860px) { .foot-in { grid-template-columns: 1fr 1fr; } }
.foot h5 { font-family: var(--font-mono); font-size: var(--t-xs); font-weight: 600; letter-spacing: var(--tr-caps); text-transform: uppercase; color: var(--ink-3); margin-bottom: var(--s-4); }
.foot ul { list-style: none; padding: 0; display: grid; gap: 10px; }
.foot a { font-size: var(--t-sm); color: var(--ink-2); }
.foot a:hover { color: var(--ink); }
.foot-base { margin-top: var(--s-8); padding-top: var(--s-5); border-top: var(--hair);
  display: flex; flex-wrap: wrap; gap: var(--s-4); justify-content: space-between; align-items: center; }
.foot-base p { font-size: var(--t-xs); color: var(--ink-3); }
/* the two CC-BY 3D-model credits (js/device3d.js's iMac/MacBook GLBs) --
   a small mono line under .foot-base per the licence's own "wherever you
   share it" requirement, kept visually last/quietest since it credits
   the raw asset rather than the product itself. */
.foot-3d-credit {
  margin-top: var(--s-3); font-family: var(--font-mono); font-size: 11px;
  line-height: 1.6; color: var(--o-400, var(--ink-3));
}
.foot-3d-credit a { color: inherit; text-decoration: underline; text-underline-offset: 2px; }
.foot-3d-credit a:hover { color: var(--ink); }

/* -------------------------------------------- marquee (aggregator strip) -- */
.marq { overflow: hidden; -webkit-mask: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
        mask: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent); }
.marq-in { display: flex; gap: var(--s-8); width: max-content; animation: slide 38s linear infinite; }
.marq:hover .marq-in { animation-play-state: paused; }
@keyframes slide { to { transform: translateX(-50%); } }
.marq span { font-family: var(--font-display); font-weight: 800; font-size: var(--t-base); color: var(--ink-3); white-space: nowrap; }

/* =============================================================== product == */
.pagehead { padding-block: clamp(36px, 4.6vw, 66px) clamp(24px, 3vw, 40px); }

/* Round 52: the 404's recovery links. Everywhere else on the site a link inside
   body copy takes its colour from the run it sits in, which is right for prose
   and wrong on a dead end, where the links are the only thing the page is for.
   Scoped to the 404 so nothing else inherits it. */
.e404-links dd a {
  color: var(--ink); text-decoration: underline;
  text-underline-offset: 3px; text-decoration-thickness: 1px;
}
.e404-links dd a:hover, .e404-links dd a:focus-visible { text-decoration-thickness: 2px; }

/* Round 52: the comparison pages' disclaimer. Set apart from the argument so a
   reader can see it is the legal note and not more marketing, but not hidden. */
.cmp-note {
  margin-top: var(--s-7); padding: var(--s-5) var(--s-5) calc(var(--s-5) - 6px);
  border: 1px solid var(--ui-line); border-radius: var(--r-3, 12px);
  background: var(--ui-sunk);
}
.cmp-note .fine { margin: 0 0 10px; color: var(--ink-2); } /* 4.48 to above 7:1 on the note's wash */
.cmp-note .fine:last-child { margin-bottom: 0; }
.cmp-note a { text-decoration: underline; text-underline-offset: 2px; }
.pagehead h1 { margin-top: var(--s-4); max-width: 20ch; }
.pagehead .lede { margin-top: var(--s-5); }

/* the sticky index of modules */
.modnav {
  position: sticky; top: 68px; z-index: 40;
  background: color-mix(in srgb, var(--ground) 88%, transparent);
  backdrop-filter: saturate(1.6) blur(16px);
  -webkit-backdrop-filter: saturate(1.6) blur(16px);
  /* Round 22, Apple Design audit section 12: a hard 1px rule on both edges
     was exactly the anti-pattern the skill calls out by name -- "instead of
     a 1px border under a sticky header, fade a small blur/gradient mask
     where floating UI meets content". The blur above already does the
     separating; what is left is a soft edge instead of a ruled line, and a
     bright top catch-light to match every other glass surface on the page
     (nav-in, local-nav-in) rather than a flat grey rule. */
  border-block: 0;
  box-shadow: inset 0 1px 0 color-mix(in srgb, var(--n-0) 55%, transparent),
              0 14px 26px -20px rgba(0,0,0,.22);
}
@media (prefers-reduced-transparency: reduce), (prefers-contrast: more) {
  .modnav { background: var(--ground); backdrop-filter: none; -webkit-backdrop-filter: none; }
}
@media (prefers-contrast: more) {
  .modnav { border-block: 2px solid var(--ink); box-shadow: none; }
}
/* It used to scroll, which quietly hid the last four modules. Eighteen links
   fit on two rows and nothing is lost. */
.modnav-in { display: flex; flex-wrap: wrap; gap: var(--s-1); padding-block: 10px; }
.modnav-in::-webkit-scrollbar { display: none; }
.modnav a {
  flex: none; font-family: var(--font-mono); font-size: var(--t-xs); font-weight: 500;
  letter-spacing: .02em; color: var(--ink-3); padding: 6px 11px; border-radius: var(--r-2);
  white-space: nowrap;
}
.modnav a:hover { color: var(--ink); background: var(--surface); }
.modnav a.is-here { color: var(--n-900); background: var(--o-200); }

/* one module */
.mod { padding-block: clamp(38px, 4.4vw, 64px); border-top: var(--hair); scroll-margin-top: 130px; }
.mod:first-of-type { border-top: 0; }
.mod-in { display: grid; grid-template-columns: minmax(0, 380px) minmax(0, 1fr); gap: clamp(28px, 5vw, 72px); align-items: start; }
@media (max-width: 900px) { .mod-in { grid-template-columns: 1fr; } }
.mod-side { position: sticky; top: 132px; }
@media (max-width: 900px) { .mod-side { position: static; } }
.mod-side .num {
  font-family: var(--font-mono); font-size: var(--t-xs); font-weight: 700;
  color: var(--o-600); letter-spacing: .08em;
}
.mod-side h2 { font-size: var(--t-xl); margin-top: var(--s-2); }
.mod-side p { margin-top: var(--s-4); font-size: var(--t-sm); color: var(--ink-2); }
.mod-side .tag { margin-top: var(--s-4); }

.mod-body { display: grid; gap: var(--s-5); }
.feat { display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); gap: 0 var(--s-6); }
.feat > div { padding: var(--s-4) 0; border-top: var(--hair); }
.feat b { display: block; font-size: var(--t-sm); font-weight: 650; font-family: var(--font-display); }
.feat p { margin-top: 4px; font-size: var(--t-xs); color: var(--ink-3); line-height: 1.55; }

.mod-shot { border-radius: var(--r-5); overflow: hidden; border: var(--hair); background: var(--n-900); padding: 10px; }
.mod-shot .inner { border-radius: var(--r-3); overflow: hidden; container-type: inline-size; background: var(--bone); }

.callout {
  border-left: 3px solid var(--o-500);
  padding: var(--s-2) 0 var(--s-2) var(--s-5);
  font-size: var(--t-sm); color: var(--ink-2);
}
.callout b { color: var(--ink); }

/* the report list reads as a printed contents page */
.replist { columns: 2; column-gap: var(--s-7); font-size: var(--t-sm); }
@media (max-width: 620px) { .replist { columns: 1; } }
.replist div {
  break-inside: avoid; display: flex; align-items: baseline; gap: var(--s-3);
  padding: 7px 0; border-bottom: 1px dotted var(--n-300);
}
.replist span:first-child { font-family: var(--font-mono); font-size: var(--t-xs); color: var(--o-600); }
.replist span:last-child { margin-left: auto; font-family: var(--font-mono); font-size: var(--t-xs); color: var(--ink-3); }

/* the permission matrix */
.matrix { width: 100%; border-collapse: collapse; font-size: var(--t-xs); }
.matrix th, .matrix td { padding: 8px 10px; border-bottom: var(--hair); text-align: center; }
.matrix thead th { font-family: var(--font-mono); text-transform: uppercase; letter-spacing: .06em; color: var(--ink-3); font-weight: 600; }
.matrix tbody th { text-align: left; font-weight: 600; color: var(--ink); white-space: nowrap; }
.matrix .y { color: var(--ok-ink); font-weight: 700; }
.matrix .n { color: var(--n-300); }

/* =============================================================== pricing == */
.billing { display: inline-flex; background: var(--surface-2); padding: 3px; border-radius: var(--r-3); border: var(--hair); }
.billing button {
  font-size: var(--t-xs); font-weight: 600; padding: 8px 16px; border-radius: var(--r-2);
  color: var(--ink-3); transition: background var(--d-1) var(--snap), color var(--d-1) var(--snap);
}
.billing button.on { background: var(--surface); color: var(--ink); box-shadow: var(--lift-1); }
.billing .save { color: var(--o-700); font-weight: 700; }

.compare { width: 100%; border-collapse: collapse; font-size: var(--t-sm); min-width: 720px; }
.compare th, .compare td { padding: 11px var(--s-4); border-bottom: var(--hair); }
.compare thead th { position: sticky; top: 68px; background: var(--surface); z-index: 2;
  font-family: var(--font-display); font-weight: 800; font-size: var(--t-base); text-align: center; }
.compare thead th:first-child { text-align: left; font-family: var(--font-mono); font-size: var(--t-xs);
  font-weight: 600; text-transform: uppercase; letter-spacing: var(--tr-caps); color: var(--ink-3); }
.compare tbody th { text-align: left; font-weight: 500; color: var(--ink-2); }
.compare td { text-align: center; }
.compare .grp th {
  font-family: var(--font-mono); font-size: var(--t-xs); font-weight: 700;
  text-transform: uppercase; letter-spacing: var(--tr-caps); color: var(--o-700);
  background: var(--o-50); border-bottom: 0; padding-top: var(--s-5);
}
.compare .yes { color: var(--ok-ink); font-weight: 700; }
.compare .no  { color: var(--n-300); }
.compare .hi  { background: color-mix(in srgb, var(--o-50) 60%, transparent); }

.band { display: grid; gap: var(--s-3); }
.band-row {
  display: grid; grid-template-columns: 190px minmax(0,1fr) 130px; gap: var(--s-4);
  align-items: center; font-size: var(--t-sm);
}
@media (max-width: 700px) { .band-row { grid-template-columns: 1fr; gap: var(--s-1); } }
.band-row b { font-weight: 600; }
.band-row .track { height: 30px; background: var(--surface-2); border-radius: var(--r-1); position: relative; overflow: hidden; }
.band-row .track i {
  position: absolute; top: 0; bottom: 0; background: var(--n-300); border-radius: var(--r-1);
}
.band-row.is-us .track i { background: var(--o-500); }
.band-row .amt { font-family: var(--font-mono); font-size: var(--t-xs); color: var(--ink-2); text-align: right; }
.band-scale { display: flex; justify-content: space-between; font-family: var(--font-mono);
  font-size: 10px; color: var(--ink-3); padding-left: 206px; }
@media (max-width: 700px) { .band-scale { padding-left: 0; } }

.tco { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s-5); }
@media (max-width: 760px) { .tco { grid-template-columns: 1fr; } }
.tco-col { border: var(--hair); border-radius: var(--r-5); padding: var(--s-6); background: var(--surface); }
.tco-col.is-us { border-color: var(--o-500); box-shadow: 0 0 0 1px var(--o-500); }
.tco-col h4 { font-size: var(--t-base); }
.tco-col dl { margin-top: var(--s-5); display: grid; gap: 0; }
.tco-col dl > div { display: flex; justify-content: space-between; gap: var(--s-4);
  padding: 9px 0; border-top: var(--hair); font-size: var(--t-sm); }
.tco-col dt { color: var(--ink-2); }
.tco-col dd { margin: 0; font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.tco-col .total { border-top: 2px solid var(--ink); margin-top: var(--s-2); padding-top: var(--s-4);
  display: flex; justify-content: space-between; align-items: baseline; }
.tco-col .total b { font-family: var(--font-display); font-weight: 900; font-size: var(--t-lg); letter-spacing: var(--tr-tight); }

/* ========================================================= design system == */
.ds-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: var(--s-3); }
.sw { border-radius: var(--r-3); overflow: hidden; border: var(--hair); background: var(--surface); }
.sw .sw-chip { display: block; height: 84px; }
.sw .meta { padding: 10px 11px; font-family: var(--font-mono); font-size: 10.5px; line-height: 1.6; }
.sw .meta b { display: block; font-size: 11px; color: var(--ink); }
.sw .meta span { color: var(--ink-3); }
.sw .meta .ratio { display: flex; justify-content: space-between; margin-top: 4px; }
.sw .meta .pass { color: var(--ok-ink); font-weight: 700; }
.sw .meta .fail { color: var(--bad-ink); font-weight: 700; }

.ramp { display: flex; border-radius: var(--r-3); overflow: hidden; border: var(--hair); }
.ramp div { flex: 1; height: 68px; display: grid; align-content: end; justify-items: center;
  font-family: var(--font-mono); font-size: 9px; padding-bottom: 6px; }

.spec {
  display: grid; grid-template-columns: 210px minmax(0,1fr); gap: var(--s-5);
  padding: var(--s-5) 0; border-top: var(--hair); align-items: baseline;
}
@media (max-width: 700px) { .spec { grid-template-columns: 1fr; gap: var(--s-2); } }
.spec > .lab { font-family: var(--font-mono); font-size: var(--t-xs); color: var(--ink-3);
  text-transform: uppercase; letter-spacing: var(--tr-caps); }
.spec code { font-family: var(--font-mono); font-size: var(--t-xs); background: var(--surface-2);
  padding: 2px 6px; border-radius: var(--r-1); color: var(--o-800); }

.demo {
  border: var(--hair); border-radius: var(--r-5); background: var(--surface);
  padding: var(--s-6); display: flex; flex-wrap: wrap; gap: var(--s-4); align-items: center;
}
.demo.dark { background: var(--n-900); border-color: #313131; }

.rule-list { display: grid; gap: 0; }
.rule-list > div { display: grid; grid-template-columns: 26px minmax(0,1fr); gap: var(--s-3);
  padding: var(--s-4) 0; border-top: var(--hair); }
.rule-list .mk { font-family: var(--font-mono); font-weight: 700; font-size: var(--t-sm); }
.rule-list .do   { color: var(--ok-ink); }
.rule-list .dont { color: var(--bad-ink); }
.rule-list p { font-size: var(--t-sm); color: var(--ink-2); }
.rule-list b { color: var(--ink); }

.logo-cell {
  display: grid; place-items: center; min-height: 150px; padding: var(--s-6);
  border: var(--hair); border-radius: var(--r-4); background: var(--surface);
}
.logo-cell.ink  { background: var(--n-900); border-color: #313131; }
.logo-cell.orng { background: var(--o-500); border-color: var(--o-600); }
.logo-cell img { max-height: 54px; width: auto; }
.logo-cell.sq img { max-height: 88px; }

.typespec { border-top: var(--hair); padding: var(--s-5) 0; display: grid; gap: var(--s-2); }
.typespec .meta { font-family: var(--font-mono); font-size: var(--t-xs); color: var(--ink-3); }

/* ============================================ built for how you serve ===== */
.serve-grid { display: grid; grid-template-columns: repeat(4, minmax(0,1fr)); gap: var(--s-3); }
@media (max-width: 1100px) { .serve-grid { grid-template-columns: repeat(3, minmax(0,1fr)); } }
@media (max-width: 820px)  { .serve-grid { grid-template-columns: repeat(2, minmax(0,1fr)); } }
@media (max-width: 460px)  { .serve-grid { grid-template-columns: 1fr; } }
.serve {
  display: grid; gap: var(--s-3); align-content: start;
  padding: var(--s-5); border-radius: var(--r-4);
  background: var(--surface); border: var(--hair);
  transition: border-color var(--d-2) var(--glide), transform var(--d-2) var(--glide),
              box-shadow var(--d-2) var(--glide);
}
.serve:hover { border-color: var(--o-300); transform: translateY(-2px); box-shadow: var(--lift-2); }
.serve .ic {
  width: 42px; height: 42px; display: grid; place-items: center;
  border-radius: var(--r-3); background: var(--o-50); color: var(--o-800);
}
.serve b { font-family: var(--font-display); font-size: var(--t-base); font-weight: 700; }
.serve p { font-size: var(--t-xs); color: var(--ink-3); line-height: 1.5; }
.serve .kv { font-family: var(--font-mono); font-size: 10px; letter-spacing: .06em;
  text-transform: uppercase; color: var(--o-800); }

/* mid-shift's own rail (.ms-track/.ms-card/.ms-pager) lives in css/apple.css,
   next to the "Get the highlights" rail it shares its vocabulary with. */

/* ======================================================== what's new ====== */
.news { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: var(--s-4); }
.news-card {
  position: relative; display: grid; gap: var(--s-3); align-content: start;
  padding: var(--s-6); border-radius: var(--r-5);
  background: var(--surface); border: var(--hair);
  transition: transform var(--d-2) var(--glide), box-shadow var(--d-2) var(--glide);
}
.news-card:hover { transform: translateY(-3px); box-shadow: var(--lift-3); }
.news-card .flag {
  align-self: start; justify-self: start;
  font-family: var(--font-mono); font-size: 10px; font-weight: 700;
  letter-spacing: .1em; text-transform: uppercase;
  padding: 4px 8px; border-radius: var(--r-1);
  background: var(--o-500); color: var(--n-900);
}
.news-card .flag.soon { background: var(--n-200); color: var(--n-700); }
.news-card b { font-family: var(--font-display); font-size: var(--t-md); font-weight: 700; }
.news-card p { font-size: var(--t-sm); color: var(--ink-2); }

/* ================================================== bento feature grid ==== */
.bento-wall {
  display: grid; gap: var(--s-3); margin-top: var(--bento-top);
  grid-template-columns: repeat(4, minmax(0,1fr));
  grid-auto-rows: minmax(150px, auto);
}
@media (max-width: 1000px) { .bento-wall { grid-template-columns: repeat(2, minmax(0,1fr)); } }
@media (max-width: 700px) { .bento-wall { gap: 12px; } }
/* point 3: single column of 2x1 tiles on phones -- the earlier 2026-09
   decision above (kept for the tablet range, 701-1000px) measured taller
   stacking as the downside of one column; with four tiles now full-bleed
   photography that trade-off reverses -- a photo tile squeezed to half
   width reads as a thumbnail, not a hero. Below 700px every tile becomes
   a full-width 2x1 row.
   Point 9: this reset used to lose to the plain, unconditional `.bx.w2`/
   `.bx.tall` rules further down this file (equal specificity, later source
   position wins the tie) -- a span:2 tile stayed two grid tracks wide even
   inside grid-template-columns:1fr, which is exactly what forced Chrome to
   open a second, content-sized implicit column and squeezed every tile on
   the page into ~50px slivers next to it. Scoping to `.bento-wall .bx.w2`
   (one extra ancestor class) outranks the plain pair regardless of order,
   so this reset actually wins now. */
@media (max-width: 700px) {
  .bento-wall { grid-template-columns: 1fr; }
  .bento-wall .bx.w2, .bento-wall .bx.tall { grid-column: span 1; grid-row: span 1; }
  .bx.photo.tall { min-height: 240px; }
}

.bx {
  position: relative; overflow: hidden;
  display: flex; flex-direction: column; gap: var(--s-2);
  padding: var(--s-4) var(--s-5);
  border-radius: var(--r-6);
  background: var(--surface); color: var(--ink);
  transition: transform var(--d-2) var(--glide), box-shadow var(--d-2) var(--glide);
}
.bx:hover { transform: translateY(-3px); box-shadow: var(--lift-3); }
.bx.w2 { grid-column: span 2; }
/* row-span modifier is named "tall", not "h2" -- ".h2" is base.css's generic
   heading-look utility (h2, .h2 { font-size: var(--t-2xl); font-weight: 800;
   ... }), and a card also carrying the class "h2" was silently picking that
   up too, blowing its body copy out to headline size/weight/tracking. */
.bx.tall { grid-row: span 2; }
@media (max-width: 560px) { .bx.tall { grid-row: span 1; } }

.bx.ember  { background: var(--o-500);  color: var(--n-900); }
.bx.ink    { background: var(--n-900);  color: var(--bone); }
.bx.teal   { background: var(--c-3);    color: var(--ui-on-teal); }
.bx.masala { background: var(--masala); color: var(--masala-ink); }   /* warm dark, from the reference palette — the chart palette's indigo is for CHARTS, not branding */
.bx.wash   { background: var(--o-50);   color: var(--n-900); }
.bx.paper  { background: var(--n-0);    color: var(--n-900); }

/* Round 63, item 4: a dim 44px orb (js/orbs.js) in the top-right corner of
   every highlights card that is not a photograph -- the card's secondary
   ink, quiet next to the kicker and headline, not a fifth icon. Rides with
   its card in the phone gallery (it is not in the `.ph-sq`/`.bx-obj`
   phone-hide list below). */
.bx-orb { position: absolute; z-index: 6; top: 14px; right: 14px; width: 44px; height: 44px; opacity: .5; }

.bx .kicker {
  font-family: var(--font-mono); font-size: var(--t-xs); font-weight: 600;
  letter-spacing: var(--tr-caps); text-transform: uppercase; opacity: .62;
}
.bx h3 { font-size: var(--t-md); }
.bx.tall h3, .bx.w2 h3 { font-size: var(--t-lg); }
.bx p { font-size: var(--t-sm); opacity: .78; line-height: 1.5; }
.bx .figure {
  font-family: var(--font-mono); font-variant-numeric: tabular-nums;
  font-size: clamp(2.1rem, 3.4vw, 3rem); font-weight: 700;
  letter-spacing: -.045em; line-height: .95; margin-top: auto;
}
.bx-foot { margin-top: var(--s-3); display: flex; align-items: baseline; gap: var(--s-3); flex-wrap: wrap; }
.bx-foot b { font-family: var(--font-mono); font-size: var(--t-md); font-weight: 700; }
/* Round 29, item 2: `>`, not a descendant. The counter's split-flap reel
   (js/apple.js, `roll()`) builds its columns out of SPANs inside the `<b>`,
   and a bare `.bx-foot span` rule took the cell down to --t-xs while the
   digits inside it stayed at --t-md. The cell is `height: 1em` with
   `overflow: hidden` and the strip travels in the cell's own em, so an
   11.6px window showed a 17.5px digit at a 19-step offset measured in the
   wrong unit: a sliver of the wrong glyph, which is the broken mark in
   front of "print events". Only the caption is styled here now. */
.bx-foot > span { font-size: var(--t-xs); opacity: .7; }

/* a small live-ish visual for the bigger boxes */
.bx-bars { display: flex; align-items: flex-end; gap: 5px; height: 54px; margin-top: auto; }
.bx-bars i { flex: 1; border-radius: 4px 4px 2px 2px; background: currentColor; opacity: .20; }
.bx-bars i.on { opacity: .55; }
.bx-rows { display: grid; gap: 6px; margin-top: auto; font-family: var(--font-mono); font-size: var(--t-xs); }
.bx-rows div { display: flex; justify-content: space-between; gap: var(--s-3); padding: 5px 0;
  border-top: 1px solid currentColor; border-color: color-mix(in srgb, currentColor 18%, transparent); }

/* ============================================== apple-style feature run === */
.moment { position: relative; overflow: hidden; }
/* the receipt (.bill) sits in real 3D, rotateY/rotateX with no scale cap --
   a rendered, transformed box can paint past its own untransformed layout
   footprint, and nothing downstream of a plain rotate clips that back in.
   Same fix as `.moment` above: contain it at the section, which is full-
   bleed and has all the room the tilt ever actually needs, rather than at
   `.bill-rig` itself, which would crop the receipt's own corners. */
.bill-band { position: relative; overflow: hidden; }
.moment-in { display: grid; gap: clamp(24px, 4vw, 56px); align-items: center;
  grid-template-columns: minmax(0,.78fr) minmax(0,1.22fr); }
.moment.flip .moment-in { grid-template-columns: minmax(0,1.22fr) minmax(0,.78fr); }
.moment.flip .moment-copy { order: 2; }
@media (max-width: 920px) {
  .moment-in, .moment.flip .moment-in { grid-template-columns: 1fr; }
  .moment.flip .moment-copy { order: 0; }
}
.moment-copy .kicker {
  font-family: var(--font-mono); font-size: var(--t-xs); font-weight: 600;
  letter-spacing: var(--tr-caps); text-transform: uppercase; color: var(--accent-text);
}
.moment-copy h2 { margin-top: var(--s-3); font-size: var(--t-2xl); max-width: 16ch; }
.moment-copy .lede { margin-top: var(--s-4); }
.moment-copy .points { margin-top: var(--s-5); display: grid; gap: var(--s-3); }
.moment-copy .points div { display: grid; grid-template-columns: 15px 1fr; gap: var(--s-3); font-size: var(--t-sm); }
.moment-copy .points b { color: var(--ink); font-weight: 650; }
.moment-copy .points i { font-style: normal; color: var(--accent); font-weight: 700; }
/* min-width: 0 overrides the grid item's default `auto` -- without it, a
   `.moment-stage` holding a fixed-width device frame (.imac-box, .mac-box)
   refuses to shrink below that frame's own min-content size, which at
   <=920px (.moment-in's single `1fr` track) forces the whole row past the
   viewport instead of letting the frame's own --s fit routine shrink it.
   .pad-box never showed this because its own pre-JS width is already
   viewport-relative (clamp(.4, calc(88vw/740px), 1)), sidestepping the grid
   item's auto-min-width entirely -- but any other device frame in this slot
   needs the shrink allowed at this level, not re-solved per frame type. */
.moment-stage {
  min-width: 0;
  border-radius: var(--r-6); padding: 12px;
  background: var(--n-900); box-shadow: var(--lift-4);
}
.moment-stage .inner { border-radius: var(--r-4); overflow: hidden; container-type: inline-size; background: var(--bone); }

/* ---- plate cards -----------------------------------------------------------
   Used to sit on the illustration's own cream (#F1E9D2) so a feathered object
   had no visible edge. On the single bone ground that cream card read as an
   unexplained third surface, so the card is white instead -- a plain hairline
   card, same family as .modcard -- and the Picco cut-outs are un-premultiplied
   against that cream (tools/unmatte.py) so they carry no fringe on white.  */
.bx.plate {
  background: var(--n-0);
  border: 1px solid var(--n-100);
  border-radius: var(--r-5);
  box-shadow: var(--lift-2);
  color: var(--n-900);
  justify-content: flex-start;
}
/* Point 1/6: the drawn Picco object that used to fill a band at the top of
   the card is now a single 44px icon tile (56px on the wide/tall cards) --
   .bx-obj keeps its name so fewer call sites moved, but it is the same
   .ic-tile everywhere else now, not an <img>. */
.bx-obj { margin-bottom: var(--s-3); }
.bx.plate { justify-content: flex-start; }
.bx.plate .kicker { order: -1; }
.bx.plate .copy { display: block; margin-top: auto; }
.bx.plate .copy h3 { font-size: var(--t-md); }
.bx.plate.tall .copy h3 { font-size: var(--t-lg); }
/* Round 14, item 3: the glyph is naked and 28px on every bento tile, wide,
   tall or plain, so the wide/tall step-up has nothing left to step up. */
.bx.plate.w2 .bx-obj, .bx.plate.tall .bx-obj { --ic-tile-s: 28px; }

/* the same tile carries the moments and the serve cards */
.serve .obj { width: 54px; height: 54px; margin-bottom: 2px; }

/* ---- point 3: four bento tiles are real daylight photography, not icons --
   full-bleed image, the tile title set in white on a bottom gradient, the
   same idiom Apple uses for a photo tile in its own "here's what you get"
   grids. `.bx` already gives the card its radius/overflow/hover-lift; this
   only swaps the fill and repositions the copy over the image instead of
   under it. */
/* Round 29, item 2: the white-climbing gradient is gone and the label is
   white on a dark scrim again. The reason is measured, not stylistic: the
   lightening band reached 55 percent white only at 60 percent of the tile's
   height, and on a short phone tile over a dark photograph (a cash float in
   shadow, a table at dusk) the ink title sat on ground the gradient had
   barely touched. "The variance belongs to a named person." measured 1.5:1
   there. The scrim is bottom-anchored, the label is #FFFFFF and the eyebrow
   rides with it instead of floating at the top of the picture, where a
   bright frame made it disappear. The brightness lift comes off with it:
   a photograph is not asked to carry white type AND be lightened. */
.bx.photo { padding: 0; color: #FFFFFF; justify-content: flex-end; }
.bx.photo .bx-photo {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; object-position: center; z-index: 0;
}
.bx.photo::after {
  content: ""; position: absolute; inset: 0; z-index: 1; pointer-events: none;
  /* one band, from the foot: opaque enough under the label to clear 4.5:1
     on the brightest frame in the set, gone by 45 percent of the height so
     the picture above it is untouched. */
  background: linear-gradient(to top,
    rgba(0,0,0,.86) 0%, rgba(0,0,0,.76) 15%, rgba(0,0,0,.60) 28%,
    rgba(0,0,0,.36) 40%, rgba(0,0,0,0) 56%);
}
.bx.photo .kicker, .bx.photo .copy { position: relative; z-index: 2; }
.bx.photo { padding: var(--s-4) var(--s-5); }
/* the eyebrow sits directly above the label, both inside the scrim */
.bx.photo .kicker { color: rgba(255,255,255,.85); margin-top: auto; }
.bx.photo .copy h3 { color: #FFFFFF; font-size: var(--t-md); }
.bx.photo.w2 .copy h3, .bx.photo.tall .copy h3 { font-size: var(--t-lg); }
.bx.photo .copy { margin-top: 0; }
.bx.photo:hover .bx-photo { transform: scale(1.045); }
.bx.photo .bx-photo { transition: transform var(--d-3) var(--snap); }
@media (prefers-reduced-motion: reduce) { .bx.photo .bx-photo { transition: none; } }

/* ---- section head with art -------------------------------------------------
   Copy on the left, one Picco scene on the right. A consistent head treatment
   across the three cream sections; the scenes are subject-matched, not
   decorative — the desk set introduces the modules, the service tools the
   restaurant types, the receipts the mid-shift detail.                    */
.section-head.with-art {
  max-width: none;
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, .82fr);
  gap: clamp(20px, 4vw, 56px); align-items: center;
}
.section-head.with-art > div { max-width: 62ch; }
.head-art { width: 100%; height: auto; display: block; justify-self: end; }
@media (max-width: 900px) {
  .section-head.with-art { grid-template-columns: 1fr; }
  .head-art { max-width: 460px; justify-self: start; }
}

.on-orange .keyrow .plus { color: var(--ink-2); }
.on-orange .eyebrow::before { background: #201812; }

/* ---- Point 1: icon tiles, everywhere a Picco illustration used to sit ------
   Every one of these classes now styles a .ic-tile (css/apple.css) holding
   an inline <use> from the sprite, not an <img>. .pic-ic keeps its name
   (fewer call sites to touch) but the rule itself just nudges the shared
   tile's size per context instead of laying out an image. */
.pic-ic.sm { --ic-tile-s: 22px; }
.pic-ic.lg { --ic-tile-s: 32px; }

/* restaurant-type cards */
.serve .pic-ic { --ic-tile-s: 28px; }
.serve:hover .pic-ic { transform: rotate(-3deg) scale(1.04); }
.serve .pic-ic { transition: transform var(--d-2) var(--snap); }

/* mega-menu cards */
.mm-card .pic-ic { --ic-tile-s: 22px; }

/* shared by .sv-panel and .lg-slide below (a fade-and-settle on swap) */
@keyframes msIn { from { opacity: 0; transform: translateY(8px) scale(.97); } to { opacity: 1; transform: none; } }

/* ---- bento interaction -----------------------------------------------------
   The wall was static. Cards now lift and their object leans, the whole thing
   settling on the `snap` curve so it reads like something you pressed rather
   than something that floated.                                            */
.bx {
  transition: transform var(--d-2) var(--snap), box-shadow var(--d-2) var(--snap),
              filter var(--d-2) var(--snap);
  will-change: transform;
}
.bx:hover  { transform: translateY(-5px) scale(1.012); box-shadow: var(--lift-4); z-index: 2; }
.bx:active { transform: translateY(-1px) scale(.998); box-shadow: var(--lift-2); }
.bx .bx-obj { transition: transform var(--d-3) var(--snap); }
.bx:hover .bx-obj { transform: translateY(-6px) rotate(-2.5deg) scale(1.06); }
.bx .kicker { transition: color var(--d-2) var(--snap), opacity var(--d-2) var(--snap); }
/* full strength on hover: at .62 the orange kicker fell below 4.5:1 on every card */
.bx:hover .kicker { color: var(--o-800); opacity: 1; }
.bx.ink:hover .kicker, .bx.masala:hover .kicker { color: var(--o-400); }

/* a card that links somewhere grows a marker on hover.
   NOT on the photo tiles: `::after` there is the legibility scrim, and this
   rule's own `opacity: 0` cascaded straight into it (the scrim's higher
   specificity won only the properties it actually declares), which is why
   every photo tile shipped with its gradient painted at zero alpha and its
   label sitting on bare photograph. Round 29, item 2. */
.bx:not(.photo)::after {
  content: "\2192"; position: absolute; right: var(--s-5); bottom: var(--s-4);
  font-size: var(--t-base); opacity: 0; transform: translateX(-6px);
  transition: opacity var(--d-2) var(--snap), transform var(--d-2) var(--snap);
}
.bx:not(.photo):hover::after { opacity: .7; transform: none; }
@media (prefers-reduced-motion: reduce) {
  .bx:hover, .bx:hover .bx-obj { transform: none; }
}

/* ---- buttons: press, don't just recolour ---------------------------------- */
.btn { position: relative; overflow: hidden; }
.btn > * { position: relative; z-index: 1; }
.btn::before {                       /* a wash that sweeps across on hover */
  content: ""; position: absolute; inset: 0; z-index: 0;
  background: currentColor; opacity: 0;
  transform: translateX(-100%) skewX(-14deg);
  transition: transform var(--d-3) var(--glide), opacity var(--d-2) var(--glide);
}
.btn:hover::before { opacity: .10; transform: translateX(0) skewX(-14deg); }
.btn-link .arw { display: inline-block; }

/* the restaurant-type cards lift too */
.serve:active { transform: translateY(0); box-shadow: var(--lift-1); }

/* ---- pull the cable --------------------------------------------------------
   The plug illustration is drawn already out of its socket, so "plugged in"
   is the transformed state: it slides right and sits home. Pulling the cable
   slides it back out, tips it, and fires a spark at the socket.           */
.plug-stage {
  position: relative; margin: 0 auto var(--s-4);
  width: 100%; max-width: 200px; aspect-ratio: 1;
  border-radius: var(--r-4); background: #F1E9D2; overflow: hidden;
}
/* The plug is a five-second animation, generated from the seated drawing and
   played on demand. It is an ANIMATED WEBP rather than a video: the artifact
   viewer serves this document under a CSP that allows data: URIs for images
   but not for media, so an inlined <video> silently showed nothing there while
   working perfectly on a real server. A WebP with loop=1 also does exactly what
   is wanted — it plays once and holds its last frame. */
.plug-vid {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: contain; display: block;
}

.plug-spark {
  /* on the socket mouth, where the pins leave */
  position: absolute; top: 30%; right: 33%;
  width: 14px; height: 14px; border-radius: 50%;
  background: var(--o-500); opacity: 0; transform: scale(.3);
  box-shadow: 0 0 0 0 rgba(255,107,61,.55);
}
.wifi.is-down .plug-spark { animation: spark 620ms var(--glide) 60ms 1; }
@keyframes spark {
  0%   { opacity: 0; transform: scale(.3); box-shadow: 0 0 0 0 rgba(255,107,61,.5); }
  35%  { opacity: 1; transform: scale(1);  box-shadow: 0 0 0 10px rgba(255,107,61,.28); }
  100% { opacity: 0; transform: scale(1.5); box-shadow: 0 0 0 24px rgba(255,107,61,0); }
}
/* the queue readout ticks up while the line is down */
.wifi.is-down .wifi-q b { color: var(--o-400); }
.wifi-q b { transition: color var(--d-2) var(--glide); }
#wifi-queue { display: inline-block; transition: transform var(--d-1) var(--snap); }
#wifi-queue.bump { transform: scale(1.35); }
@media (prefers-reduced-motion: reduce) {
  .plug-art { transition: none; }
  .wifi.is-down .plug-spark { animation: none; }
}

/* ------------------------------------------------- module index + pager ---
   product.html is the index of the eighteen module pages; each page ends with
   the two next to it, so the set can be read straight through.             */
.modcards {
  display: grid; gap: var(--s-3); margin-top: var(--s-6);
  grid-template-columns: repeat(auto-fill, minmax(238px, 1fr));
}
.modcard {
  position: relative; display: grid; gap: 5px; align-content: start;
  padding: var(--s-5); border-radius: var(--r-5);
  background: var(--surface); border: 1px solid var(--line-soft);
  color: var(--ink);
  transition: transform var(--d-2) var(--snap), box-shadow var(--d-2) var(--glide),
              border-color var(--d-2) var(--glide);
}
.modcard:hover { transform: translateY(-4px); box-shadow: var(--lift-3); border-color: var(--line); }
.modcard .num {
  font-family: var(--font-mono); font-size: var(--t-xs); font-weight: 700;
  color: var(--accent-text); letter-spacing: .1em;
}
.modcard b { font-family: var(--font-display); font-weight: 800; font-size: var(--t-md);
             letter-spacing: var(--tr-tight); line-height: 1.15; }
.modcard .blurb { color: var(--ink-3); font-size: var(--t-sm); line-height: 1.5; }
.modcard::after {
  content: "\2192"; position: absolute; right: var(--s-5); top: var(--s-5);
  color: var(--ink-3); opacity: 0; transition: opacity var(--d-2) var(--glide);
}
.modcard:hover::after { opacity: .7; }

/* Round 26, item 5: the pager's whole recipe moved to css/modules.css, where
   the rest of the module page's own idiom lives. The rules that were here
   drew a bordered grid card and mirrored the "next" one to the right, which
   is what put its label, its title and its line on three alignments. */
.modnav a[aria-current="page"] { color: var(--accent-text); font-weight: 700; }

/* ---------------------------------------------------- savings calculator ---
   The comparison the pricing page is really about: not what delEat costs, but
   what a percentage of every order costs when somebody else takes it. The
   subscription is netted off deliberately — a saving that ignores the tool's
   own price is not a saving.                                               */
.calc {
  display: grid; grid-template-columns: minmax(0,1.05fr) minmax(0,.95fr);
  gap: clamp(20px, 3vw, 44px); margin-top: var(--s-6);
  align-items: start;
}
@media (max-width: 860px) { .calc { grid-template-columns: 1fr; } }

.calc-inputs { display: grid; gap: var(--s-5); }
.calc-row { display: grid; grid-template-columns: 1fr auto; gap: 4px 12px; }
.calc-row label { font-weight: 600; font-size: var(--t-sm); }
.calc-row output {
  font-family: var(--font-mono); font-weight: 700; font-size: var(--t-sm);
  font-variant-numeric: tabular-nums; color: var(--accent-text);
}
.calc-row input[type="range"] { grid-column: 1 / -1; width: 100%; }
.calc-scale {
  grid-column: 1 / -1; display: flex; justify-content: space-between;
  font-family: var(--font-mono); font-size: var(--t-xs); color: var(--ink-3);
}

/* One slider, drawn rather than borrowed, so it matches the keys elsewhere. */
input[type="range"] {
  -webkit-appearance: none; appearance: none; background: transparent;
  height: 24px; margin: 2px 0 0; cursor: pointer;
}
input[type="range"]::-webkit-slider-runnable-track {
  height: 6px; border-radius: 999px; background: var(--surface-sunk);
  border: 1px solid var(--line);
}
input[type="range"]::-moz-range-track {
  height: 6px; border-radius: 999px; background: var(--surface-sunk);
  border: 1px solid var(--line);
}
input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 22px; height: 22px; margin-top: -9px; border-radius: 50%;
  background: var(--accent); border: 2px solid var(--ink);
  box-shadow: 0 2px 0 var(--o-700);
  transition: transform var(--d-1) var(--snap);
}
input[type="range"]::-moz-range-thumb {
  width: 22px; height: 22px; border-radius: 50%; border: 2px solid var(--ink);
  background: var(--accent); box-shadow: 0 2px 0 var(--o-700);
}
input[type="range"]:active::-webkit-slider-thumb { transform: translateY(2px) scale(.94); }
input[type="range"]:focus-visible::-webkit-slider-thumb { outline: 2px solid var(--o-600); outline-offset: 2px; }

.calc-out {
  background: var(--surface); border: 1px solid var(--line-soft);
  border-radius: var(--r-5); padding: var(--s-5);
}
.calc-hero { padding-bottom: var(--s-4); border-bottom: 1px solid var(--line-soft); }
.calc-hero .lab {
  display: block; font-family: var(--font-mono); font-size: var(--t-xs);
  letter-spacing: .1em; text-transform: uppercase; color: var(--ink-3);
}
.calc-hero b {
  display: block; margin-top: 4px;
  font-family: var(--font-mono); font-weight: 700;
  font-size: clamp(30px, 4.6vw, 46px); letter-spacing: -.035em;
  font-variant-numeric: tabular-nums; line-height: 1;
}
.calc-hero .cap { display: block; margin-top: 8px; font-size: var(--t-sm); color: var(--ink-3); }

.calc-lines { margin: var(--s-4) 0 0; display: grid; gap: 7px; }
.calc-lines > div {
  display: flex; justify-content: space-between; gap: var(--s-4);
  font-size: var(--t-sm); color: var(--ink-2);
}
.calc-lines dt { margin: 0; }
.calc-lines dd {
  margin: 0; font-family: var(--font-mono); font-variant-numeric: tabular-nums;
  color: var(--ink);
}
.calc-lines .sum {
  padding-top: 7px; margin-top: 3px; border-top: 1px solid var(--line-soft);
  font-weight: 600; color: var(--ink);
}
.calc-be {
  margin: var(--s-4) 0 0; padding-top: var(--s-4);
  border-top: 1px solid var(--line-soft);
  font-size: var(--t-sm); color: var(--ink-3);
}
.calc-be b { color: var(--ink); font-family: var(--font-mono); }

/* ---- Pricing v6: card tips, the module catalogue, the bill calculator ------
   No boxed feature grid on purpose: the catalogue is a ruled list, one module a
   row, the price and what it replaces set side by side. */
.cycle-note { text-align: center; margin: calc(var(--s-5) * -1) auto var(--s-7); max-width: 62ch; }
.plan-tip {
  margin: var(--s-4) 0 0; padding: var(--s-3) var(--s-4);
  background: var(--surface-2); border-radius: var(--r-3);
  font-size: var(--t-xs); line-height: 1.5; color: var(--ink-2);
}
.plan-tip [data-tip-second] { display: block; margin-top: 4px; color: var(--ink-3); }

.cat { margin-top: var(--s-7); display: grid; gap: var(--s-9); }
.cat-core { border: var(--hair); border-radius: var(--r-5); padding: var(--s-5) var(--s-6); background: var(--surface); }
.cat-core h3 { font-size: var(--t-md); }
.cat-core p { margin: var(--s-2) 0 0; color: var(--ink-2); font-size: var(--t-sm); line-height: 1.55; max-width: 74ch; }
.cat-group h3 { font-size: var(--t-md); padding-bottom: var(--s-3); border-bottom: 2px solid var(--ink); }
.cat-list { list-style: none; margin: 0; padding: 0; }
.cat-mod {
  display: grid; grid-template-columns: minmax(200px, .8fr) minmax(0, 1.7fr);
  gap: var(--s-3) clamp(20px, 4vw, 56px); padding: var(--s-5) 0; border-bottom: var(--hair);
  scroll-margin-top: 96px;
}
.cat-top h4 { font-size: var(--t-base); line-height: 1.25; }
.cat-top h4 a { color: inherit; text-decoration: none; }
.cat-top h4 a:hover { text-decoration: underline; text-underline-offset: 3px; }
.cat-price { margin: 6px 0 0; display: flex; align-items: baseline; gap: 6px; flex-wrap: wrap; }
.cat-price b { font-family: var(--font-mono); font-weight: 700; font-size: var(--t-md); color: var(--accent-text); font-variant-numeric: tabular-nums; }
.cat-price span { font-size: var(--t-xs); color: var(--ink-3); }
.cat-in { margin: 4px 0 0; font-family: var(--font-mono); font-size: 11px; letter-spacing: .04em; color: var(--ink-3); }
.cat-dl { margin: 0; display: grid; gap: var(--s-3); align-content: start; }
.cat-dl > div { display: grid; grid-template-columns: 92px minmax(0, 1fr); gap: var(--s-4); }
.cat-dl dt { font-family: var(--font-mono); font-size: 11px; letter-spacing: .08em; text-transform: uppercase; color: var(--ink-3); padding-top: 3px; }
.cat-dl dd { margin: 0; font-size: var(--t-sm); line-height: 1.55; color: var(--ink-2); }
.cat-dl dd a { color: var(--accent-text); text-underline-offset: 3px; }
.cat-note, .cat-need { grid-column: 2; margin: 0; font-size: var(--t-xs); line-height: 1.5; color: var(--ink-2); }
.cat-note { padding: var(--s-3) var(--s-4); background: var(--surface-2); border-radius: var(--r-3); }
.cat-need { color: var(--ink-3); }
.cat-mod-soon .cat-price { margin-top: 8px; }
@media (max-width: 760px) {
  .cat-mod { grid-template-columns: 1fr; padding: var(--s-4) 0; }
  .cat-dl > div { grid-template-columns: 1fr; gap: 2px; }
  .cat-note, .cat-need { grid-column: 1; }
}

.calc-bill { grid-template-columns: minmax(0, 1.15fr) minmax(0, .85fr); }
.bill-in { display: grid; gap: var(--s-5); margin: 0; min-width: 0; }
.bill-group { border: 0; margin: 0; padding: 0; min-width: 0; }
.bill-group legend { padding: 0 0 var(--s-2); font-family: var(--font-mono); font-size: 11px; letter-spacing: .08em; text-transform: uppercase; color: var(--ink-3); }
.bill-opt {
  display: grid; grid-template-columns: 22px minmax(0, 1fr) auto; align-items: center; gap: var(--s-3);
  padding: 11px 2px; border-bottom: 1px solid var(--line-soft); cursor: pointer; min-height: 44px;
}
.bill-opt input[type="checkbox"] { width: 18px; height: 18px; accent-color: var(--o-600); margin: 0; }
.bill-name { font-size: var(--t-sm); line-height: 1.35; color: var(--ink); }
.bill-name small { display: block; font-size: var(--t-xs); color: var(--ink-3); margin-top: 2px; }
.bill-p { font-family: var(--font-mono); font-size: var(--t-xs); font-variant-numeric: tabular-nums; color: var(--ink-2); white-space: nowrap; }
.bill-unit { grid-template-columns: minmax(0, 1fr) 64px auto; }
.bill-unit input[type="number"] {
  width: 64px; height: 38px; padding: 0 8px; font: inherit; font-family: var(--font-mono); font-size: var(--t-sm);
  border: var(--hair); border-radius: var(--r-2); background: var(--surface); color: var(--ink);
}
.bill-out .calc-lines > div.is-best dt, .bill-out .calc-lines > div.is-best dd { color: var(--ink); font-weight: 700; }
.bill-out .calc-lines > div.is-best dt::after { content: " lowest"; font-family: var(--font-mono); font-size: 10px; letter-spacing: .06em; text-transform: uppercase; color: var(--accent-text); }
.bill-say { margin: var(--s-4) 0 0; font-size: var(--t-sm); line-height: 1.5; color: var(--ink); font-weight: 600; }
.bill-say[hidden], .bill-added[hidden] { display: none; }
.bill-added, .bill-bundle { margin: var(--s-3) 0 0; }
.bill-cta { margin-top: var(--s-5); width: 100%; }
@media (min-width: 900px) and (hover: hover) {
  .calc-bill .bill-out { position: sticky; top: 96px; }
}
@media (max-width: 860px) { .calc-bill { grid-template-columns: 1fr; } }

/* ------------------------------------------------- which one are you ------
   A type picker over the modules that shape actually needs. The first pass
   stacked a photograph on top of a white card holding the icon and the blurb;
   the card fought the photograph, and the icon ended up at the bottom of the
   column where nobody would look. Now the photograph IS the right column, and
   the module's icon and line live in the row itself, where the eye already is.
                                                                            */
/* Round 39, item 7: the glass pill is the WRAPPER and the tablist inside it
   is a transparent scroller. The rail fade is a mask, and a mask eats the
   element it is set on: with the surface on the scroller the strip's own
   right end dissolved at rest, which reads as a broken pill rather than as
   "there is more this way". The surface is never masked now; only the chips
   are. */
.sv-pillwrap {
  position: relative; width: fit-content; max-width: 100%;
  border-radius: 999px; padding: 5px;
  /* Liquid glass: a translucent ground that samples what is behind it, a
     hairline edge, and a light catch along the top. */
  background: color-mix(in srgb, var(--surface) 55%, transparent);
  -webkit-backdrop-filter: blur(18px) saturate(1.7);
  backdrop-filter: blur(18px) saturate(1.7);
  border: 1px solid color-mix(in srgb, var(--ink) 9%, transparent);
  box-shadow: inset 0 1px 0 color-mix(in srgb, #FFFFFF 62%, transparent),
              0 10px 26px -14px color-mix(in srgb, var(--ink) 34%, transparent);
}
.sv-pills {
  position: relative;
  display: flex; gap: 2px; padding: 5px;
  border-radius: 999px; width: fit-content; max-width: 100%;
  overflow-x: auto; scrollbar-width: none; -ms-overflow-style: none;
  cursor: grab; user-select: none; touch-action: pan-y;
  /* the surface, for the older index variants that have no wrapper */
  background: color-mix(in srgb, var(--surface) 55%, transparent);
  -webkit-backdrop-filter: blur(18px) saturate(1.7);
  backdrop-filter: blur(18px) saturate(1.7);
  border: 1px solid color-mix(in srgb, var(--ink) 9%, transparent);
  box-shadow: inset 0 1px 0 color-mix(in srgb, #FFFFFF 62%, transparent),
              0 10px 26px -14px color-mix(in srgb, var(--ink) 34%, transparent);
}
/* wrapped: the wrapper is the pill, the tablist is a bare scroller */
.sv-pillwrap > .sv-pills {
  padding: 0; width: auto;
  background: none; border: 0; box-shadow: none;
  -webkit-backdrop-filter: none; backdrop-filter: none;
}
.sv-pillwrap > .sv-pills .sv-thumb { left: 0; top: 0; height: 100%; }
.sv-pills::-webkit-scrollbar { display: none; }
.sv-pills.is-dragging { cursor: grabbing; }
@media (prefers-reduced-transparency: reduce), (prefers-contrast: more) {
  .sv-pillwrap { background: var(--surface); backdrop-filter: none; -webkit-backdrop-filter: none; }
}
@media (prefers-contrast: more) {
  .sv-pillwrap { border: 2px solid var(--ink); }
}

/* The selection is a single object that TRAVELS between the options rather
   than a background that blinks from one to the next — the same behaviour the
   Live / Analyse control has in the product. It can also be dragged, and the
   panel follows it while it moves.
   Round 22, Apple Design audit sections 3/5/6: no CSS transition here any
   more — js/site.js drives transform/width every frame with a Spring pair
   (velocity handed off from the drag release, skill section 5), which a
   fixed-curve CSS transition cannot take a mid-flight velocity into and
   cannot be re-targeted from without restarting the curve. Reduced motion
   still gets a plain jump: js/site.js never constructs the springs there,
   the same effect this transition:none used to produce. */
.sv-thumb {
  position: absolute; left: 5px; top: 5px; z-index: 0;
  height: calc(100% - 10px); width: 0; border-radius: 999px;
  background: var(--ink); pointer-events: none;
  box-shadow: 0 2px 10px -3px color-mix(in srgb, var(--ink) 55%, transparent);
  transform: translateX(0);
}

.sv-pills button {
  position: relative; z-index: 1;
  font: inherit; font-size: var(--t-sm); font-weight: 600; cursor: pointer;
  padding: 9px 17px; border: 0; border-radius: 999px; white-space: nowrap;
  background: transparent; color: var(--ink-2);
  transition: color var(--d-2) var(--glide);
}
.sv-pills button:hover { color: var(--ink); }
.sv-pills button[aria-selected="true"] { color: var(--ground); }
/* Round 22, Apple Design audit section 1: feedback on pointer-down, not on
   release — every other pressable control on the page already highlights on
   :active (see .btn, .chapter-pill-open, .ask-chip, .bx); this tab strip did
   not. Scale only, so it never fights the thumb spring travelling under it. */
.sv-pills button:active { transform: scale(.94); transition: transform .1s ease-out, color var(--d-2) var(--glide); }
@media (prefers-reduced-motion: reduce) { .sv-pills button:active { transform: none; } }
.sv-pills button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.sv-line { margin: var(--s-5) 0 0; max-width: 62ch; font-size: var(--t-md); color: var(--ink-2); }
.sv-body {
  display: grid; grid-template-columns: minmax(0,1fr) minmax(0,.92fr);
  gap: clamp(20px, 3.4vw, 56px); align-items: start; margin-top: var(--s-5);
}
@media (max-width: 860px) { .sv-body { grid-template-columns: 1fr; } }

.sv-list { display: grid; }
.sv-list a {
  display: grid; grid-template-columns: 34px minmax(0,1fr) 26px;
  align-items: center; gap: var(--s-4);
  padding: 14px 0; border-bottom: 1px solid var(--line-soft);
  color: var(--ink-3);
  transition: color var(--d-2) var(--glide);
}
.sv-ic {
  --ic-tile-s: 28px;
  opacity: .6;
  transition: opacity var(--d-2) var(--glide), transform var(--d-2) var(--snap);
}
.sv-list a b {
  display: block; font-family: var(--font-display); font-weight: 800;
  font-size: clamp(18px, 2vw, 25px); letter-spacing: var(--tr-tight); line-height: 1.18;
}
.sv-list a em {
  display: block; font-style: normal; font-size: var(--t-sm);
  color: var(--ink-2); line-height: 1.5;
  max-height: 0; opacity: 0; overflow: hidden;
  transition: max-height var(--d-3) var(--glide), opacity var(--d-2) var(--glide),
              margin-top var(--d-3) var(--glide);
}
.sv-list a i {
  justify-self: end; width: 26px; height: 26px; border-radius: 50%;
  background: var(--ink); opacity: 0; transform: scale(.7);
  transition: opacity var(--d-2) var(--glide), transform var(--d-2) var(--snap);
}
.sv-list a i::after {
  content: "\2197"; display: grid; place-items: center; width: 100%; height: 100%;
  color: var(--ground); font-size: 13px; font-style: normal;
}
.sv-list a:hover, .sv-list a:focus-visible { color: var(--ink); }
.sv-list a:hover .sv-ic { opacity: .9; }
.sv-list a.on { color: var(--ink); }
.sv-list a.on .sv-ic { opacity: 1; transform: scale(1.06); }
.sv-list a.on em { max-height: 6em; opacity: 1; margin-top: 4px; }
.sv-list a.on i { opacity: 1; transform: scale(1); }
@media (prefers-reduced-motion: reduce) {
  .sv-list a em, .sv-ic, .sv-list a i { transition: none; }
}

/* The photograph is the column, not something stacked above a card. */
.sv-art { position: relative; }
.sv-photo {
  display: block; width: 100%; height: auto; aspect-ratio: 4 / 5;
  object-fit: cover; border-radius: var(--r-5); background: var(--surface-2);
}
@media (max-width: 860px) { .sv-photo { aspect-ratio: 16 / 10; } }
@media (prefers-reduced-motion: no-preference) {
  .sv-panel:not([hidden]) .sv-photo { animation: msIn var(--d-4) var(--glide); }
}

/* ============================================================================
   The sign-in page. Half form, half product — the shape Sapaad's uses, built
   out of this site's own tokens so it reads as the same thing rather than a
   banner someone exported into a different application.
   ========================================================================= */
.login-body { min-height: 100dvh; }
.login {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr);
  min-height: 100dvh;
}
@media (max-width: 900px) { .login { grid-template-columns: 1fr; } }

.lg-form {
  display: grid; grid-template-rows: auto 1fr auto; gap: var(--s-6);
  padding: clamp(24px, 4vw, 52px) clamp(22px, 5vw, 72px);
  background: var(--ground);
}
.lg-brand img { display: block; height: 30px; width: auto; }
.lg-inner { align-self: center; width: 100%; max-width: 392px; }
.lg-inner h1 {
  font-family: var(--font-display); font-weight: 800;
  font-size: clamp(30px, 4.2vw, 42px); letter-spacing: var(--tr-tight); line-height: 1.05;
}
.lg-sub { margin-top: 8px; color: var(--ink-3); font-size: var(--t-sm); }

.lg-fields { display: grid; gap: 7px; margin-top: var(--s-6); }
.lg-fields label, .lg-row label {
  font-size: var(--t-sm); font-weight: 600; letter-spacing: -.004em;
}
.lg-fields input[type="email"], .lg-fields input[type="password"] {
  font: inherit; font-size: var(--t-md); width: 100%;
  padding: 13px 15px; margin-bottom: var(--s-4);
  border-radius: var(--r-3); border: 1px solid var(--line);
  background: var(--surface); color: var(--ink);
  transition: border-color var(--d-2) var(--glide), box-shadow var(--d-2) var(--glide);
}
.lg-fields input::placeholder { color: var(--ink-3); opacity: .7; }
.lg-fields input:focus {
  outline: none; border-color: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 28%, transparent);
}
.lg-row { display: flex; align-items: baseline; justify-content: space-between; gap: var(--s-4); }
.lg-forgot { font-size: var(--t-sm); color: var(--accent-text); font-weight: 600; }
.lg-check {
  display: flex; align-items: center; gap: 9px; font-weight: 400;
  font-size: var(--t-sm); color: var(--ink-2); margin: 2px 0 var(--s-5);
}
.lg-check input { width: 17px; height: 17px; accent-color: var(--accent); }
.lg-go { width: 100%; justify-content: center; }
.lg-alt { margin-top: var(--s-5); font-size: var(--t-sm); color: var(--ink-3); }
.lg-alt a { color: var(--accent-text); font-weight: 600; }
.lg-foot {
  font-family: var(--font-mono); font-size: var(--t-xs);
  letter-spacing: .12em; text-transform: uppercase; color: var(--ink-3);
}

/* ---- the half that sells something --------------------------------------- */
.lg-promo {
  position: relative; overflow: hidden;
  display: grid; grid-template-rows: 1fr auto; align-content: center;
  gap: var(--s-6); padding: clamp(28px, 4vw, 64px);
  background: var(--n-900); color: var(--bone);
}
@media (max-width: 900px) { .lg-promo { min-height: 62vh; } }
/* the same scanlines the hero's tube has, at a whisper */
.lg-scan {
  position: absolute; inset: 0; pointer-events: none; opacity: .5;
  background: repeating-linear-gradient(to bottom,
    rgba(255,255,255,.028) 0 1px, transparent 1px 4px);
}
.lg-slide {
  position: relative; align-self: center; max-width: 460px;
  margin-inline: auto; text-align: left;
}
.lg-slide[hidden] { display: none; }
@media (prefers-reduced-motion: no-preference) {
  .lg-slide:not([hidden]) { animation: msIn var(--d-4) var(--glide); }
}
.lg-slide img {
  display: block; width: 138px; height: auto;
  margin-bottom: var(--s-5);
}
.lg-eyebrow {
  font-family: var(--font-mono); font-size: var(--t-xs); font-weight: 700;
  letter-spacing: .12em; text-transform: uppercase; color: var(--o-400);
}
.lg-slide h2 {
  margin-top: 10px; font-family: var(--font-display); font-weight: 800;
  font-size: clamp(24px, 3.1vw, 34px); letter-spacing: var(--tr-tight);
  line-height: 1.12; text-wrap: balance;
}
.lg-slide ul { list-style: none; margin: var(--s-5) 0 0; padding: 0; display: grid; gap: 12px; }
.lg-slide li { display: grid; gap: 2px; }
.lg-slide li b { font-size: var(--t-sm); font-weight: 650; }
.lg-slide li span { font-size: var(--t-sm); color: #A8A5A1; }
.lg-slide .go { margin-top: var(--s-5); display: inline-block; }

.lg-dots { display: flex; gap: 8px; justify-content: center; }
.lg-dots button {
  width: 30px; height: 4px; border: 0; border-radius: 999px; cursor: pointer;
  background: rgba(255,255,255,.22);
  transition: background var(--d-2) var(--glide), width var(--d-2) var(--snap);
}
.lg-dots button[aria-current="true"] { background: var(--o-500); width: 46px; }
.lg-dots button:focus-visible { outline: 2px solid var(--bone); outline-offset: 3px; }

.nav-signin {
  font-size: var(--t-sm); font-weight: 600; color: var(--ink-2);
  padding: 0 var(--s-3); white-space: nowrap;
  transition: color var(--d-2) var(--glide);
}
.nav-signin:hover { color: var(--ink); }
@media (max-width: 620px) { .nav-signin { display: none; } }

/* ---------------------------------------------------------------- legal ---- */
.legal-in { max-width: 66ch; }
.legal-meta { font-family: var(--font-mono); font-size: var(--t-xs); color: var(--ink-3); letter-spacing: .04em; }
.legal h2 { font-family: var(--font-display); font-weight: 800; font-size: var(--t-lg); letter-spacing: var(--tr-tight); margin-top: var(--s-7); }
.legal p { margin-top: var(--s-3); color: var(--ink-2); line-height: 1.6; }
.legal p em { font-style: normal; font-weight: 600; color: var(--ink); }
.legal a { color: var(--accent-text); font-weight: 600; }

/* ------------------------------------------------------- start (signup) ---- */
.start .lg-inner.st-inner { max-width: 560px; }
.st-fields { gap: 0; }
.st-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 6px var(--s-4); }
@media (max-width: 560px) { .st-grid { grid-template-columns: 1fr; } }
.st-field { display: grid; gap: 6px; margin-bottom: var(--s-4); }
.st-field label { font-size: var(--t-sm); font-weight: 600; }
.st-field input, .st-field select {
  font: inherit; font-size: var(--t-md); width: 100%; padding: 12px 14px;
  border-radius: var(--r-3); border: 1px solid var(--line); background: var(--surface); color: var(--ink);
  transition: border-color var(--d-2) var(--glide), box-shadow var(--d-2) var(--glide);
}
.st-field input:focus, .st-field select:focus {
  outline: none; border-color: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 28%, transparent);
}
.st-field input:user-invalid { border-color: var(--bad); }
/* Round 22, Apple Design audit section 16: inline error text, not just a
   red border -- the four kinds of feedback the skill names (status,
   completion, warning, error) need something readable, not only colour. */
.st-field.has-err input, .st-field.has-err select { border-color: var(--bad); }
.st-msg { font-size: var(--t-xs); color: var(--bad); margin-top: -1px; }
.st-msg[hidden] { display: none; }
.st-submit-warn {
  margin-top: var(--s-4); padding: 12px 14px; border-radius: var(--r-3);
  background: var(--warn-wash); color: var(--warn-ink); font-size: var(--t-sm);
}
.st-submit-warn[hidden] { display: none; }
.st-plans { border: 0; padding: 0; margin: var(--s-2) 0 var(--s-5); display: grid; gap: 8px; }
.st-plans legend { font-size: var(--t-sm); font-weight: 600; margin-bottom: 8px; }
.st-plans legend span { font-weight: 400; color: var(--ink-3); }
.st-plan { display: block; cursor: pointer; }
.st-plan input { position: absolute; opacity: 0; width: 1px; height: 1px; }
.st-plan span {
  display: grid; grid-template-columns: auto 1fr; gap: 2px 14px; align-items: baseline;
  padding: 12px 14px; border-radius: var(--r-3); border: 1.5px solid var(--line); background: var(--surface);
  transition: border-color var(--d-2) var(--glide), box-shadow var(--d-2) var(--glide);
}
.st-plan span b { font-family: var(--font-display); font-weight: 800; font-size: var(--t-md); }
.st-plan span em { font-style: normal; font-family: var(--font-mono); font-size: var(--t-sm); justify-self: end; }
.st-plan span em i { font-style: normal; color: var(--ink-3); }
.st-plan span small { grid-column: 1 / -1; color: var(--ink-3); font-size: var(--t-xs); }
.st-plan input:checked + span { border-color: var(--accent); box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 22%, transparent); }
.st-plan input:focus-visible + span { outline: 2px solid var(--accent); outline-offset: 2px; }
.st-fine { font-size: var(--t-xs); }
.st-done h2 { font-family: var(--font-display); font-weight: 800; font-size: var(--t-xl); letter-spacing: var(--tr-tight); margin-top: 10px; }
.st-done p { margin-top: var(--s-3); color: var(--ink-2); }
.st-done .go { margin-top: var(--s-5); display: inline-block; }
.st-side .lg-slide { max-width: 420px; }
.st-note { margin-top: var(--s-5); font-size: var(--t-sm); color: #A8A5A1; }


/* ------------------------------------------------------------- drift -----
   Objects crossing the band in three lanes at three depths — the Picco hero,
   done with the site's own illustrations. Pure CSS marquee: each track holds
   two copies of its lane and slides exactly one copy, so it loops without a
   seam. Depth is speed, size and blur, nothing cleverer.                   */
.drift { overflow: hidden; padding-bottom: 0; }
.drift-head h2 { max-width: 22ch; }
.drift-field { position: relative; height: clamp(300px, 34vw, 460px); margin-top: var(--s-5); }
.drift-lane { position: absolute; left: 0; right: 0; overflow: visible; }
.drift-lane.l1 { top: 4%;  height: 46%; }
.drift-lane.l2 { top: 38%; height: 34%; }
.drift-lane.l3 { top: 66%; height: 26%; }
.drift-track {
  display: flex; align-items: center; gap: clamp(40px, 6vw, 110px);
  width: max-content; height: 100%;
  animation: drift var(--drift-t, 60s) linear infinite;
}
.drift-track img { height: 100%; width: auto; flex: none; animation: bob var(--bob-t, 5s) ease-in-out infinite; }
.l1 .drift-track { --drift-t: 58s; }
.l2 .drift-track { --drift-t: 82s; animation-direction: reverse; filter: blur(.3px); opacity: .9; }
.l3 .drift-track { --drift-t: 118s; filter: blur(.8px); opacity: .62; }
.l2 .drift-track img { --bob-t: 6.5s; }
.l3 .drift-track img { --bob-t: 8s; }
.drift-track img:nth-child(odd)  { animation-delay: -2.1s; }
.drift-track img:nth-child(3n)   { animation-delay: -3.7s; }
@keyframes drift { to { transform: translateX(-50%); } }
@keyframes bob { 0%,100% { transform: translateY(0) rotate(-1.5deg); } 50% { transform: translateY(-9px) rotate(1.5deg); } }
.drift-field::before, .drift-field::after {
  content: ""; position: absolute; top: 0; bottom: 0; width: clamp(40px, 8vw, 140px); z-index: 2; pointer-events: none;
}
.drift-field::before { left: 0;  background: linear-gradient(to right, var(--ground), transparent); }
.drift-field::after  { right: 0; background: linear-gradient(to left,  var(--ground), transparent); }
@media (prefers-reduced-motion: reduce) { .drift-track, .drift-track img { animation: none; } }

/* ------------------------------------------------- module landing pages ---
   Five parts, in order: the claim, the thing, what it sits beside, the two
   questions, and somewhere to go. The section in the middle is the one that
   was always there; everything around it is what makes this a page.       */
.mod-hero { padding-block: clamp(34px, 5vw, 72px) clamp(30px, 4.5vw, 60px); }
.mh-in { display: grid; grid-template-columns: minmax(0,1.25fr) minmax(0,.75fr); gap: clamp(24px, 4vw, 64px); align-items: center; }
@media (max-width: 860px) { .mh-in { grid-template-columns: 1fr; } .mh-art { order: -1; } }
.mh-copy h1 { margin-top: var(--s-3); max-width: 16ch; }
.mh-copy .lede { margin-top: var(--s-4); max-width: 54ch; }
.mh-copy .hero-actions { margin-top: var(--s-5); }
.mh-art { display: grid; place-items: center; }
.mh-icon { width: min(100%, 300px); height: auto; filter: drop-shadow(0 30px 34px rgba(0,0,0,.45)); }
@media (prefers-reduced-motion: no-preference) {
  .mh-icon { animation: mhFloat 7s ease-in-out infinite; }
}
@keyframes mhFloat { 0%,100% { transform: translateY(0) rotate(-1deg); } 50% { transform: translateY(-12px) rotate(1.5deg); } }
.mod-h { font-size: var(--t-lg) !important; }

.mod-related .rel-grid { display: grid; grid-template-columns: repeat(3, minmax(0,1fr)); gap: var(--s-4); margin-top: var(--s-6); }
@media (max-width: 760px) { .mod-related .rel-grid { grid-template-columns: 1fr; } }
.rel {
  display: grid; grid-template-columns: 34px minmax(0,1fr); gap: 4px 14px; align-items: start;
  padding: var(--s-5); border-radius: var(--r-5); background: var(--surface); border: 1px solid var(--line-soft);
  color: var(--ink); transition: transform var(--d-2) var(--snap), box-shadow var(--d-2) var(--glide);
}
.rel:hover { transform: translateY(-4px); box-shadow: var(--lift-3); }
.rel-ic { grid-row: 1 / span 2; width: 28px; height: 28px; }
.rel b { font-family: var(--font-display); font-weight: 800; letter-spacing: var(--tr-tight); }
.rel > span:last-child { color: var(--ink-3); font-size: var(--t-sm); line-height: 1.45; } /* scoped: the status pill and icon tile keep their own colours */

.faq-in { max-width: 72ch; }
.faq-list { margin-top: var(--s-6); display: grid; gap: 8px; }
.faq-item { border: 1px solid var(--line-soft); border-radius: var(--r-4); background: var(--ground); padding: 0 var(--s-5); }
.faq-item summary {
  cursor: pointer; list-style: none; padding: 16px 0; font-weight: 650;
  display: flex; justify-content: space-between; gap: var(--s-4); align-items: center;
}
.faq-item summary::-webkit-details-marker { display: none; }
.faq-item summary::after { content: "+"; font-family: var(--font-mono); color: var(--accent-text); transition: transform var(--d-2) var(--snap); }
.faq-item[open] summary::after { transform: rotate(45deg); }
.faq-item p { margin: 0 0 16px; color: var(--ink-2); line-height: 1.55; }
.modcard-ic { width: 44px; height: 44px; object-fit: contain; margin-bottom: 4px; }


/* ---- the rooms (superseded) -------------------------------------------
   The built-not-shot room set (three CSS planes + a picco cut-out) is gone
   from the markup -- #product-stage below replaces it with real photography
   of the same eight-plus product ideas, Toast-style. .rm-card and its
   children survive: they are the "live card" vocabulary (toast/pill/row/
   list/bar/big/btn-ui), and #product-stage's floating UI cards reuse them
   verbatim rather than inventing a second set of the same components.    */

/* the live card: a real piece of the product, in the product's own type */
.rm-card {
  position: absolute; z-index: 2; left: 10%; right: 10%; top: 12%;
  background: #FFFFFF; color: #000000; border-radius: 14px; padding: 12px 14px;
  font-family: var(--font-ui); font-size: 12px; line-height: 1.4; text-align: left;
  box-shadow: 0 30px 44px -20px rgba(0,0,0,.55), 0 1px 0 rgba(0,0,0,.06);
  transition: transform var(--d-3) var(--glide);
}
.room:hover .rm-card, .ps-card:hover .rm-card { transform: translateY(-6px); }
.rm-card b { display: block; font-weight: 700; }
.rm-card small { display: block; color: var(--ui-ink-3); font-size: 10.5px; line-height: 1.4; }
.rm-card .row { display: flex; justify-content: space-between; gap: 8px; align-items: baseline; }
.rm-card .mono { font-family: var(--font-mono); font-variant-numeric: tabular-nums; font-weight: 700; }
.rm-card .pill { display: inline-block; font-size: 10px; font-weight: 700; padding: 2px 7px; border-radius: 10px; background: var(--o-100); color: var(--o-800); white-space: nowrap; }
.rm-card .pill.ok { background: #E7F4EE; color: #0E5C40; }
.rm-card .pill.warn { background: #FBF1DC; color: #8A5C05; }
.rm-card .bar { display: block; height: 6px; border-radius: 3px; background: #F0EDE8; margin-top: 9px; overflow: hidden; }
.rm-card .bar i { display: block; height: 100%; width: var(--w, 60%); background: var(--o-500); border-radius: 3px; }
.rm-card .toast { display: flex; align-items: center; gap: 9px; padding: 8px 10px; border: 1px solid #E4E0DA; border-radius: 10px; margin-bottom: 10px; font-weight: 600; font-size: 11.5px; }
.rm-card .toast i { width: 24px; height: 24px; border-radius: 7px; background: var(--o-500); display: grid; place-items: center; font-style: normal; font-size: 12px; color: #000000; flex: none; }
.rm-card .toast b { display: block; }
.rm-card .big { display: block; font-family: var(--font-mono); font-size: 24px; font-weight: 700; letter-spacing: -.03em; line-height: 1; margin-top: 4px; }
.rm-card .list { display: grid; gap: 6px; }
.rm-card .list div { display: flex; justify-content: space-between; align-items: center; gap: 8px; }
.rm-card .ok-dot { width: 8px; height: 8px; border-radius: 50%; background: #1A7F5A; flex: none; }
.rm-card .btn-ui { display: inline-block; margin-top: 9px; font-size: 10.5px; font-weight: 700; padding: 6px 10px; border-radius: 8px; background: #000000; color: #F7F3EE; }
.rm-card .btn-ui.acc { background: var(--o-500); color: #000000; }

.room-go {
  position: absolute; top: 14px; right: 14px; z-index: 3; width: 42px; height: 42px; border-radius: 50%;
  display: grid; place-items: center; color: #FFFFFF;
  border: 1.5px solid rgba(255,255,255,.75); background: rgba(0,0,0,.18);
  opacity: 0; transform: translateY(-6px);
  transition: opacity var(--d-2) var(--glide), transform var(--d-2) var(--glide);
}
.room-go svg { width: 18px; height: 18px; }
.room:hover .room-go, .room:focus-visible .room-go,
.ps-card:hover .room-go, .ps-card:focus-visible .room-go { opacity: 1; transform: none; }
@media (hover: none) { .room-go { opacity: 1; transform: none; } }
.room-cap {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 2; display: block;
  padding: 34px 18px 22px; text-align: center;
  background: linear-gradient(to top, rgba(0,0,0,.36), rgba(0,0,0,0));
}
.room-cap b { display: block; font-family: var(--font-display); font-weight: 700; font-size: clamp(19px, 1.5vw, 24px); letter-spacing: -.02em; line-height: 1.15; }
.room-cap span { display: block; margin-top: 6px; font-size: var(--t-sm); line-height: 1.4; opacity: .94; }

/* a two-way switch in the same glass as the type picker */
.seg2 {
  position: relative; display: inline-grid; grid-auto-flow: column; grid-auto-columns: 1fr;
  padding: 5px; border-radius: 999px; flex: none;
  background: color-mix(in srgb, var(--surface) 55%, transparent);
  -webkit-backdrop-filter: blur(18px) saturate(1.7); backdrop-filter: blur(18px) saturate(1.7);
  border: 1px solid color-mix(in srgb, var(--ink) 9%, transparent);
  box-shadow: inset 0 1px 0 color-mix(in srgb, #FFFFFF 62%, transparent),
              0 10px 26px -14px color-mix(in srgb, var(--ink) 34%, transparent);
}
.seg2-thumb {
  position: absolute; top: 5px; left: 5px; height: calc(100% - 10px);
  /* --n defaults to 2 (the plain two-way switch); the highlight strip's
     six-tab .seg2 sets --n:6 inline and this is the only formula that has
     to change to grow the pill from a two-way toggle into an N-way one --
     translateX(100%) below is already relative to THIS box's own width, so
     moving "one segment" is correct at any N without touching that line. */
  width: calc((100% - 10px) / var(--n, 2));
  border-radius: 999px; background: var(--ink); display: block;
  transform: translateX(calc(var(--x, 0) * 100%)); transition: transform 460ms var(--snap);
  box-shadow: 0 2px 10px -3px color-mix(in srgb, var(--ink) 55%, transparent);
}
.seg2 button {
  position: relative; z-index: 1; font: inherit; font-size: var(--t-sm); font-weight: 600;
  padding: 10px 20px; border: 0; border-radius: 999px; background: transparent; color: var(--ink-2);
  cursor: pointer; white-space: nowrap; transition: color var(--d-2) var(--glide);
}
.seg2 button[aria-selected="true"] { color: var(--ground); }
.seg2 button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) {
  .seg2-thumb { transition: none; }
  .room, .rooms-grid.is-in .room { animation: none; opacity: 1; translate: 0 0; }
}

/* ---- the pocket -----------------------------------------------------------
   Apple's product pages pin one object and let the scroll turn it. Here the
   object is a phone drawn in CSS, so the screen is the real product UI and
   not a picture of one: the numbers tick, the tickets move, the bars grow.
   The section pins only when the document scrolls in its own viewport (a
   one-file build inside a sized frame does not) and plays itself otherwise. */
.ph { --ground: var(--n-900); background: var(--ground); overflow: clip; }
/* 150vh, not the 320vh this pin used to run over (200vh at the previous
   trim) -- the choreography is driven by the scroller's own height as a 0-1
   fraction (js/site.js's onScroll), so a shorter run plays the exact same
   three-pose animation over less scroll distance, not a different one.
   Pulled in again as part of Part B's page-height budget; the 860px pass
   below already used the same trick for narrower screens. */
.ph.pinned .ph-scroller { height: 108vh; }
.ph.pinned .ph-stage { position: sticky; top: 0; height: 100vh; display: grid; align-items: center; padding-block: 0; }
.ph-stage { padding-block: var(--s-6); }
.ph-in { display: grid; grid-template-columns: minmax(0, .9fr) minmax(0, 1.1fr); align-items: center; gap: clamp(20px, 4vw, 64px); }
@media (max-width: 860px) { .ph-in { grid-template-columns: 1fr; gap: var(--s-6); } .ph.pinned .ph-scroller { height: 112vh; } }
.ph-steps { position: relative; margin-top: var(--s-5); min-height: 11.5em; }
.ph-step { position: absolute; inset: 0 0 auto 0; opacity: 0; transform: translateY(14px); pointer-events: none;
           transition: opacity .55s var(--glide), transform .55s var(--glide); }
.ph-step.is-on { opacity: 1; transform: none; pointer-events: auto; }
.ph-step h2 { font-size: var(--t-2xl); letter-spacing: var(--tr-head); line-height: var(--lh-head); max-width: 13ch; text-wrap: balance; }
.ph-step p { margin-top: var(--s-4); font-size: var(--t-md); color: var(--ink-2); max-width: 38ch; }
.ph-dots { list-style: none; margin: var(--s-6) 0 0; padding: 0; display: flex; gap: 8px; }
.ph-dots li { width: 28px; height: 4px; border-radius: 2px; background: color-mix(in srgb, var(--bone) 18%, transparent); transition: background .4s var(--glide), width .4s var(--glide); }
.ph-dots li.is-on { background: var(--o-500); width: 44px; }
.ph.reduced .ph-steps { min-height: 0; }
.ph.reduced .ph-step { position: static; opacity: 1; transform: none; pointer-events: auto; margin-top: var(--s-5); }
.ph.reduced .ph-dots { display: none; }

/* the object: laid out at 393 x 852 and scaled into its box, so every measure
   inside it is a real point on a real phone */
/* perspective for `.ph-box`'s own entrance tilt (motion() part b,
   js/apple.js), the same reasoning as `.pad-rig`'s below: `.ph-box`'s own
   `perspective` still governs `#phone`'s pinned-scroll transform exactly as
   before, this is only for the wrapper's own rotateX. */
.ph-rig { display: grid; place-items: center; perspective: 1800px; }
.ph-box { --s: .78; position: relative; width: calc(393px * var(--s)); height: calc(852px * var(--s)); perspective: 1500px; }
/* #pocket's own phone also grows 6% on entrance (js/apple.js, motion() part
   b, a `scale()` layered on top of this box via inline style) -- .78 * 1.06
   only clears a 320px phone's column down to about 365px wide; narrower
   than that it ran past the section gutter on both sides, same class of
   bug as the kitchen chapter's iMac. .ph-trio's own smaller .62/.58 scales
   (below) were never close to that ceiling, so they are untouched. */
@media (max-width: 364px) { #pocket .ph-box { --s: .65; } }
/* absolutely centred on the box, because a grid row would size itself to the
   unscaled 852px and push the phone down by half the difference */
.phone {
  position: absolute; left: 50%; top: 50%; margin: -426px 0 0 -196.5px;
  width: 393px; height: 852px; transform-style: preserve-3d;
  transform: scale(var(--s)) rotateY(var(--ry, 0deg)) rotateX(var(--rx, 0deg)) translateY(var(--ty, 0px));
  will-change: transform;
}
.ph-frame {
  position: absolute; inset: 0; border-radius: 62px;
  background: linear-gradient(155deg, #EDEDF0 0%, #C3C3C9 28%, #8F8F96 52%, #B9B9BF 74%, #E3E3E7 100%);
  box-shadow: inset 0 0 0 1px rgba(0,0,0,.28), inset 0 0 0 3px rgba(255,255,255,.35), 0 60px 90px -30px rgba(0,0,0,.75);
}
.ph-glass { position: absolute; inset: 4px; border-radius: 58px; background: #000; overflow: hidden; }
/* screen glass reflection at 6% -- same recipe as the iMac/MacBook glass in
   css/modules.css, so every device family in the site reads as one glass. */
.ph-glass::after {
  content: ""; position: absolute; inset: 0; z-index: 5; pointer-events: none; border-radius: inherit;
  background: linear-gradient(122deg, rgba(255,255,255,.06) 0%, rgba(255,255,255,.02) 22%, transparent 42%);
}
.ph-screen { position: absolute; inset: 9px; border-radius: 50px; overflow: hidden; background: #F7F3EE; color: #000000; }
.ph-island { position: absolute; top: 12px; left: 50%; width: 126px; height: 37px; margin-left: -63px; border-radius: 20px; background: #000; z-index: 5; display: block; }
.ph-island::after { content: ""; position: absolute; right: 11px; top: 11px; width: 15px; height: 15px; border-radius: 50%; background: radial-gradient(circle at 40% 40%, #2B2B3C, #05050A 70%); }
.ph-status { position: absolute; top: 24px; left: 34px; right: 34px; display: flex; justify-content: space-between; align-items: center; z-index: 4;
             font-family: var(--font-ui); font-size: 17px; font-weight: 600; letter-spacing: -.01em; color: #000000; pointer-events: none; }
.ph-status .ph-icons { display: inline-flex; align-items: center; gap: 6px; }
.ph-home { position: absolute; bottom: 10px; left: 50%; width: 140px; height: 5px; margin-left: -70px; border-radius: 3px; background: rgba(0,0,0,.85); z-index: 4; display: block; }
.ph-back { position: absolute; inset: 0; border-radius: 62px; display: block; transform: translateZ(-45px); background: linear-gradient(155deg, #D9D9DE, #9A9AA1); }
/* each face is rotated so its FRONT points outward, then pushed back into the
   body; with backface-visibility hidden the inside of the shell never shows */
.ph-side { position: absolute; display: block; backface-visibility: hidden; background: linear-gradient(180deg, #A9A9B0, #7E7E86 50%, #B9B9BF); }
.ph-side.l { left: 0; top: 62px; bottom: 62px; width: 45px; transform-origin: left center; transform: translateZ(-45px) rotateY(-90deg); }
.ph-side.r { right: 0; top: 62px; bottom: 62px; width: 45px; transform-origin: right center; transform: translateZ(-45px) rotateY(90deg); }
.ph-side.t { top: 0; left: 62px; right: 62px; height: 45px; transform-origin: center top; transform: translateZ(-45px) rotateX(90deg); background: linear-gradient(90deg, #A9A9B0, #C9C9CF 50%, #8E8E96); }
.ph-side.b { bottom: 0; left: 62px; right: 62px; height: 45px; transform-origin: center bottom; transform: translateZ(-45px) rotateX(-90deg); background: linear-gradient(90deg, #A9A9B0, #C9C9CF 50%, #8E8E96); }
.ph-btn { position: absolute; width: 8px; border-radius: 3px; display: block; transform: translateZ(-22px); background: linear-gradient(90deg, #7C7C84, #C9C9CF 50%, #8B8B93); }
.ph-btn.act  { left: -7px; top: 176px; height: 36px; }
.ph-btn.vol1 { left: -7px; top: 236px; height: 66px; }
.ph-btn.vol2 { left: -7px; top: 316px; height: 66px; }
.ph-btn.pwr  { right: -7px; top: 262px; height: 106px; }

/* the screen: the product's own components at phone scale */
.ph-ui {
  position: absolute; inset: 0; font-family: var(--font-ui); font-size: 13px; line-height: 1.4;
  --ui-bg: #F7F3EE; --ui-card: #FFFFFF; --ui-sunk: #F0EDE8; --ui-line: #E4E0DA; --ui-ink: #000000;
  --ui-ink-2: #5C5A57; --ui-ink-3: #6E6D6B; --ui-acc: #FF6B3D; --ui-acc-sf: #FFF0E9;
}
.ph-view { position: absolute; inset: 0; padding: 70px 18px 30px; opacity: 0; transform: translateX(28px) scale(.98); pointer-events: none; overflow: hidden;
           transition: opacity .55s var(--glide), transform .55s var(--glide); }
.ph-view.is-on { opacity: 1; transform: none; pointer-events: auto; }
.ph-hd { display: flex; align-items: flex-start; justify-content: space-between; gap: 10px; margin-bottom: 14px; }
.ph-hd h3 { font-family: var(--font-display); font-weight: 800; font-size: 27px; letter-spacing: -.03em; line-height: 1.05; }
.ph-hd .sub { font-size: 12px; color: var(--ui-ink-3); margin-top: 5px; display: flex; align-items: center; gap: 6px; }
.ph-hd .sub .live { width: 7px; height: 7px; border-radius: 50%; background: #1A7F5A; animation: pulse 2s ease-in-out infinite; }
.ph-hd .ui-btn { font-size: 12px; padding: 7px 12px; border-radius: 9px; }
.ph-tiles { display: grid; gap: 10px; }
.ph-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.ph-ui .tile { border-radius: 18px; padding: 14px 16px; }
.ph-ui .tile-h b { font-size: 12px; }
.ph-ui .tile .fig { font-size: 40px; }
.ph-ui .tile.hero-tile .fig { font-size: 52px; }
.ph-ui .tile .fig.sm { font-size: 28px; }
.ph-ui .tile .cap { font-size: 11px; }
.ph-ui .delta { font-size: 11px; }
.ph-ui .bars { height: 84px; }
.ph-ui .bars rect.v { transform-box: fill-box; transform-origin: 50% 100%; transform: scaleY(0); transition: transform .7s var(--glide); transition-delay: calc(var(--i) * 45ms); }
.ph-view.is-on .bars rect.v { transform: scaleY(1); }
.ph-ui .spark { height: 64px; }
.ph-ui .spark path { stroke-dasharray: 600; stroke-dashoffset: 600; transition: stroke-dashoffset 1.4s var(--glide) .2s; }
.ph-view.is-on .spark path { stroke-dashoffset: 0; }
.ph-ui .rank { margin-top: 12px; gap: 8px; }
.ph-ui .rank > div { font-size: 12px; padding: 8px 10px; }
.ph-ui .rank i { transition: width .9s var(--glide) .15s; }
.ph-view:not(.is-on) .rank i { width: 0 !important; }
.ph-kds { display: grid; gap: 10px; }
.ph-ui .tkt { border-radius: 14px; transition: transform .5s var(--glide), opacity .5s var(--glide); }
.ph-ui .tkt .th b { font-size: 13.5px; } .ph-ui .tkt .th .ag { font-size: 12px; }
.ph-ui .tkt .tm { font-size: 10.5px; } .ph-ui .tkt li { font-size: 12.5px; } .ph-ui .tkt li em { font-size: 10.5px; }
.ph-ui .tkt .ta b { font-size: 11px; padding: 7px; }
.ph-view:not(.is-on) .tkt, .ph-ui .tkt.is-new { opacity: 0; transform: translateY(18px); }
.ph-view.is-on .tkt { transition-delay: calc(var(--i, 0) * 90ms); }
.ph-ui .tkt.is-bumped { opacity: 0; transform: translateX(70px); transition-delay: 0s; }
.ph-toast {
  position: absolute; left: 14px; right: 14px; top: 60px; z-index: 6;
  background: rgba(0,0,0,.93); color: #F7F3EE; border-radius: 16px; padding: 10px 12px;
  display: flex; gap: 10px; align-items: center; font-size: 12px; line-height: 1.3;
  transform: translateY(-160%); opacity: 0; transition: transform .5s var(--snap), opacity .4s var(--glide);
}
.ph-toast.is-in { transform: none; opacity: 1; }
.ph-toast i { width: 30px; height: 30px; border-radius: 9px; background: var(--o-500); flex: none; display: grid; place-items: center; font-style: normal; font-weight: 800; color: #000000; font-size: 16px; }
.ph-toast b { display: block; font-weight: 700; }
.ph-toast small { color: #B5B2AE; font-size: 11px; }

/* the line-up: three of them, fanned like a product shot, once they are seen */
/* the single-phone pinned animation above and this trio are both inside
   #pocket's own .wrap-wide, so nothing but this margin ever separated them
   -- give it the section's own rhythm (--sec-y), not a flat spacing token,
   so the trio reads as its own beat rather than crowding the animation it
   follows. .ph-trio-kicker gives it a line of its own too. */
.ph-trio-kicker { margin-top: var(--sec-y); text-align: center; }
@media (max-width: 720px) { .ph-trio-kicker { display: none; } }
.ph-trio { display: flex; justify-content: center; align-items: flex-end; gap: clamp(0px, 2vw, 34px); margin-top: var(--s-7); perspective: 1800px; }
.ph-trio .ph-box { --s: .62; perspective: none; flex: none; }
.ph-trio .ph-box.side { --s: .58; }
.ph-trio .phone { transition: transform 1.1s var(--glide); }
.ph-trio .pose-l .phone { --ry: 26deg; --rx: 2deg; }
.ph-trio .pose-r .phone { --ry: -26deg; --rx: 2deg; }
.ph-trio:not(.is-in) .pose-l .phone { --ry: 0deg; transform: scale(var(--s)) translateX(58%); }
.ph-trio:not(.is-in) .pose-r .phone { --ry: 0deg; transform: scale(var(--s)) translateX(-58%); }
.ph-trio:not(.is-in) .pose-c .phone { transform: scale(var(--s)) translateY(24px); }
.ph-trio .ph-screen .ph-ui { position: absolute; }
/* Below 720px the #phone stage above already plays every pose (kitchen,
   live, analyse) on its own timer -- see js/site.js's 'auto' mode -- so the
   trio line-up would just be the same three screens rendered a second time,
   statically, right underneath it. The old rule only hid the two side
   poses, leaving the centre .pose-c phone as a visible duplicate; the whole
   line-up hides here instead, one phone per chapter. */
@media (max-width: 720px) { .ph-trio { display: none; } }

/* Round 10 fix, point 4. On a phone the pocket device is drawn at close to
   the full column width, and each view's content stopped about two thirds of
   the way down the screen -- so the last third rendered as a tall blank cream
   slab, which read in the 390px capture as a SECOND, empty phone stacked
   above the real one. There is only ever one phone here (the .ph-trio line-up
   is display:none from 720px down, and #pocket itself holds a single
   .ph-box), so the fix is not to remove a frame: it is to let each view fill
   the screen it is drawn on. The grid rows take the slack and each tile
   spreads its own contents, rather than one chart being stretched out of
   proportion. Desktop, where the phone is small enough for the rest of the
   screen to read as screen, is untouched. */
@media (max-width: 900px) {
  .ph-view { display: flex; flex-direction: column; padding-bottom: 58px; }
  .ph-tiles, .ph-kds { flex: 1 1 auto; min-height: 0; gap: 14px; }
  .v-live .ph-tiles    { grid-template-rows: minmax(0, 1fr) auto minmax(0, 1fr); }
  .v-analyse .ph-tiles { grid-template-rows: minmax(0, 1fr) auto minmax(0, 1fr); }
  .v-kitchen .ph-kds   { grid-template-rows: repeat(3, minmax(0, 1fr)); }
  /* header stays pinned to the top of its tile, the number and its caption
     sink to the foot -- the tile grows the way a real card grows, instead of
     opening a hole in its middle. */
  .ph-tiles > .tile { display: flex; flex-direction: column; }
  .ph-tiles > .tile > .fig, .ph-tiles > .tile > .bars,
  .ph-tiles > .tile > .spark, .ph-tiles > .tile > .rank { margin-top: auto; }
  .ph-kds > .tkt { display: flex; flex-direction: column; }
  .ph-kds > .tkt > ul { margin-top: auto; }
  /* the two drawn charts carry a little of the extra height themselves, so
     the tiles they sit in do not open a gap above them. */
  .ph-ui .bars { height: 128px; }
  .ph-ui .spark { height: 92px; }
}
.ph-trio-cap { text-align: center; margin-top: var(--s-6); color: var(--ink-2); font-size: var(--t-sm); }
.ph-trio-cap a { color: var(--ink); text-decoration: underline; text-underline-offset: 3px; }
@media (prefers-reduced-motion: reduce) {
  .ph-view, .ph-step, .ph-ui .tkt, .ph-ui .rank i, .ph-ui .bars rect.v, .ph-ui .spark path, .ph-trio .phone { transition: none; }
  .ph-ui .bars rect.v { transform: none; } .ph-ui .spark path { stroke-dashoffset: 0; }
}


/* ---- the mega menu is glass -----------------------------------------------
   The pill it hangs from is already translucent; a solid white sheet under it
   read as a different material. Same recipe as the pill and the type rail:
   a mostly-opaque white that still samples what is behind it, a hairline,
   and a light catch along the top edge. */
.nav-menu {
  left: clamp(10px, 2vw, 20px); right: clamp(10px, 2vw, 20px);
  border-radius: 24px; border: 1px solid rgba(255,255,255,.62); border-top-color: rgba(255,255,255,.9);
  background: color-mix(in srgb, #FFFFFF 78%, transparent);
  -webkit-backdrop-filter: blur(28px) saturate(1.8); backdrop-filter: blur(28px) saturate(1.8);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.85), inset 0 -1px 0 rgba(0,0,0,.05),
              0 30px 70px -24px rgba(0,0,0,.5), 0 8px 24px -12px rgba(0,0,0,.25);
}
.mm-card { background: rgba(255,255,255,.55); box-shadow: inset 0 0 0 1px rgba(255,255,255,.55); }
.mm-card:hover { background: rgba(255,255,255,.9); }
.mm-feature { background: color-mix(in srgb, var(--n-900) 92%, transparent); }

/* ---- the tablet -----------------------------------------------------------
   The three moments used to sit in a flat black frame. They are now on a
   tablet drawn the way the phone is: laid out at 740 x 520 and scaled, a
   space-grey shell with real thickness, the product UI on its screen. The
   scroll turns it (data-turn = rotateY from,to, rotateX from,to as the
   section passes) and the pointer adds a little on top. */
/* perspective for `.pad-box`'s OWN entrance tilt (motion() part c,
   js/apple.js) -- `.pad-box`'s own `perspective` below governs `.pad`'s
   3D transform same as always; this is a second, outer perspective context
   for the wrapper's rotateX so the level-out reads as a tilt, not a flat
   vertical squash. */
.pad-rig { background: none; box-shadow: none; padding: 0; border-radius: 0; display: grid; justify-items: center; perspective: 1800px; }
/* fluid, not a breakpoint step: at its native 740px this tablet mockup
   overflows a phone-width column outright (740px > any mobile viewport), and
   the fixed-width `.pad` it wraps has no intrinsic size a grid track can
   shrink it against -- `--s` has to shrink first, or `.moment-in`'s single
   mobile column grows to fit it and the whole page gains a horizontal
   scrollbar. calc(88vw / 740px) is a unitless ratio (same-dimension
   division), clamped so the mockup never grows past its drawn size on a wide
   viewport and never shrinks below readable on a narrow one. */
@property --pry { syntax: '<angle>'; inherits: false; initial-value: 0deg; }
@property --prx { syntax: '<angle>'; inherits: false; initial-value: 0deg; }

.pad-box {
  --s: clamp(.4, tan(atan2(88vw, 740px)), 1); position: relative; width: calc(740px * var(--s)); height: calc(520px * var(--s)); perspective: 1600px;
  /* no background, border or box-shadow here: this box only sizes and frames
     the 3D perspective for `.pad` below. A shadow on THIS untransformed
     wrapper draws a plain rectangle behind the tilted device, which is
     exactly the "visible container" the tilt is supposed to avoid -- the
     shadow lives on `.pad` itself instead (a drop-shadow filter, which
     follows the element's own rendered, rotated silhouette rather than its
     untransformed bounding box). */
}
.pad {
  position: absolute; left: 50%; top: 50%; width: 740px; height: 520px; margin: -260px 0 0 -370px;
  transform-style: preserve-3d; will-change: transform;
  transform: scale(var(--s)) rotateY(calc(var(--sry, 0deg) + var(--pry, 0deg))) rotateX(calc(var(--srx, 0deg) + var(--prx, 0deg)));
  /* Round 53: the POINTER's two angles glide; the SCROLL's two do not.
     Round 48 made every frame two stacked elements, a preserve-3d shell and
     a flat face, that must stay exactly on top of each other. Both are
     `[data-turn]`, so both get `--sry`/`--srx` written to them every scroll
     tick -- but they are separate elements with separate writes, and a
     half-second `transition: transform` turns any single frame where the two
     disagree into half a second of two visibly different poses. That is the
     doubled tablet photographed in Safari: the body behind sits up and to the
     left of the face and is turned less far, which is a pose mid-transition,
     not a projection difference. Chrome happens never to split the writes
     across a frame; Safari does, after a tab switch or on first load.
     Transitioning only `--pry`/`--prx` keeps the pointer parallax exactly as
     it was and makes a scroll-pose disagreement last one frame instead of
     five hundred milliseconds. @property is what makes an angle custom
     property animatable; without it (Safari below 16.4) nothing transitions
     and the parallax simply snaps, which is the safe way to fail. */
  transition: --pry .5s var(--glide), --prx .5s var(--glide);
  /* the entrance js/apple.js's motion() (part c) plays on this box: lifts
     24px and levels from a 6° tilt as it scrolls in, --pad-lift-shadow
     riding 0→1 alongside so the contact shadow grows in step rather than
     just appearing at full strength. A filter, not a box-shadow, so it
     traces the tilted device's real silhouette instead of its flat box. */
  filter: drop-shadow(0 calc(var(--pad-lift-shadow, 0) * 34px) calc(var(--pad-lift-shadow, 0) * 40px) rgba(0,0,0, calc(var(--pad-lift-shadow, 0) * .22)));
}
.pad-frame {
  position: absolute; inset: 0; border-radius: 28px;
  background: linear-gradient(150deg, #5A5A5F 0%, #303033 38%, #232326 68%, #4A4A4F 100%);
  box-shadow: inset 0 0 0 1px rgba(255,255,255,.18), inset 0 0 0 2.5px rgba(0,0,0,.35), 0 50px 80px -30px rgba(0,0,0,.6);
}
.pad-glass { position: absolute; inset: 4px; border-radius: 25px; background: #000; overflow: hidden; }
.pad-glass::after {
  content: ""; position: absolute; inset: 0; z-index: 5; pointer-events: none; border-radius: inherit;
  background: linear-gradient(122deg, rgba(255,255,255,.06) 0%, rgba(255,255,255,.02) 22%, transparent 42%);
}
.pad-screen { position: absolute; inset: 20px; border-radius: 10px; overflow: hidden; background: var(--bone); }
.pad-screen .inner { position: absolute; inset: 0; border-radius: 0; overflow: hidden; container-type: inline-size; background: var(--bone); }

/* ---- screen-on: a brief power-up flourish the first time a device screen
   scrolls into view (js/site.js, IntersectionObserver, once) -- a horizontal
   line expands to the full frame, bright, then settles to normal brightness.
   A class toggle plus this one keyframe; nothing runs per frame. Both
   .pad-screen (the three moment tablets) and .sc-stage (the dashboard) start
   in their normal, fully-visible state regardless of script, so a visitor
   without JS or before the observer fires never sees a blank screen. */
@keyframes screenOn {
  0%   { transform: scaleY(.02); filter: brightness(2.2); opacity: .7; }
  39%  { transform: scaleY(1);   filter: brightness(2.2); opacity: 1; }
  100% { transform: scaleY(1);   filter: brightness(1);   opacity: 1; }
}
.pad-screen.screen-on, .sc-stage.screen-on { animation: screenOn .36s var(--glide) both; }
/* round 37: a screen below the fold waits in the keyframe's first frame */
.pad-screen.screen-wait, .sc-stage.screen-wait { transform: scaleY(.02); filter: brightness(2.2); opacity: .7; }
@media (prefers-reduced-motion: reduce) {
  .pad-screen.screen-on, .sc-stage.screen-on { animation: none; }
  .pad-screen.screen-wait, .sc-stage.screen-wait { transform: none; filter: none; opacity: 1; }
}
.pad-cam { position: absolute; top: 7px; left: 50%; width: 7px; height: 7px; margin-left: -3.5px; border-radius: 50%; display: block; z-index: 3;
           background: radial-gradient(circle at 40% 40%, #3A3A4A, #050508 70%); }
.pad-back { position: absolute; inset: 0; border-radius: 28px; display: block; transform: translateZ(-18px); background: linear-gradient(150deg, #4E4E53, #2B2B2E); }
.pad-side { position: absolute; display: block; backface-visibility: hidden; background: linear-gradient(180deg, #4A4A4F, #2A2A2E 50%, #55555A); }
.pad-side.l { left: 0; top: 28px; bottom: 28px; width: 18px; transform-origin: left center;   transform: translateZ(-18px) rotateY(-90deg); }
.pad-side.r { right: 0; top: 28px; bottom: 28px; width: 18px; transform-origin: right center; transform: translateZ(-18px) rotateY(90deg); }
.pad-side.t { top: 0; left: 28px; right: 28px; height: 18px; transform-origin: center top;    transform: translateZ(-18px) rotateX(90deg);  background: linear-gradient(90deg, #4A4A4F, #5C5C61 50%, #3A3A3E); }
.pad-side.b { bottom: 0; left: 28px; right: 28px; height: 18px; transform-origin: center bottom; transform: translateZ(-18px) rotateX(-90deg); background: linear-gradient(90deg, #4A4A4F, #5C5C61 50%, #3A3A3E); }
.pad-btn { position: absolute; display: block; border-radius: 2px; transform: translateZ(-9px); background: linear-gradient(90deg, #3A3A3E, #6A6A70 50%, #3A3A3E); }
.pad-btn.pwr { top: -4px; left: 34px; width: 34px; height: 4px; }
.pad-btn.vol { top: -4px; left: 82px; width: 56px; height: 4px; }

/* ---- the bill is paper ------------------------------------------------------
   It hangs in the same space as the devices: the scroll turns it, the pointer
   nudges it, and it prints out when it first comes into view. Each line
   carries its own small rotateX on a staggered clock, which is what makes the
   surface ripple like paper rather than tilt like a card. The mask that cuts
   the torn edges flattens 3D children, so the ripple gets its perspective from
   the receipt itself, not from the rig. */
.bill-rig { perspective: 1400px; }
.bill {
  position: relative; transform-style: preserve-3d;
  transform: rotateY(calc(var(--sry, -14deg) + var(--pry, 0deg))) rotateX(calc(var(--srx, 4deg) + var(--prx, 0deg)));
  transition: transform .45s var(--glide);
}
.bill::after {
  content: ""; position: absolute; left: 8%; right: -4%; top: 10%; bottom: -5%; z-index: -1;
  background: rgba(0,0,0,.30); filter: blur(28px); transform: translateZ(-80px); border-radius: 14px;
}
.receipt { perspective: 900px; }
.receipt > * { transform-origin: 50% 0; animation: paperWave 5.2s ease-in-out infinite; animation-delay: calc(var(--i, 0) * -0.22s); }
/* Same rule as the cloud diagram's wires above: a hover is a person looking
   at this one thing, and the paper should hold still for them. */
.bill:hover .receipt > * { animation-play-state: paused; }
@keyframes paperWave { 0%, 100% { transform: rotateX(0deg) translateZ(0); } 50% { transform: rotateX(4.5deg) translateZ(6px); } }
.rv-armed .bill-rig[data-reveal]:not(.is-in) .receipt { clip-path: inset(-2% -2% 100% -2%); }
.js .bill-rig.is-in .receipt { animation: printOut 1.9s cubic-bezier(.2,.8,.2,1) forwards; }
@keyframes printOut { from { clip-path: inset(-2% -2% 100% -2%); transform: translateY(-36px); } to { clip-path: inset(-2% -2% -2% -2%); transform: none; } }
@media (prefers-reduced-motion: reduce) {
  .pad, .bill { transition: none; transform: scale(var(--s, 1)); }
  .receipt > * { animation: none; }
  .rv-armed .bill-rig[data-reveal] .receipt { clip-path: none; animation: none; }
}

/* ---- the key, pressed ------------------------------------------------------ */
.ctrl-key { display: block; width: clamp(130px, 14vw, 210px); height: auto; margin: 0 auto var(--s-3); filter: drop-shadow(0 22px 30px rgba(0,0,0,.5)); }

/* the dashboards move: what the live routine in js/site.js animates */
.donut circle { transition: stroke-dasharray .8s var(--glide), stroke-dashoffset .8s var(--glide); }
.rank i { transition: width .8s var(--glide); }


/* ---- the floating field ------------------------------------------------------
   Parker Studio's Picco hero, in delEat's objects: a saturated field with the
   things at several depths drifting across it and over the words. Near things
   are large, fast and sharp and pass in front of the headline; far things are
   small, slow, soft and pass behind it. Two animations that do not fight: the
   drift is on `transform`, the bob on `translate`; the pointer parallax moves
   `left` and `top` so it can coexist with both. */
.float { position: relative; overflow: hidden; padding: 0; min-height: clamp(520px, 56vw, 760px); display: grid; align-items: center; }
.float-in { position: relative; z-index: 6; pointer-events: none; text-align: center; padding-block: clamp(56px, 8vw, 120px); }
.float-in .eyebrow { justify-content: center; }
.float-in h2 { max-width: 20ch; margin-inline: auto; text-wrap: balance; }
.float-field { position: absolute; inset: 0; z-index: 1; --px: 0px; --py: 0px; container-type: size; }
.fo {
  position: absolute; top: calc(var(--y) + var(--py) * var(--z)); left: calc(var(--px) * var(--z));
  width: min(var(--w), 36cqw); height: auto; will-change: transform;
  z-index: calc(var(--z) * 10 + 1);
  animation: foDrift var(--dur) linear infinite var(--delay), foBob calc(5s + var(--z) * 3s) ease-in-out infinite;
}
.fo.rev { animation-direction: reverse, normal; }
.fo.mid { opacity: .92; }
.fo.far { opacity: .78; filter: blur(1.2px); }
@keyframes foDrift {
  from { transform: translate3d(calc(-1 * var(--w) - 6cqw), 0, 0) rotate(calc(-1 * var(--rot))); }
  to   { transform: translate3d(106cqw, calc(var(--z) * -4cqh), 0) rotate(var(--rot)); }
}
@keyframes foBob { 0%, 100% { translate: 0 0; } 50% { translate: 0 -14px; } }
@media (max-width: 720px) { .fo { width: min(var(--w), 44cqw); } .fo.far { display: none; } }
@media (prefers-reduced-motion: reduce) { .fo { animation: none; left: calc(var(--y) * 1.1); } }

/* the till tiles carry photographs now */
.dish .ph img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block; }
.dish .ph .veg { z-index: 2; }
.dish .ph::after { z-index: 1; background: linear-gradient(to top, rgba(0,0,0,.18), transparent 60%); }

/* clean cut-outs stand a little larger in their rooms */
.rm-obj { width: 50%; bottom: 27%; }
.rm-obj.dock { width: 74%; }
.rm-obj.dock img { width: 36%; margin-inline: -3%; }


/* ---- the machine is an object now -----------------------------------------
   Six boxes with real faces, laid out at 880 x 760 and scaled into the rig:
   monitor, stand, system unit, keyboard, mouse and a cord. The screen is the
   monitor's front face, so the live dashboard sits IN the tube rather than on
   a plate; the rig's pointer tilt turns the whole object and the depth of the
   tube is what you see. Beige plastic with a light from the upper left: tops
   lightest, right sides darkest. The photographed plate survives behind the
   switch. */
.m3 {
  --m-front: #D9CCAA; --m-top: #E6DCBF; --m-left: #CBBE9C; --m-right: #B9AC8B; --m-back: #A99C7C; --m-bottom: #9E9174;
  --m-dark: #2A2724; --m-key: #E4D9BC; --m-key-edge: #B7AA88; --m-led: #FF6B3D;
  position: absolute; left: 50%; top: 50%; width: 880px; height: 760px; margin: -380px 0 0 -440px;
  transform-style: preserve-3d; transform: scale(var(--ms, .7)); pointer-events: none;
}
.machine[data-machine="3d"] { aspect-ratio: 880 / 760; }
.machine[data-machine="3d"] .machine-plate { display: none; }
.machine:not([data-machine="3d"]) .m3 { display: none; }
.machine[data-machine="3d"]::after { left: 6%; right: 4%; bottom: 0; height: 9%; }
.box {
  position: absolute; left: 50%; top: 50%; width: var(--w); height: var(--h);
  margin: calc(var(--h) / -2) 0 0 calc(var(--w) / -2); transform-style: preserve-3d;
  transform: translate3d(var(--x), var(--y), var(--z)) rotateX(var(--tilt, 0deg));
}
.f { position: absolute; left: 50%; top: 50%; backface-visibility: hidden; background: var(--c); box-shadow: inset 0 0 0 1px rgba(0,0,0,.05); }
.f.front, .f.back  { width: var(--w); height: var(--h); margin: calc(var(--h) / -2) 0 0 calc(var(--w) / -2); }
.f.left,  .f.right { width: var(--d); height: var(--h); margin: calc(var(--h) / -2) 0 0 calc(var(--d) / -2); }
.f.top,   .f.bottom{ width: var(--w); height: var(--d); margin: calc(var(--d) / -2) 0 0 calc(var(--w) / -2); }
.f.front  { --c: var(--m-front);  transform: translateZ(calc(var(--d) / 2)); background: linear-gradient(180deg, color-mix(in srgb, var(--c) 90%, #FFFFFF) 0%, var(--c) 45%, color-mix(in srgb, var(--c) 86%, #000000) 100%); }
.f.back   { --c: var(--m-back);   transform: rotateY(180deg) translateZ(calc(var(--d) / 2)); }
.f.right  { --c: var(--m-right);  transform: rotateY(90deg)  translateZ(calc(var(--w) / 2)); background: linear-gradient(90deg, var(--c), color-mix(in srgb, var(--c) 80%, #000000)); }
.f.left   { --c: var(--m-left);   transform: rotateY(-90deg) translateZ(calc(var(--w) / 2)); background: linear-gradient(90deg, color-mix(in srgb, var(--c) 92%, #FFFFFF), var(--c)); }
.f.top    { --c: var(--m-top);    transform: rotateX(90deg)  translateZ(calc(var(--h) / 2)); }
.f.bottom { --c: var(--m-bottom); transform: rotateX(-90deg) translateZ(calc(var(--h) / 2)); }
/* the monitor */
.mon .f.front, .mon .f.back { border-radius: 16px; }
.mon .f.top { background: repeating-linear-gradient(90deg, transparent 0 18px, rgba(0,0,0,.13) 18px 21px) 0 30px / 100% 230px no-repeat, var(--m-top); }
.m3-screen {
  position: absolute; left: 52px; top: 40px; width: 416px; height: 312px; border-radius: 18px; overflow: hidden; background: #101010;
  box-shadow: inset 0 0 0 7px #C7BA98, inset 0 0 0 8px rgba(0,0,0,.4), inset 0 12px 26px rgba(0,0,0,.6);
}
.m3-screen .crt { left: 9px; top: 9px; width: calc(100% - 18px); height: calc(100% - 18px); transform: none; }
.mon .badge { position: absolute; left: 60px; bottom: 24px; width: 58px; height: 9px; border-radius: 2px; background: rgba(0,0,0,.22); }
.mon .led { position: absolute; right: 64px; bottom: 26px; width: 9px; height: 9px; border-radius: 50%; background: var(--m-led); box-shadow: 0 0 9px var(--m-led); }
/* the system unit */
.unit .f.front { border-radius: 8px; }
.unit .grille { position: absolute; left: 56px; top: 44px; width: 210px; height: 62px; border-radius: 4px; background: repeating-linear-gradient(180deg, rgba(0,0,0,.2) 0 2px, transparent 2px 7px); }
.unit .btn { position: absolute; left: 306px; top: 58px; width: 44px; height: 30px; border-radius: 5px; background: var(--m-left); box-shadow: 0 3px 0 var(--m-right), inset 0 1px 0 rgba(255,255,255,.45); }
.unit .led { position: absolute; left: 372px; top: 70px; width: 8px; height: 8px; border-radius: 50%; background: var(--m-led); box-shadow: 0 0 8px var(--m-led); }
.unit .slot { position: absolute; left: 470px; top: 62px; width: 196px; height: 18px; border-radius: 9px; background: var(--m-dark); box-shadow: inset 0 4px 8px rgba(0,0,0,.65), 0 1px 0 rgba(255,255,255,.35); }
.unit .slot::after { content: ""; position: absolute; right: 12px; top: 5px; width: 26px; height: 8px; border-radius: 4px; background: var(--m-right); }
/* the stand */
.stand .f { --c: var(--m-left); }
.stand .f.top { --c: var(--m-top); }
/* the keyboard */
.kbd .f.front { border-radius: 4px; }
.kbd .f.top { background: var(--m-front); }
.kbd .k { position: absolute; display: block; border-radius: 5px; background: var(--m-key); box-shadow: 0 3px 0 var(--m-key-edge), inset 0 1px 0 rgba(255,255,255,.55); }
.kbd .k.dk { background: var(--m-dark); box-shadow: 0 3px 0 #000000; color: #F7F3EE; font: 700 9px/30px var(--font-ui); text-align: center; letter-spacing: -.01em; }
/* the mouse */
.mouse .f.front, .mouse .f.top { border-radius: 30px 30px 10px 10px; }
.mouse .f.top { background: linear-gradient(90deg, var(--m-top) 49%, var(--m-front) 51%); }
.mouse .f.left, .mouse .f.right { border-radius: 8px; }
.cord { position: absolute; left: 50%; top: 50%; width: 250px; height: 3px; border-radius: 2px; background: #8A7D62; transform: translate3d(220px, 294px, 280px) rotateY(-24deg) rotateX(90deg); }
@media (prefers-reduced-motion: reduce) { .machine { transform: none; } }


/* ---- the sign-in and trial pages carry the field ---------------------------
   Their right half used to be an orange band; the site now keeps exactly two
   section grounds (--bone and charcoal), so this one is charcoal too, same
   as `.lg-promo`'s own default ground on the sign-in page. Kept as its own
   selector rather than folded into plain `.lg-promo` because the layout is
   different: a bottom-pinned glass card instead of a centred carousel. */
.lg-promo.st-promo {
  position: relative; overflow: hidden; display: grid; align-items: end; min-height: 62vh;
  padding: clamp(24px, 4vw, 52px);
  background: radial-gradient(78% 62% at 68% 22%, var(--n-800) 0%, transparent 62%),
              linear-gradient(168deg, var(--n-900) 0%, var(--n-950) 100%);
}
.lg-promo .float-field { position: absolute; inset: 0; }
.lg-promo .fo { width: min(var(--w) * .82, 34cqw); }
.lg-card {
  position: relative; z-index: 12; max-width: 460px; padding: clamp(20px, 2.6vw, 30px); border-radius: 22px; color: var(--n-900);
  background: color-mix(in srgb, #FFFFFF 66%, transparent);
  -webkit-backdrop-filter: blur(22px) saturate(1.6); backdrop-filter: blur(22px) saturate(1.6);
  border: 1px solid rgba(255,255,255,.72); box-shadow: inset 0 1px 0 rgba(255,255,255,.85), 0 30px 60px -30px rgba(0,0,0,.45);
}
.lg-card .lg-eyebrow { color: var(--o-800); }
.lg-card h2 { font-family: var(--font-display); font-weight: 800; font-size: clamp(22px, 2.4vw, 30px); letter-spacing: var(--tr-tight); line-height: 1.1; margin-top: 8px; color: var(--n-900); }
.lg-card > p { margin-top: 10px; font-size: var(--t-sm); color: var(--n-700); line-height: 1.5; }
.lg-card ul { list-style: none; margin: 14px 0 0; padding: 0; display: grid; gap: 10px; }
.lg-card li { display: grid; gap: 2px; }
.lg-card li b { font-size: var(--t-sm); font-weight: 700; color: var(--n-900); }
.lg-card li span { font-size: var(--t-sm); color: var(--n-600); line-height: 1.45; }
.lg-card .st-note { margin-top: 14px; color: var(--n-600); font-size: var(--t-xs); }
.lg-links { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 14px; }
.lg-chip { font-size: var(--t-xs); font-weight: 600; padding: 7px 11px; border-radius: 999px; background: var(--n-900); color: var(--bone); }
.lg-chip:hover { background: var(--n-800); }
/* the trial form, tidied: the plans sit three across, the groups are numbered */
.st-step { display: flex; align-items: center; gap: 10px; margin: var(--s-5) 0 var(--s-3); font-family: var(--font-mono); font-size: var(--t-xs); font-weight: 600; letter-spacing: var(--tr-caps); text-transform: uppercase; color: var(--ink-3); }
.st-step i { font-style: normal; display: inline-grid; place-items: center; width: 22px; height: 22px; border-radius: 50%; background: var(--o-500); color: var(--n-900); font-size: 10px; }
.st-step:first-of-type { margin-top: var(--s-4); }
.st-plans { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; margin: 0 0 var(--s-4); }
.st-plans legend { grid-column: 1 / -1; }
.st-plan span { display: grid; gap: 4px; align-content: start; padding: 12px 12px 13px; height: 100%; }
.st-plan span em { justify-self: start; }
.st-plan span small { grid-column: auto; }
@media (max-width: 560px) { .st-plans { grid-template-columns: 1fr; } }
.lg-fields.st-fields { margin-top: var(--s-4); }


/* the machine sits a little smaller and higher in its rig, so the keyboard
   and the mouse stay inside the column at every width */
.m3 { transform: scale(calc(var(--ms, .7) * .78)) translate(-8%, -9%); }
/* plan cards: one column inside each card, the price on its own line */
.st-plan span { grid-template-columns: 1fr; }
.st-plan span em { font-size: var(--t-sm); }
.st-plan span em i { display: block; font-size: var(--t-xs); }


/* ---- the menu blurs for real --------------------------------------------------
   An element with backdrop-filter is the backdrop root for everything inside
   it, so the menu, which lives inside the pill, was blurring the pill's own
   (empty) contents and letting the hero show through sharp. While a menu is
   open the pill gives up its filter and the menu samples the page. */
.nav-in:has(.has-menu:hover), .nav-in:has(.has-menu:focus-within) {
  -webkit-backdrop-filter: none; backdrop-filter: none;
  background: color-mix(in srgb, var(--ground) 94%, transparent);
}
.nav-menu {
  background: color-mix(in srgb, #FFFFFF 86%, transparent);
  -webkit-backdrop-filter: blur(34px) saturate(1.9); backdrop-filter: blur(34px) saturate(1.9);
}
.mm-card { background: rgba(255,255,255,.62); }

/* ---- the field's words sit on frosted glass ----------------------------------
   Every object now passes behind the plate; the near ones are still large and
   fast, they just do not cross the words. */
.float-in { z-index: 12; }
.float-plate {
  display: inline-block; padding: clamp(22px, 3vw, 40px) clamp(26px, 4vw, 56px); border-radius: 30px;
  background: color-mix(in srgb, var(--hero-mid) 42%, transparent);
  -webkit-backdrop-filter: blur(22px) saturate(1.4); backdrop-filter: blur(22px) saturate(1.4);
  border: 1px solid color-mix(in srgb, #FFFFFF 28%, transparent);
  box-shadow: inset 0 1px 0 color-mix(in srgb, #FFFFFF 38%, transparent), 0 30px 60px -30px rgba(58,16,4,.5);
}
.float-plate h2 { text-shadow: 0 1px 0 color-mix(in srgb, var(--hero-mid) 60%, transparent); }
.fo { z-index: calc(var(--z) * 5 + 1); }

/* the clean cut-outs are not square, so every icon slot fits them inside */
.pic-ic, .sv-ic, .modcard-ic, .rel-ic, .mh-icon, .ms-card-ic, .core-ic, .cm-ic img, .rel-ic { object-fit: contain; }


/* ---- the machine, as a model --------------------------------------------------
   A WebGL canvas fills the rig and the CRT overlay is projected onto the glass
   by js/machine3d.js. The rig itself no longer turns in CSS: the model does. */
.machine[data-machine="3d"] { transform: none; aspect-ratio: 1000 / 900; }
.m3d { position: absolute; inset: 0; }
.m3d canvas { display: block; width: 100%; height: 100%; }
.m3d-screen { position: absolute; left: 0; top: 0; width: 1000px; height: 800px; transform-origin: 0 0; will-change: transform; overflow: hidden; }
.m3d-screen .crt { position: absolute; inset: 0; width: auto; height: auto; transform: none; }
.machine:not([data-machine="3d"]) .m3d { display: none; }
.machine[data-machine="3d"]:not([data-ready]) .m3d-screen { opacity: 0; }

/* light fills (ceramic, silver, paper) sit close to chartreuse in lightness and
   read as holes there; a soft contact shadow separates every object from any
   ground, and reads as depth on orange too */
.fo { filter: drop-shadow(0 12px 14px rgba(58,16,4,.30)); }
.fo.far { filter: blur(1.2px) drop-shadow(0 8px 10px rgba(58,16,4,.24)); }


/* ---- the machine, in parts --------------------------------------------------
   Four drawings in the Picco idiom, one per part, stacked into the machine and
   each reacting to the pointer on its own: the pointer tilt on the rig
   (--ry / --rx) becomes a parallax shift and a small turn per part, scaled by
   that part's depth, so the mouse moves most and the monitor least. The live
   dashboard sits on the monitor's glass and moves with it. */
.machine[data-machine="parts"] { transform: none; aspect-ratio: 1100 / 938; }
.machine:not([data-machine="parts"]) .parts { display: none; }
.machine[data-machine="parts"] .machine-plate { display: none; }
.parts { position: absolute; inset: 0; perspective: 1400px; }
.part {
  position: absolute; left: var(--x); top: var(--y); width: var(--w); display: block;
  transform: translate(calc(var(--ry, 0deg) * var(--d) * 1.6px / 1deg), calc(var(--rx, 0deg) * var(--d) * -1.3px / 1deg))
             rotateY(calc(var(--ry, 0deg) * var(--d) * .6)) rotateX(calc(var(--rx, 0deg) * var(--d) * .6));
  transition: transform .14s linear;
  filter: drop-shadow(0 24px 24px rgba(58,16,4,.32));
}
.part img { display: block; width: 100%; height: auto; }
img.part { height: auto; }
.p-unit { z-index: 1; } .p-mon { z-index: 2; } .p-kbd { z-index: 3; } .p-mouse { z-index: 4; }
/* The glass is a tilted quad in this drawing, not a rectangle. `.p-screen`
   is laid out as a flat 1000 x 916 box (916 = the quad's own average
   height/width, at 1000 wide) and js/site.js projects that box onto the
   four measured corners with a matrix3d, from `data-glass` on `.p-mon` --
   see the projection helper next to `const monEl` in js/site.js. */
.p-screen {
  position: absolute; left: 0; top: 0; width: 1000px; height: 916px;
  overflow: hidden; border-radius: 24px / 34px; background: #101010;
  transform-origin: 0 0;
}
.p-screen .crt { position: absolute; inset: 0; width: auto; height: auto; transform: none; }
@keyframes partBob { 0%, 100% { translate: 0 0; } 50% { translate: 0 -7px; } }
/* The very slow levitation once a part has settled: a degree of rotate
   either way, phased per part by --dl so the four never turn in step.
   Starts and ends at 0deg so the handoff from partIn is seamless. */
@keyframes partTilt { 0%, 50%, 100% { rotate: 0deg; } 25% { rotate: 1deg; } 75% { rotate: -1deg; } }

/* The parts drift in, then bob and tilt forever. Three animations that never
   touch the same property twice at once: the pointer tilt above only ever
   writes `transform`; the entrance, the bob and the idle tilt drive the
   separate translate/rotate/scale properties. partIn ends holding rotate at
   0deg (fill: both); partTilt is listed after it and starts the instant it
   ends, so it takes over that property without a visible jump. */
.parts.is-flying .part {
  translate: var(--fx) var(--fy); rotate: var(--fr); scale: var(--fs); opacity: 0;
}
.parts.is-set .part {
  animation: partIn 2.6s cubic-bezier(.22,1,.36,1) both,
             partBob var(--t, 6s) ease-in-out infinite var(--dl, 0s),
             partTilt var(--tr, 11s) ease-in-out infinite var(--dl, 0s);
  animation-delay: var(--in, 0s), calc(var(--in, 0s) + 2.6s), calc(var(--in, 0s) + 2.6s);
}
@keyframes partIn {
  from { translate: var(--fx) var(--fy); rotate: var(--fr); scale: var(--fs); opacity: 0; }
  to   { translate: 0 0; rotate: 0deg; scale: 1; opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .part { animation: none; transition: none; transform: none; }
  .parts .part { translate: 0 0; rotate: 0deg; scale: 1; opacity: 1; }
}

/* the field's ground steps one notch deeper than the hero, so pale fills
   (ceramic, silver, paper) keep their edge on chartreuse as well as orange */
.float.on-orange { background: radial-gradient(70% 60% at 62% 30%, var(--o-400) 0%, transparent 62%), linear-gradient(168deg, var(--o-500) 0%, var(--o-600) 100%); }

/* the machine takes the room it needs: the rig grows and the copy column yields */
.machine[data-machine="parts"] { width: min(100%, 900px); }
@media (min-width: 1100px) { .hero-in { grid-template-columns: minmax(0, .82fr) minmax(0, 1.18fr); } }

/* ============================================================================
   hero: the boot -- see the closing block at the end of this file for the
   full component (the computer, its live CRT, the parts that float above
   it). What stays here is the shared stage scaffolding and the copy column,
   both used exactly as before. No scroll story: the stage is a plain
   full-height block, not a pinned/sticky one -- the parts just float in
   place (see hero-rig's own group bob below) rather than assembling on
   scroll.
   ========================================================================= */
.hero.hero-boot {
  overflow: clip;
  display: block; padding: 0; gap: 0;
  background: var(--bone);
}
.hero-boot .grain { display: none; }
/* No strip behind the floating nav pill: the stage carries its own flat
   ground full-bleed from the very top, so the usual nav-height spacer (which
   would otherwise paint a band of body bone above it) is dropped;
   `.hero-copy` gets its own clearance below instead. */
.hero-boot::before { display: none; }

.hero-scroller { position: relative; min-height: 100vh; }
.hero-stage {
  position: relative;
  min-height: 100vh;
  background: var(--bone);
}
/* index-figure.html / index-video.html only: the video/WebGL figure hero
   keeps its own flat charcoal ground (its `.turn`/`.lisa` figure is a
   true-alpha clip with no baked-in backdrop of its own) plus the grain and
   foot vignette that ground needs -- neither applies to the restored
   drawn-computer hero above, which is `.hero-boot` without `.on-dark` and
   never matches this scope. */
.hero-boot.on-dark .hero-stage { background: var(--n-900); }
.hero-boot.on-dark .hero-stage::before {
  content: ""; position: absolute; inset: 0; z-index: 0; pointer-events: none;
  opacity: .22; mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23n)' opacity='.6'/%3E%3C/svg%3E");
}
.hero-boot.on-dark .hero-stage::after {
  content: ""; position: absolute; inset: 0; z-index: 0; pointer-events: none;
  background: linear-gradient(to top, rgba(0,0,0,.14) 0%, transparent 16%);
}

.hero-boot .hero-in { min-height: 100vh; align-items: stretch; padding-inline: var(--gutter);
  /* the copy column is ~46% of the row, the scene the remaining ~54% --
     the headline is the dominant element, so it gets the room to be one. */
  grid-template-columns: minmax(0, .46fr) minmax(0, .54fr);
}
.hero-boot .hero-copy {
  position: relative; z-index: 3;
  display: flex; flex-direction: column; justify-content: flex-start;
  max-width: none;
  /* top-anchored, not centred: the copy block sits a fixed ~18vh below the
     nav pill so the vertical rhythm is deliberate rather than however the
     content happens to balance out when centred in a 100vh cell.         */
  padding-top: calc(var(--nav-h) + 18vh);
}
.hero-boot .hero-copy h1 {
  font-size: clamp(48px, 5.2vw, 84px); max-width: none;
  line-height: .98; letter-spacing: var(--tr-tight);
  /* charcoal, from the shared `.hero-copy h1` rule above -- the stage's
     ground is bone, the same as every other section, so this needs no
     colour override of its own. */
}
/* index-figure.html / index-video.html only (see `.hero-boot.on-dark
   .hero-stage` above): that stage's ground is charcoal, so its headline
   opts back into the token instead of the shared rule's hardcoded charcoal. */
.hero-boot.on-dark .hero-copy h1 { color: var(--ink); }
.hero-copy h1 {
  margin-top: var(--s-3);
  font-size: clamp(48px, 5.4vw, 88px); font-weight: 800; line-height: .98;
  letter-spacing: var(--tr-tight); max-width: 9ch; color: var(--n-900);
}
.hero-copy .hero-prop {
  margin-top: var(--s-4);
  font-family: var(--font-ui); font-weight: 500;
  font-size: var(--t-md); line-height: 1.5; letter-spacing: 0;
  max-width: 30ch; color: var(--ink-2);
}
/* the hero-boot lede: bigger and narrower than the shared default above,
   now that the copy column has the width to spare for a taller headline. */
.hero-boot .hero-copy .hero-prop {
  font-size: clamp(20px, 1.1vw + 14px, 21px); max-width: 34ch;
}
.hero-boot .hero-actions { margin-top: var(--s-5); flex-wrap: wrap; gap: 12px; }
/* desktop only: this is what pulls the CTA row down to ~72vh -- on the
   mobile/tablet flex-column layout (see the 900px media block below) the
   copy stacks top-down right under the nav, and this extra push would just
   be dead air, so it stays behind the same breakpoint the two-column grid
   itself is gated behind. */
@media (min-width: 901px) {
  .hero-boot .hero-actions { margin-top: calc(var(--s-5) + 8vh); }
}
/* the buttons carry more weight at this headline size */
.hero-boot .hero-actions .btn { height: 56px; padding-inline: 30px; font-size: var(--t-base); border-radius: var(--r-4); }
/* index-figure.html / index-video.html only: .btn-ghost is edge-transparent
   at rest everywhere else on the site -- fine on a paper ground, invisible
   on this one's charcoal stage. The restored (light) hero above needs none
   of this: its ghost button already gets a dark ring for free from the
   default `--line` token, per the light `:root` values in css/tokens.css. */
.hero-boot.on-dark .hero-actions .btn-ghost { box-shadow: inset 0 0 0 1.5px rgba(247,243,238,.55); }
.hero-boot.on-dark .hero-actions .btn-ghost:hover { box-shadow: inset 0 0 0 1.5px rgba(247,243,238,.85); }
.hero-boot .hero-note {
  margin-top: var(--s-4);
  /* `clip`, not `ellipsis`: at exactly the 1280px edge this line and its
     column are within a rounding pixel of each other, and a trailing
     "..." would read as a truncated promise ("no cut of your sal...")
     where a silent one-pixel clip of the full stop is invisible.       */
  white-space: nowrap; overflow: hidden; text-overflow: clip; max-width: 100%;
}
@media (max-width: 1279px) {
  .hero-boot .hero-note { white-space: normal; overflow: visible; text-overflow: clip; }
}
/* a second, quieter row of concrete trust facts -- the same mono utility
   face as `.hero-note` above, one notch smaller and dimmer so it reads as a
   footnote rather than competing with the note it sits under. */
.hero-trust {
  margin-top: var(--s-2); font-family: var(--font-mono); font-size: 11px;
  letter-spacing: .01em; color: var(--ink-3, var(--ink-2)); opacity: .78;
}
/* "Ctrl." is the brand vermilion here, not charcoal -- the ground is bone,
   so it no longer needs the white-on-orange stroke/shadow treatment. Its
   glitch ghosts are re-tinted too: the "before" channel used to be this
   same orange against a white letter, which would vanish against an orange
   letter, so it swaps to charcoal and lets the blue "after" channel stand
   as the second glitch colour. */
.hero-boot .hero-copy h1 i[data-glitch] { color: var(--o-500); -webkit-text-stroke: 0; text-shadow: none; }
.hero-boot .hero-copy h1 i[data-glitch]::before { color: var(--n-900); }
.hero-boot .hero-copy h1 i[data-glitch]::after  { color: #4B69F0; }

@media (max-width: 900px) {
  .hero-scroller { min-height: 0; }
  .hero-stage { min-height: 0; }
  .hero-boot .hero-in { display: flex; flex-direction: column; min-height: 0; height: auto; gap: clamp(8px, 3vw, 20px); }
  .hero-boot .hero-copy {
    max-width: none; height: auto;
    padding: calc(var(--nav-h) + 24px) var(--gutter) var(--s-6);
  }
}

/* ---- second act: the showcase assembles once it is seen ------------------
   Gated behind a class js/site.js adds only once IntersectionObserver has
   confirmed support, so the tiles are never hidden for a visitor without
   script. `.bento` gets the perspective; each `.tile` starts pitched back
   and dark, and settles in on a short stagger set by js/site.js (--i). */
.sc-stage {
  box-shadow: var(--lift-4), 0 30px 60px -30px rgba(0,0,0,.6), 0 2px 0 rgba(255,255,255,.06) inset;
}
.showcase.sc-armed .bento { perspective: 1200px; }
.showcase.sc-armed .sc-panel.is-on .tile {
  opacity: 0; transform: translate3d(0, 40px, -80px) rotateX(8deg);
  transition: transform .9s cubic-bezier(.22,1,.36,1), opacity .6s ease-out;
  transition-delay: calc(var(--i, 0) * 90ms);
}
.showcase.sc-armed.sc-in .sc-panel.is-on .tile {
  opacity: 1; transform: none;
}
@media (prefers-reduced-motion: reduce) {
  .showcase.sc-armed .sc-panel.is-on .tile { opacity: 1; transform: none; transition: none; }
}

/* ===== "One system": the lead photo ==========================================
   The drawn computer scene that used to lead this section (monitor, unit,
   keyboard, mouse, a live till running on the tube) has been removed along
   with the rest of the CRT/machine idea. In its place, the same daylight
   photo the "serve" section's multi-outlet panel already uses
   (img/photo/multi.webp: an owner checking the dashboard on a tablet at the
   pass, a cook plating behind her) leads here instead -- framed rather than
   left plain, since a photograph wants a rounded edge and a shadow the way
   the old scene's own drawn parts never did. Sits in `.section-head.with-art`'s
   own `.head-art` right-hand column unchanged (same grid slot, same
   width/justify-self the two sibling sections' plain `<img class="head-art">`
   gets); this class styles the photo itself, inside that slot. */
.head-art-photo {
  display: block; width: 100%; height: auto; aspect-ratio: 4 / 3;
  border-radius: var(--r-6); box-shadow: var(--lift-4); object-fit: cover;
}

/* ===== hero: the figure ======================================================
   A cursor-scrubbed video (js/turn.js), driven by the cursor's DIRECTION and
   distance from the figure rather than only its X position: a rendered clip
   of the same knit-turtleneck, CRT-headed figure looking around a fixed set
   of poses (centre, down, right, up, left), with the playhead eased toward
   whichever keypoint the cursor vector is closest to instead of ever
   actually playing (see js/turn.js). Idle, touch, and a cursor that has left
   the hero resume PLAYING the loop forward instead of just holding a pose.

   The figure is now a TRUE-ALPHA video -- shot on a flat chroma-green
   backdrop, keyed out, and shipped with real per-pixel transparency (VP9 in
   .webm, for Chrome/Firefox/Edge). Safari/iOS get a plain H.264 .mp4 instead
   (this build's hevc_videotoolbox would not honour -alpha_quality, so no
   HEVC-alpha .mov could be produced -- see js/turn.js's own note); that mp4
   is pre-composited over this exact --n-900 at encode time, so the fallback
   is pixel-identical to the alpha path rather than merely close. Neither
   clip carries its own baked-in studio background any more, so `.hero-stage`
   paints ONE flat ground (--n-900) behind
   the whole section, copy and figure alike, and the browser composites the
   alpha video straight over it: no seam to fake closed, so the old
   feathered edge-mask is gone along with it. `.turn`'s own background-image
   is the poster (img/hero/lisa/figure-poster.webp, the clip's centre pose,
   pre-composited over that same --n-900 so it matches pixel-for-pixel),
   painted from first load with no JS at all; `.turn-video` sits above it and
   only ever fades in once js/turn.js has primed a real frame. */
.hero-visual {
  position: relative; min-height: 340px;
  /* the till/phone/kitchen device row (#hero-devices) is a normal, sized
     box that needs centring; the video/WebGL figure below (index-video.html
     / index-figure.html / index-3d.html) is absolutely positioned inset:0
     instead, so it is unaffected by these alignment properties -- one
     shared cell, two different fills. */
  display: flex; flex-direction: column; align-items: flex-end; justify-content: center;
}

.turn {
  position: absolute; inset: 0;
  overflow: hidden;
  cursor: pointer;
  /* relative to THIS file's own location (css/), not the page -- one level
     up, the same convention as `.lisa` below (a plain <img src="img/..."> or
     <video poster="img/..."> in the HTML resolves the same path differently,
     which is why the corrected `../` prefix has to live here). */
  background-image: url("../img/hero/lisa/figure-poster.webp");
  background-size: cover;
  background-position: 50% 20%;
  will-change: transform;
}
.turn-video {
  position: absolute; inset: 0;
  display: block; width: 100%; height: 100%;
  object-fit: cover; object-position: 50% 20%;
  opacity: 0;
  transition: opacity 1.1s ease;
}
.turn.is-playing .turn-video { opacity: 1; }
/* reduced motion: js/turn.js never touches the clip at all in this case --
   this just makes sure nothing is left decoding invisibly underneath, and
   the poster (a still, not a loop) is the whole of what shows. */
.turn.is-static .turn-video { display: none; }

/* ---- the WebGL figure (js/lisa.js), restored as the main hero in place of
   the scrubbed clip above -- `.lisa` fills `.hero-visual` the same way
   `.turn` does. Its own background-image is the poster
   (img/hero/lisa/figure.webp), so there is always something correct on
   screen the instant CSS loads, no script required; the <canvas> the module
   renders into sits above it and only fades in once js/lisa.js has primed a
   real frame (`.lisa.is-ready canvas`). The `<img class="lisa-still">`
   inside is a second, JS-owned copy of that same poster: it stays `hidden`
   until the module's own no-WebGL/reduced-motion path explicitly reveals it
   and hides the canvas instead (see `fallback()` in js/lisa.js) -- a
   dedicated element for that case, distinct from the CSS poster above, which
   needs no script to show. */
.lisa {
  position: absolute; inset: 0;
  overflow: hidden;
  cursor: pointer;
  /* relative to THIS file's own location (css/), not the page -- one level
     up, unlike the plain <img src="img/..."> paths in index.html itself. */
  background-image: url("../img/hero/lisa/figure.webp");
  background-size: cover;
  background-position: 50% 22%;
}
.lisa canvas {
  position: absolute; inset: 0;
  display: block; width: 100%; height: 100%;
  opacity: 0;
  transition: opacity .5s ease;
}
.lisa.is-ready canvas { opacity: 1; }
.lisa.is-fallback canvas { display: none; }
.lisa-still[hidden] { display: none !important; }
.lisa.is-ready { background-image: none; }
.lisa-still {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover; object-position: 50% 22%;
}

/* ---- desktop: the figure fills the whole visual column -- `.hero-boot
   .hero-in`'s own `align-items: stretch` (see above) is what gives
   `.hero-visual` a real height to fill in the first place. */
@media (min-width: 901px) {
  .hero-visual { min-height: calc(100vh - var(--nav-h) - 8vh); }
}

/* the rig fills the scene; every part's --x/--y/--w below is a fraction of
   this box, so the whole composition scales with it. The shared group bob
   lives here rather than on any one part, so all four rise and fall
   together and the composition never drifts out of alignment with itself. */
.hero-rig { position: absolute; inset: 0; z-index: 2; animation: heroRigFloat 8s ease-in-out infinite; }
@keyframes heroRigFloat {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-10px); }
}
@media (prefers-reduced-motion: reduce) { .hero-rig { animation: none; } }

/* ---- a part: --x/--y/--w/--r (read here) place it in its fixed pose;
   --depth/--dur/--delay/--bob are not consumed by this rule -- js/site.js
   reads --depth off the inline style every frame for the parallax, and the
   @keyframes below reads the rest for the part's own small bob. The
   incremental pointer-parallax transform js/site.js writes to this element's
   own `transform` never fights the part's idle bob, which animates the
   `.hp-float` child instead -- two elements, two owners, one property each. */
.hp-part {
  position: absolute; left: var(--x); top: var(--y); width: var(--w);
  transform: rotate(var(--r, 0deg));
  will-change: transform;
}
.hp-part img {
  display: block; width: 100%; height: auto;
  filter: drop-shadow(0 26px 30px rgba(58,16,4,.26));
}
/* depth order: the monitor sits above everything so no part ever crosses in
   front of its screen; among the rest, keyboard rides in front of the unit
   and the mouse rides in front of the keyboard, per the brief */
.hp-monitor  { z-index: 4; }
.hp-mouse    { z-index: 3; }
.hp-keyboard { z-index: 2; }
.hp-unit     { z-index: 1; }

/* the part's own small, out-of-phase top-up on the shared group bob above --
   translate only, no sway/rotate, so an individual part never tips out of
   the group's fixed pose */
.hp-float { position: relative; animation: hpFloat var(--dur, 8s) ease-in-out infinite var(--delay, 0s); }
@keyframes hpFloat {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(calc(-1 * var(--bob, 3px))); }
}
@media (prefers-reduced-motion: reduce) {
  .hp-float { animation: none; }
}

/* ---- power-on entrance: every load (not session-gated -- unlike the boot
   veil), the four parts start displaced outward from their fixed pose,
   rotated, faded, and glide into place once js/login.js's own arm() adds
   `.is-armed` to the sign-in page's `.hero-rig` (after the boot veil hides
   when it shows this session, or right away when it does not). A third
   element per part, `.hp-enter`, owns this transform+opacity alone --
   `.hp-part` is the per-frame parallax owner (the same script overwrites
   its `transform` every frame, never this one) and `.hp-float` is the
   idle-bob owner (its own @keyframes above); nesting the three means the
   parallax, this glide and the bob compose as ordinary nested transforms
   rather than fighting over one property. */
.hp-enter {
  transform: translate3d(var(--enter-x, 0px), var(--enter-y, 0px), 0) rotate(var(--enter-r, 0deg));
  opacity: 0;
  transition: transform 1.1s cubic-bezier(.2, .7, .2, 1), opacity 1.1s cubic-bezier(.2, .7, .2, 1);
  transition-delay: var(--enter-delay, 0s);
}
.hero-rig.is-armed .hp-enter { transform: none; opacity: 1; }
/* outward per the brief: monitor up-left, unit left, keyboard down-right,
   mouse right -- keyboard carries the longest delay so it settles last. */
.hp-monitor  .hp-enter { --enter-x: -55px; --enter-y: -50px; --enter-r: -3deg; --enter-delay: 0ms; }
.hp-unit     .hp-enter { --enter-x: -65px; --enter-y: 0px;   --enter-r: 2deg;  --enter-delay: 80ms; }
.hp-mouse    .hp-enter { --enter-x: 65px;  --enter-y: 0px;   --enter-r: -2deg; --enter-delay: 160ms; }
.hp-keyboard .hp-enter { --enter-x: 60px;  --enter-y: 55px;  --enter-r: 3deg;  --enter-delay: 240ms; }
@media (prefers-reduced-motion: reduce) {
  .hp-enter { transform: none; opacity: 1; transition: none; }
}

/* ---- power-on flash: the CRT's own moment inside the entrance, ~0.9s in
   (js/site.js) -- a 120ms hairline expand-and-brighten on the tube's glass,
   the same idea @keyframes screenOn already gives the pad screens elsewhere,
   just timed for this smaller, closer flourish before the boot typewriter
   starts. */
.hero-crt-glass.screen-on { animation: screenOn .12s var(--glide) both; }
@media (prefers-reduced-motion: reduce) {
  .hero-crt-glass.screen-on { animation: none; }
}

/* ---- reactive halftone shadows: one dotted ground shadow per part, drawn
   the way the poster's own flat illustrations are (radial-gradient dots, no
   images), sized a little past the part's own footprint and skewed to sit
   on the desk. `.hp-shadow` sits outside the parallax chain -- a sibling of
   the parts at the bottom of `.hero-rig`'s stacking, so it is always the
   "ground" regardless of which part is on top -- and reacts to the shared
   rAF loop in js/site.js purely through --lift/--lift-x/--lift-y, the same
   drive-a-custom-property idiom --depth already uses: JS never touches this
   element's `transform` directly, only the three custom properties this
   rule's own calc()s read every frame. */
.hp-shadow {
  position: absolute; z-index: 0; pointer-events: none; display: block;
  left: var(--x); top: var(--y); width: calc(var(--w) * 1.14); aspect-ratio: 5 / 1.6;
  --lift: 0; --lift-x: 0px; --lift-y: 0px;
  transform:
    translate(6%, 58%)
    translate3d(var(--lift-x), var(--lift-y), 0)
    skewX(-9deg)
    scale(calc(1 - .06 * var(--lift)));
  border-radius: 50%;
  opacity: clamp(.70, calc(.85 - .15 * var(--lift)), 1);
  background-image: radial-gradient(circle, rgba(0,0,0,.28) 1.1px, transparent 1.4px);
  background-size: 4px 4px;
  filter: blur(.4px);
}

/* ---- the screen: a flat rectangle warped onto the monitor drawing's four
   measured corners (see heroBoot -> fitCrt in js/site.js: a vermilion mask
   of img/hero/parts/monitor.webp, its largest connected component, corners
   taken as the mask's own points that maximise x+y / x-y / -x+y / -x-y --
   the real corners of a tilted quad -- then inset 2% toward their own
   centroid so the UI sits inside the drawn bezel line). A child of
   .hp-float rather than of .hp-part directly, so it inherits the monitor's
   bob the same way it inherits the pointer-parallax transform one level up
   -- two ordinary nested transforms, no preserve-3d needed since nothing
   here asks to share a parent's 3D space, only to ride along inside it. */
.hero-crt { position: absolute; left: 0; top: 0; transform-origin: 0 0; pointer-events: none; }
.hero-crt-glass {
  position: absolute; inset: 0; overflow: hidden;
  border-radius: 4%;
  background: #0C0904;
  transition: background var(--d-4) var(--glide);
  container-type: inline-size;
}
/* powered on: the drawing's own vermilion floods the tube, the same way the
   artwork already shows it lit */
.hero-crt.is-booted .hero-crt-glass { background: var(--o-500); }
.hero-crt-glass::before {                 /* scanlines */
  content: ""; position: absolute; inset: 0; z-index: 4; pointer-events: none;
  background: repeating-linear-gradient(to bottom, rgba(0,0,0,.28) 0 1px, transparent 1px 3px);
  mix-blend-mode: multiply;
}
.hero-crt-glass::after {                  /* vignette + the curve of the glass */
  content: ""; position: absolute; inset: 0; z-index: 5; pointer-events: none;
  border-radius: inherit;
  background:
    linear-gradient(122deg, rgba(255,255,255,.10) 0%, rgba(255,255,255,.03) 22%, transparent 42%),
    radial-gradient(120% 130% at 50% 50%, transparent 56%, rgba(0,0,0,.40) 100%);
}
/* the boot text and the mini till both live here, stacked on top of each
   other (not flex siblings sharing a row -- two items in one flex row would
   shrink each other for space even while one sits at opacity 0) and each
   centres itself within the same rectangle so the crossfade has nothing to
   reflow */
.hero-crt-vp { position: absolute; inset: 0; }

.hero-caret {
  display: inline-block; width: .5em; height: .95em; vertical-align: -.15em;
  background: currentColor; margin-left: .08em;
  animation: heroBlink 1.05s steps(1) infinite;
}
@keyframes heroBlink { 0%, 49% { opacity: 1; } 50%, 100% { opacity: 0; } }

/* ---- the login terminal (sign-in page) is real: on a fine pointer,
   `.hero-crt` stops being a decorative loop and starts taking real clicks
   -- `.hero-crt` carries the matrix3d homography, so turning its own
   pointer-events back on is what lets a click land on the transformed quad
   at all. A touch visitor never matches this query, so touch never sees
   this cursor. */
@media (hover: hover) and (pointer: fine) {
  .hero-crt {
    pointer-events: auto;
    cursor:
      url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='28' height='28' viewBox='0 0 28 28'%3E%3Cpath d='M12.5 3.2c0-1 .8-1.8 1.8-1.8s1.8.8 1.8 1.8v6.9l1.1.1V6.4c0-1 .8-1.8 1.8-1.8s1.8.8 1.8 1.8v4.4l1.1.2V8.9c0-1 .8-1.7 1.7-1.7s1.7.7 1.7 1.7v7.6c0 3.9-3.1 7-7 7h-2.9c-1.9 0-3.7-.8-4.9-2.3l-3.6-4.3c-.7-.8-.6-2 .2-2.7.8-.7 2-.6 2.7.2l1.7 2V3.2z' fill='%23F7F3EE' stroke='%23000000' stroke-width='1.1' stroke-linejoin='round' stroke-linecap='round'/%3E%3C/svg%3E")
      13 2, pointer;
  }
}

@media (max-width: 900px) {
  .hero-visual {
    order: -1; height: min(118vw, 700px); min-height: 340px; width: 100%;
    margin-top: calc(var(--nav-h) + clamp(10px, 4vw, 24px));
  }
}

/* ---- mobile: the figure runs full width under the copy, a fixed 64vh --
   `.turn` stays absolutely positioned and inset:0 throughout (see above), so
   this only ever has to set `.hero-visual`'s own box; nothing about the
   video's own sizing changes. Hover-only pointer follow never applies on
   touch (js/turn.js's own hover:hover gate), so the figure runs its
   autonomous look-around instead -- object-position shifts up from a plain
   50% so the head (sitting at ~38% down the source frame -- see js/turn.js's
   own 0.38 origin) is what a full-width 64vh strip centres on, not the
   chest below it. */
@media (max-width: 760px) {
  .hero-visual { order: 0; height: 64vh; min-height: 0; width: 100%; margin-top: 0; }
  .turn, .turn-video { background-position: 50% 30%; object-position: 50% 30%; }
  .lisa, .lisa-still { background-position: 50% 12%; object-position: 50% 12%; }
}

/* ===== page: boot veil =======================================================
   Once per session (sessionStorage, js/site.js), a fixed bone veil covers the
   page for about a second while three mono lines type in over a six-segment
   progress bar, then fades. Built and torn down entirely by script, so a
   visitor without JS -- or a repeat visit within the session -- never sees
   it; the page underneath is already fully rendered regardless. Skipped for
   reduced motion. */
.boot-veil {
  position: fixed; inset: 0; z-index: 300;
  background: var(--bone); color: var(--n-900);
  padding: var(--s-5) 0 0 calc(clamp(10px, 2vw, 20px) + var(--s-4));
  opacity: 1; transition: opacity 260ms var(--glide);
}
.boot-veil.is-out { opacity: 0; }
.bv-line {
  font-family: var(--font-mono); font-size: 13px; line-height: 1.7;
  min-height: 1.7em; white-space: nowrap;
}
.bv-bar { margin-top: var(--s-3); display: flex; gap: 3px; width: 200px; }
.bv-bar i { display: block; flex: 1; height: 3px; border-radius: 1px; background: var(--n-200); }
.bv-bar i.is-on { background: var(--o-500); }

/* ===== page: grain + vignette on the dark bands ==============================
   A tiny tiling noise PNG (96x96, PIL, two-tone flecks baked at ~12% alpha
   so the tiled average lands near 6%, <3KB) plus a plain radial vignette,
   layered on every `.section.on-dark` band and on the live CRT screen.
   Both are decorative, static, `pointer-events:none` -- no per-frame cost.
   The vignette is ordinary alpha so it always darkens regardless of ground;
   the noise carries its own blend mode since a lightening speckle needs
   `screen` on a dark ground and would wash out `multiply` would suit a light
   one instead. */
.section.on-dark { position: relative; }
.section.on-dark::before, .hero-crt::before {
  content: ""; position: absolute; inset: 0; z-index: 2; pointer-events: none;
  background: radial-gradient(120% 130% at 50% 45%, transparent 55%, rgba(0,0,0,.34) 100%);
}
.section.on-dark::after, .hero-crt::after {
  content: ""; position: absolute; inset: 0; z-index: 3; pointer-events: none;
  /* 2026-09: charcoal (n-900) became plain black -- the same ~12%-alpha
     tile read noticeably grittier against true black than it did against
     #171717, so it now paints at half strength on black grounds. */
  opacity: .5;
  background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAQAAABIkb+zAAAJxklEQVR42o2cW3bbMAxEJR2vw/tfVzbifsgM53GhND1NE1kiKRKPwQDo8Xkfx3Ecx+e9ftpfn/f+2/fcv+1r6ze+qp/fn+n9Pbbfob/DWn2Re3m6nBzEX8OX6iPtqz5eL9DH0Lt0Xp/hzH0/f/aV82fdeP+0/v0++uOf7afPH3/i885Rc4498h5JN+keUcde1+C48vB8V/1Pi1wL3vEwR9+pwpmi5ef0/Uxv87/+oE/kAuaHmwLZuuE6RbO6iOnL5AacuUt65OvgtgC4KKzDdPFTMXNhcBHYY6zrLbopcutnFdsjhWRWm77eokZK6nP4Vfqes9HJrO9XvE0olO6Fn4Kq2vnjKrevr7HuO+7p76t6xvp0a8needzAlsc2g6xirKx5Kr17OVPLd75Am1rZzpRalUXWgm3YWvJdvs3YiTFcz/ZcqTOpT/3pNTmydew+ydqP+xNXMH39LZbrlXTJt9gs8VnjqCi279/3uZ6c6ZZy93O/dW/bOrD7a5e1ZuwTIqvj88W/bEHI0rSrb/fS7ieBAQOQGQs1gAhz4W7ad2zvD9l3ByBpgfo0XV9cT9JT9HcHLPuMrz2Aymf7gTWpHu66c92fO5b4JsXCTeStW26AdTVr8frk533qe/a0rZQtjwnZ0mKlD3bg57PryykWIJt13/PqPdPltbK1EqbYuSy3q5o3K22a27EU6/vrchO2TCQFLi3167prgBq8ffwNp/1U09OvMfvlddTjuPZetydQKXdrnnZEfYKLUepUoi6X6C0w5GDX5ujLnmScfA/SvWVoMY/gIY5/9+ddf1xQMhwKTUopbsV048XRUYPgBsjziAxIetvKB6QVcvVqO96esneL/LRDjPkUyGv40ttnXLrrqb5bIdVCuxdIm6OGwG22qnmKR+IrF9ZGsQLZWfZTDNwrk9g4ZmVf3fbfZT5xVJ4OYeOLg+a19y39bncyuN8noyd273lrWb5iYs3eqIaGB5FLU7CSYG1ibpq98WC8w0cCc0QENLS/ttD4h/zQfQquorqYNr2OnchiOXRe3kED0z1vxm8R0PXHBJ/bNxPTQ1cIMk+sEYenfQZXO3MFBEs2U7SSN0sd2daIXja3S3VLUbFapEbF9/crUfaeXM9AB1OvoM7fLfY2vx3N7tHW8+kn8m69fwPEz/s4Xm0VPKChgFJRTILvZUv2TqlBdHiu+5u+NjHoZJqP/5fDKaxryZ+sDlmx+Q7mSHO+k5z4THtMMC7d3MRT/EESHgQvKCrZZ/Ry+d5mMkOJhBHNw9EJqe9Mr0svRDxpIzNV/6uZmPxNVY4lMZWQbFUqtEe4vsDGRYqhlh+6n3pN6YfG4G4mE/w22M6oluDAHEwxb52kwDegIbntcL6HmKLVBsZNeeXLZQgz52sCDxHxREgoUQrbI7InxCs3pUV4jAjnspaciaR0WvNtxDs3uJsZuSmL03Byzgadib+Js+mcSfrfDh5TiedojYUrg1fiq0WE0kgxU0qZFEqTPmfL+pTJXfX8LLYXeeF09B7cZZ5mk/C0fAfJDhKSdGfXuOWgaeZvfmDjTdX+1gqNtNydpNlVAZuMYOYXnP93d6nb5Cd4TYSewunkx7ZbIifnflafXkujLNs+fd9EWochVdbtKSh5Slv/pQ0UGk3ZSfb5lE27iOpWYmOitvQucj1umP3eVkm63lu2BXKv5NryOotBK5pzphsUbElN+Z2QvMfjO5pg7+CxouXI5uwgweiJz+vwL5FVs05t6YmA58398kL7Jkpn6Cs56a1uaXFJaRKZ2eDEOL0k187s1VybBFzfuzooj1UNaEezieTdenQo+sSME/2i4PrryDZ34NZezWlbf10K6VGyRz7CjqBVCzvWcyMKutQp/C7RYH5sgiBU0EF8XwO7qWiE+Lp1/0Xy6HtLuDSxTSenSKUzKMrX8Ux91+Kp+5RqFUpINHpkYtaV1sUlA0quMUpwsv37/kM80dqSi/ZLJVrtr7uSpUYUv07ZdjfYnWHw4FU3RDlT3boroZh6WT/UroVI1kytjZJVDvqIZ2ttSIVVZdfI/HIgTAyzGkIHYG5q9USofEpfx6/6MhtxEhnwO0raHH6EIdVTnqDtE4U3HG8zG95h1HF83q9mIFLukm6ZnLwKCrkmPZ3nZFTLhC7dVf7FdZsaXnQCewtLR9Lp0lzu2z41kJhqkIiy+bzPZzYzw/yZH50KMrLONtOtXCg1FTYka/5besxokpLRSf92rQpFtROB29wdPdVCu1/xcqtMdpkdFAVBmedxgVLH5CFluzfP2GRxibq/VzOPhC4zCZQC0F7cXY/DiCwKbCflqJVEep3dlehbHYsyluyyPByldJJPl9hVkai/VhY+6Sc7E3T+nF27NldKkHRzjUkHRnMdxszpEYdXOQPmH9j5cJX0zG9yipYSs1P1+lQ/aaMSsUH5XfIWnPklVvo4puJLWmbPMZab9+BPe9ZcNleB9k9PJQndZjGdVAOaq8stpumVAyU/QAlzjZ3neuitzonGNGHuFIw40Sn9MNfezk061OsxMX5z4QK1II1NQlTq95zb5V4ayuNOZcaEL6fuGTIk+um1rX5nJmfePusYSG+mfgSiK/dvHjOnl+ow55X40XsrUgLn2jbqkiFvQP0zXCWWnGhu1Hc0OhxmIJ4KaDivRYJDAsVZOq6WSx24nKunMpguuvbsSSIkRadZjbJnad65C41JN5vxxjINepx6lqb9m9mdKYB9ylA+2azLa4TcIlOPGbWDaAGgS2rSkBP8m/CPFyO3JlrhK1fbzonUqTXnibqlFK07txZJSrJaA4QmDog75qqrJFyaBG/XtHNlbaYdyqce+Onq3eezGfurIrqbFeYmiu5xotYHioaZev+8j4kBmgjcriCcrEWnqicwPPl66j9GT5wwrNWH+wk0qKRqwhY9D3vIy2fUl+Rypvo+7+M4JyqlvSY3NCdb0ULTJZOTaEy9NVza9gslMluYieq2QUmNeMWc9iE5v8x10LRVMx1ffAVnQLic5qmOh8prOCDt8hnOEE+p70AL1FT43FXBATcJH1dwPTVBUBvLXAR3PAdrXGPOp0CBP5/O3Pg7WRqSAFt3OwzO8GYI3jmsqZtyFrepDHxK9nX90kmdde222cI8cckOHLoT0sVtFmTu8Nsjv7yDzm2F39q4KCF1+hJq5CHU5ES9wglGU8bPNd859R5RHRv1mzXzTF0wTOazh3jq+L68067hXJY6kQXq/snuQ8oO2ax5oIS5sqnpCn8h+vP/YEJlTDMRciDHMdOP0xPH0GyN/uaZ/pgL78lYchRNJbFPUdxENTaQ+32FiXaaKNzpv2iYKdy/vPOUkZxOen/9A8M8DmcVpXyqAAAAAElFTkSuQmCC");
  background-repeat: repeat; background-size: 96px 96px;
  mix-blend-mode: screen;
}
/* ===== login: crt terminal ===================================================
   The sign-in page carries the homepage's own levitating PC -- same drawing
   (img/hero/scene/*.webp), same measured positions (img/hero/scene/
   layout.json), same halftone shadows and power-on entrance -- set down on
   the page's single charcoal ground now, filling the left column behind the
   headline, with its tube running a login terminal rather than the till.
   Every part/shadow/entrance/glass rule this needs already exists above
   under "hero: the parts" (.hp-part, .hp-float, .hp-enter, .hp-shadow,
   .hero-crt / .hero-crt-glass / .hero-crt-vp) and is reused here unchanged,
   by class name -- this block adds only what is login-specific: the
   two-column charcoal layout, the left column's copy, the scene's own box,
   and the liquid-glass sign-in card floated over the scene's right edge.

   `.login.lg-boot` -- not the bare `.login` class start.html's own trial
   page also carries -- scopes every layout rule below so that page's
   two-column grid (.lg-form / .lg-promo.on-orange) is never touched. */
.login.lg-boot {
  background: var(--n-900);
  display: grid;
  grid-template-columns: 1fr;
  grid-template-rows: auto auto auto;
  grid-template-areas: "copy" "card" "scene";
}
@media (min-width: 901px) {
  .login.lg-boot {
    grid-template-columns: minmax(0, 1.22fr) minmax(0, 1fr);   /* ~55/45 */
    grid-template-rows: auto 1fr;
    grid-template-areas: "copy card" "scene card";
  }
}

/* ---- the left column's words: the site's own eyebrow (mono, the accent
   square already built into .eyebrow::before), a headline set in the
   display face with its one word in the brand fill, and the tail of the
   homepage's own lede. */
.lg-copy {
  grid-area: copy;
  position: relative; z-index: 3;
  max-width: 30rem;
  padding: clamp(28px, 4vw, 64px) clamp(24px, 5vw, 64px) 0;
}
.lg-headline {
  margin-top: var(--s-4);
  font-family: var(--font-display); font-weight: 800;
  font-size: clamp(32px, 3.4vw, 54px); line-height: 1.05;
  letter-spacing: var(--tr-tight); color: var(--ink);
}
.lg-headline .accent { color: var(--o-500); }
.lg-copy-lede {
  margin-top: var(--s-4); max-width: 30ch;
  font-size: var(--t-md); line-height: 1.5; color: var(--ink-2);
}

/* ---- the scene: fills whatever height the copy above leaves it (the
   grid's own 1fr row, desktop only), bled a little past the page's own left
   and bottom edges the same way the homepage's own hero bleeds past its
   column -- body keeps its usual overflow-x:hidden (base.css) so the left
   bleed never causes page-level scroll. Mobile stacks it last, contained at
   a plain 92vw rather than height-driven. */
.lg-scene-wrap { grid-area: scene; position: relative; min-height: 180px; }
.lg-scene {
  /* sized by the column's own width first (its right edge lands flush with
     the column boundary, where the card overlaps it, below) rather than by
     the wrap's height -- on a short viewport that leaves less vertical room
     than the aspect ratio wants, the excess becomes bottom bleed instead of
     shrinking the drawing away from the card. */
  position: absolute;
  left: -4%; bottom: -7%;
  width: 100%; height: auto;
  aspect-ratio: 4096 / 3499;
}
@media (max-width: 900px) {
  .lg-scene-wrap { min-height: 0; padding: 0 0 clamp(20px, 5vw, 32px); }
  .lg-scene { position: relative; left: auto; bottom: auto; width: 92vw; height: auto; margin: 0 auto; }
}

/* the halftone ground-shadows read as black dots against the homepage's
   own bone; against this page's charcoal they would all but vanish, so here
   they take the opposite move instead -- a soft bone glow under each part,
   no dot texture -- so the parts read as objects a light is falling on
   rather than drawings sitting on paper. */
.lg-scene .hp-shadow {
  background-image: radial-gradient(closest-side, rgba(247,243,238,.07), transparent 72%);
  background-size: 100% 100%;
  filter: blur(6px);
}

/* ---- the sign-in card: liquid glass, floated over the scene's own right
   edge (desktop) so the blur has something behind it to bend. */
.lg-card-wrap {
  grid-area: card;
  position: relative; z-index: 20;
  align-self: center; justify-self: center;
  width: 100%; max-width: 440px;
  padding: 0 clamp(20px, 5vw, 32px) clamp(20px, 5vw, 32px);
}
/* desktop: anchored to the column's own left edge (not centred) and pulled
   back exactly 40px, so the card's own left edge -- flush with the wrapper's
   here, its mobile-only side padding above dropped to 0 -- lands 40px inside
   the scene's right edge rather than the column boundary the two share. */
@media (min-width: 901px) {
  .lg-card-wrap { justify-self: start; padding: 0; margin-left: -40px; }
}

.lg-glass {
  position: relative;
  border-radius: 22px;
  padding: clamp(28px, 3vw, 38px) clamp(24px, 3vw, 32px) clamp(22px, 2.4vw, 28px);
  background: rgba(247,243,238,.09);
  border: 1px solid rgba(247,243,238,.14);
  -webkit-backdrop-filter: blur(28px) saturate(1.2);
  backdrop-filter: blur(28px) saturate(1.2);
  box-shadow: inset 0 1px 0 rgba(247,243,238,.22), var(--lift-4);
}
@media (prefers-reduced-transparency: reduce), (prefers-contrast: more) {
  .lg-glass { background: var(--n-900); backdrop-filter: none; -webkit-backdrop-filter: none; }
}
@media (prefers-contrast: more) {
  .lg-glass { border: 2px solid var(--bone); }
}
.lg-card-mark { display: block; height: 26px; width: auto; margin-bottom: var(--s-5); }
.lg-glass .lg-inner { max-width: none; }
/* fields, for a dark glass card rather than a solid surface */
.lg-glass .lg-fields input[type="email"],
.lg-glass .lg-fields input[type="password"] {
  background: rgba(247,243,238,.06);
  border-color: rgba(247,243,238,.16);
  color: var(--ink);
}
.lg-glass .lg-fields input::placeholder { color: var(--ink-3); opacity: 1; }
.lg-glass .lg-fields input:focus {
  background: rgba(247,243,238,.10);
  border-color: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 32%, transparent);
}
.lg-glass .lg-foot { margin-top: var(--s-6); }
.lg-copyright {
  margin-top: var(--s-4); text-align: center;
  font-size: var(--t-xs); color: var(--ink-3);
}

/* the login CRT never hands off to a till, so it never wants the till's
   hand cursor either -- two classes beat the shared rule's one, no
   `!important` needed. */
@media (hover: hover) and (pointer: fine) {
  .hero-crt.lg-crt { pointer-events: none; cursor: default; }
}

/* the terminal: a mono phosphor readout that stays up for as long as the
   tube is lit, rather than a boot sequence that fades once something else
   takes over -- its own class, not shared with any fade-out rule. */
.lg-term {
  position: absolute; inset: 0; z-index: 2;
  /* bottom-anchored, not centred: at the bigger type below, the full
     six-line state (title/outlet/login/password/session/status) runs
     close to the tube's own height on the smaller desktop breakpoints,
     and a centred stack would clip evenly off both ends. Packing to the
     end instead means any overflow comes off the OLDEST line at the top
     -- clipped by .hero-crt-glass's own overflow:hidden -- while the
     lines the operator actually cares about (what they're typing right
     now, at the bottom) stay fully in view, the "~8 lines, oldest
     scrolls off" a terminal is expected to behave like. */
  display: flex; flex-direction: column; align-items: flex-start; justify-content: flex-end;
  padding: 7%;
  font-family: var(--font-mono); color: #F7C79A; text-align: left;
  text-shadow: 0 0 8px rgba(242,140,60,.7);
  /* the tube's own screen (the container this cqw reads, in its own
     untransformed layout size -- container queries read that, not the
     matrix3d-warped box js/crtmap.js visually stretches it to) grows with
     the bigger scene above, but the old 3.4cqw was tuned for the old,
     smaller screen and was landing on its 9px floor even at 1440 --
     illegible. 9cqw against the new screen size reads ~15-16px on screen
     at 1440 once the homography's own slight scale-up is applied (roughly
     1.6x that old floor); the 18px ceiling is headroom for ultra-wide
     panels, the 14px floor for narrow ones. */
  font-size: clamp(14px, 9cqw, 18px); line-height: 1.55;
}
.lg-term p { min-height: 1.55em; white-space: nowrap; overflow: hidden; }
.lg-term p[hidden] { display: none; }

/* ---- mobile: `.lg-scene` stays contained (92vw, above) rather than
   height-driven, so the tube's screen is much smaller here than on
   desktop -- the 14px floor tuned for the desktop screen is wide enough
   to clip "delEat OS 2.4" against a screen this size (nowrap + hidden
   overflow, above, so a clipped line just vanishes off the right edge
   rather than wrapping). 10cqw keeps scaling with the screen as it grows
   through the tablet end of this range; the 9px floor is the old size,
   kept only as the safety net for the very smallest phones it was
   already sized for. */
@media (max-width: 900px) {
  .lg-term { font-size: clamp(9px, 10cqw, 16px); }
}

@media (prefers-reduced-motion: reduce) {
  .lg-term .hero-caret { animation: none; opacity: 1; }
}

/* ============================================================================
   HUMANIZE PASS, index.html only. Founder note, the hands photography
   bands, the two photo-moments (kitchen, delivery), Ask delEat, the evidence
   strip, and the shared bits status.html borrows (it links css/site.css
   directly). Nothing above this comment is touched by any of it.
   ========================================================================= */

/* ---- founder note (about.html) --------------------------------------------
   One aligned block: 5/12 photo, 7/12 copy, vertically centred. The portrait
   slot degrades to a monogram panel until img/photo/founder.webp exists (see
   the onerror handler in markup). No halftone ground-shadow here on purpose
   -- that dotted drop-shadow is the hero's own device idiom (.hp-shadow),
   and a signed note is not a floating object, it is a person, photographed
   flat like any editorial portrait. One radius (--r-5) everywhere, and the
   photo stretches to the exact height of the copy column (align-items:
   stretch, object-fit: cover) rather than carrying its own fixed aspect
   ratio, so the two columns always resolve to one clean rectangle. */
.fn { background: var(--bone); }
.fn-in {
  display: grid; grid-template-columns: 5fr 7fr;
  gap: clamp(28px, 4vw, 56px); align-items: stretch;
}
@media (max-width: 780px) { .fn-in { grid-template-columns: 1fr; gap: var(--s-6); } }
.fn-photo-wrap {
  position: relative; border-radius: var(--r-5); overflow: hidden;
  min-height: 360px;
}
@media (max-width: 780px) {
  /* edge to edge, then the copy below -- no side gutter, no radius, the
     photo reads as a full-bleed band the way every other photo band on the
     site does, not a shrunk portrait squeezed beside the text. */
  .fn-photo-wrap {
    margin-inline: calc(-1 * var(--gutter)); border-radius: 0; min-height: 62vw;
  }
}
.fn-photo, .fn-fallback {
  position: absolute; inset: 0; width: 100%; height: 100%;
}
.fn-photo {
  object-fit: cover; display: block;
  transition: transform var(--d-3) var(--snap);
}
@media (hover: hover) and (pointer: fine) { .fn-photo:hover { transform: scale(1.02); } }
.fn-photo.fn-broken { display: none; }
.fn-fallback {
  display: none; place-items: center; background: var(--n-100);
  background-image: radial-gradient(circle, rgba(0,0,0,.08) 1px, transparent 1.3px);
  background-size: 6px 6px;
}
.fn-photo.fn-broken + .fn-fallback { display: grid; }
.fn-fallback span {
  font-family: var(--font-display); font-weight: 800; font-size: clamp(40px, 7vw, 64px);
  letter-spacing: var(--tr-tight); color: var(--n-400);
}
.fn-copy { display: flex; flex-direction: column; justify-content: center; }
.fn-copy .kicker { margin-bottom: var(--s-4); }
.fn-copy p.fn-note {
  font-size: var(--t-lg); line-height: var(--lh-snug); color: var(--ink);
  max-width: 42ch; font-family: var(--font-display); font-weight: 500;
}
.fn-sign {
  margin-top: var(--s-6); font-family: var(--font-display); font-weight: 700;
  color: var(--ink);
}
.fn-sign small {
  display: block; margin-top: 2px; font-family: var(--font-mono); font-weight: 500;
  font-size: var(--t-xs); letter-spacing: var(--tr-caps); text-transform: uppercase;
  color: var(--ink-3);
}

/* ---- hands photography bands ------------------------------------------------
   One large photograph, full bleed, never a grid. The copy sits on a soft
   bone scrim (manifest.json, the luminance map the other agent is writing,
   is not on disk yet, so every band defaults to the documented fallback:
   copy on the left, scrim under it). Warm-photo idiom matches img/photo/mod:
   object-fit cover, rounded on the two bands that are not full-bleed, the
   site's own `.grain` overlay for texture, `loading="lazy"` on every one but
   the first to use this pattern. */
.hb { position: relative; isolation: isolate; overflow: hidden; background: var(--n-900); min-height: clamp(360px, 52vw, 620px); }
.hb-photo {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block;
}
.hb-scrim {
  position: absolute; inset: 0; z-index: 1; pointer-events: none;
  /* left-to-right bone wash: the documented fallback for a missing luminance
     map. `data-side="right"` flips it, for the day the manifest lands and a
     band's own top-left turns out to be dark rather than light. */
  background: linear-gradient(90deg, rgba(247,243,238,.88) 0%, rgba(247,243,238,.62) 32%, rgba(247,243,238,0) 62%);
}
.hb[data-side="right"] .hb-scrim { background: linear-gradient(270deg, rgba(247,243,238,.88) 0%, rgba(247,243,238,.62) 32%, rgba(247,243,238,0) 62%); }
.hb-in {
  position: relative; z-index: 2; min-height: inherit; display: flex; align-items: center;
  padding-block: clamp(48px, 7vw, 96px);
}
.hb-copy { max-width: 30ch; }
.hb[data-side="right"] .hb-in { justify-content: flex-end; }
.hb[data-side="right"] .hb-copy { max-width: 32ch; text-align: right; margin-left: auto; }
.hb-copy .kicker {
  font-family: var(--font-mono); font-size: var(--t-xs); font-weight: 600;
  letter-spacing: var(--tr-caps); text-transform: uppercase; color: var(--o-800);
}
.hb-copy h2 { margin-top: var(--s-3); font-size: var(--t-2xl); color: var(--n-900); text-wrap: balance; }
.hb-copy p { margin-top: var(--s-4); font-size: var(--t-base); color: var(--n-700); max-width: 42ch; }

/* the floating schedule mock: a real UI card (glass, mono labels), the
   product itself, set down on top of a photograph the way a phone sits on a
   counter. This is the one place a hands band carries product chrome, and it
   is deliberate: everything else in the band is the restaurant; this card is
   delEat. */
.hb-card {
  position: absolute; z-index: 3; width: min(280px, 74vw);
  padding: var(--s-4) var(--s-5); border-radius: var(--r-4);
  background: color-mix(in srgb, var(--surface) 74%, transparent);
  border: 1px solid rgba(0,0,0,.08);
  -webkit-backdrop-filter: blur(18px) saturate(1.6); backdrop-filter: blur(18px) saturate(1.6);
  box-shadow: var(--lift-3);
  transition: transform var(--d-2) var(--snap);
}
@media (hover: hover) and (pointer: fine) { .hb-card:hover { transform: translateY(-3px); } }
.hb-card .lab {
  font-family: var(--font-mono); font-size: 10px; font-weight: 600;
  letter-spacing: var(--tr-caps); text-transform: uppercase; color: var(--ink-3);
}
.hb-card .rows { margin-top: 8px; display: grid; gap: 5px; }
.hb-card .rows div { display: flex; justify-content: space-between; gap: var(--s-4); font-size: var(--t-sm); }
.hb-card .rows span { color: var(--ink-2); font-weight: 500; }
.hb-card .rows b { font-family: var(--font-mono); color: var(--ink); font-weight: 600; }
.hb-card .fine { display: block; margin-top: 9px; font-size: 11px; color: var(--ink-3); }
@media (max-width: 640px) {
  .hb { min-height: 420px; }
  .hb-scrim { background: linear-gradient(180deg, rgba(247,243,238,.9) 0%, rgba(247,243,238,.72) 42%, rgba(247,243,238,.2) 100%); }
  .hb[data-side="right"] .hb-scrim { background: linear-gradient(180deg, rgba(247,243,238,.9) 0%, rgba(247,243,238,.72) 42%, rgba(247,243,238,.2) 100%); }
  /* The card cannot float over the photo here -- there is no room. It joins
     the copy in normal flow instead, stacked below it. */
  .hb-in { flex-direction: column; align-items: flex-start; padding-bottom: var(--s-6); }
  .hb[data-side="right"] .hb-in { justify-content: flex-start; }
  .hb-copy, .hb[data-side="right"] .hb-copy { text-align: left; margin-left: 0; }
  .hb-card { position: static; margin-top: var(--s-5); width: 100%; max-width: 340px; }
}

/* ---- moment.photo: the kitchen and the delivery run -------------------------
   Same two-column anatomy as the existing .moment sections, but the stage is
   a photograph instead of a device, since these are physical moments, not screens,
   so they get the warm idiom (rounded, grained) rather than a pad-rig. */
.moment.photo .moment-stage { padding: 0; background: transparent; box-shadow: none; }
/* width:100% is load-bearing, not decorative: with only aspect-ratio and an
   auto width, the frame is meant to stretch to its grid/flex track the same
   way .pad-box does, and that stretch quietly fails to resolve whenever the
   frame's own child is an absolutely-positioned <img> (.hb-frame img, just
   below) rather than a normal-flow element -- an absolutely-positioned
   child contributes nothing to shrink-to-fit sizing, and shrink-to-fit is
   what a stretch that has not resolved falls back to, collapsing the frame
   to 0 x 0. An explicit width sidesteps that stretch/aspect-ratio
   interaction instead of relying on it. */
.hb-frame { position: relative; width: 100%; border-radius: var(--r-6); overflow: hidden; box-shadow: var(--lift-4); aspect-ratio: 4 / 5; }
.hb-frame img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block; transition: transform var(--d-4) var(--glide); }
@media (hover: hover) and (pointer: fine) { .hb-frame:hover img { transform: scale(1.035); } }
@media (max-width: 900px) { .hb-frame { aspect-ratio: 16 / 10; } }
@media (prefers-reduced-motion: reduce) { .hb-frame img, .fn-photo, .hb-card { transition: none; } }
/* a <video> in a hands frame or a scrim band shares the exact photo rules
   above (`.hb-photo`, `.hb-frame img`) by class, not by tag -- this only
   covers what a <video> needs and an <img> does not. */
.hb-frame video, .hb-photo { background: var(--n-800); }
@media (hover: hover) and (pointer: fine) { .hb-frame:hover video { transform: scale(1.035); } }

/* ---- moment.photo on the real two-column grid: no dead card below a short
   photo --------------------------------------------------------------------
   `.hb-frame`'s fixed aspect-ratio (above) sizes the stage off its OWN
   width, never off the copy column next to it, so a copy block taller than
   that fixed-aspect height left bare `--bone` section ground showing below
   the frame (worst on the kitchen chapter, whose copy runs three points
   deep). Scoped to >920px, the one range where `.moment-in` is actually the
   two-column grid (it collapses to one column at 920px and below, and
   #moment-rail's own max-width:700px rules already give the mobile card its
   own working fix) -- so this never touches the tablet or phone
   presentations. `align-items: stretch` gives `.moment-stage` the row's
   real height (the copy column's, since copy is the taller of the two);
   `height: 100%` on `.hb-frame` then fills that instead of the fixed
   aspect-ratio; `object-fit: cover` on the video (the img rule above
   already covers the picture/img chapters) keeps the frame full without
   distorting the footage. */
@media (min-width: 921px) {
  .moment.photo .moment-in { align-items: stretch; }
  .moment.photo .hb-frame { aspect-ratio: auto; height: 100%; }
  .moment.photo .hb-frame video {
    position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block;
    transition: transform var(--d-4) var(--glide);
  }
}

/* ---- vband: a full-bleed looping video band -------------------------------
   Reuses `.hb`'s whole anatomy (photo layer, grain, bone scrim, floating
   copy) -- the only difference from a hands band is the fixed viewport-
   relative height a moving image wants rather than a photo's clamp(), and
   no `.hb-card` (a vband carries one short line, not a product mock). */
.vband { min-height: 62vh; }
.vband .hb-copy { max-width: 34ch; }
@media (max-width: 700px) { .vband { min-height: 48vh; } }

/* ---- looped background video: mp4/webm sources are mounted by JS only once
   the element nears the viewport (js/site.js) -- the element itself carries
   no <source> at parse time, just a poster, so a visitor who never scrolls
   there never fetches a frame of it. Until JS mounts it, the poster fills
   the frame exactly like a plain photo would. */
video.loop { object-fit: cover; }

/* ---- till-loop's inset was removed from #screens: with only two grid
   tracks defined on .showcase (300px tab column, 1fr stage column) a third
   top-level child pushed the real stage into the narrow tab column, where
   the scale-to-fit produced a tiny dashboard mock, and its caption sat
   under the sticky tab list while scrolling. .showcase now has exactly the
   two children its grid expects: `.sc-tabs` and the stage+caption wrapper. */

/* ---- #screens: the dashboard mock is laid out at a real desktop width
   (`.sc-fit`, `--sc-w`) and scaled down to fit whatever column it lands in --
   the same idea as `#crt-vp` above. Container queries inside resolve
   against the unscaled width, so a phone shows a small, faithful five-tab
   dashboard instead of the rail reflowing into a stack of bare sidebar
   icons. js/site.js measures the wrapper and sets `--sc-s`; the wrapper's
   own height is set in the same measurement, since a transform does not
   change the layout box it is applied to. */
.sc-fit-wrap { position: relative; overflow: hidden; border-radius: var(--r-4); background: var(--bone); }
.sc-fit {
  position: absolute; top: 0; left: 0;
  width: var(--sc-w, 900px);
  transform-origin: 0 0;
  transform: scale(var(--sc-s, 1));
}
.sc-fit .sc-panel { border-radius: 0; box-shadow: none; }

/* ---- Ask delEat -------------------------------------------------------------
   The product layer set down on bone: a glass card (the `.sv-pills` recipe,
   translucent surface, blur, hairline), mono labels and Inter prose, never a
   chat bubble. A caret blinks while a question types; the answer commits at
   once under reduced motion. */
.ask-card {
  position: relative; margin-top: var(--s-6);
  background: color-mix(in srgb, var(--surface) 72%, transparent);
  border: 1px solid var(--line-soft);
  border-radius: var(--r-6); padding: clamp(24px, 3.4vw, 40px);
  -webkit-backdrop-filter: blur(20px) saturate(1.5); backdrop-filter: blur(20px) saturate(1.5);
  box-shadow: var(--lift-3);
}
.ask-q { display: flex; align-items: flex-start; gap: var(--s-3); }
.ask-q-mark {
  flex: none; display: grid; place-items: center; width: 26px; height: 26px;
  margin-top: 2px; border-radius: 50%; background: var(--n-900); color: var(--bone);
  font-family: var(--font-mono); font-size: 12px; font-weight: 700;
}
#ask-question {
  font-family: var(--font-display); font-weight: 700; font-size: var(--t-lg);
  color: var(--ink); letter-spacing: var(--tr-head); min-height: 1.3em;
}
.ask-caret {
  display: inline-block; width: .48em; height: .82em; vertical-align: -.1em;
  background: var(--o-500); margin-left: .06em;
  animation: heroBlink 1.05s steps(1) infinite;
}
.ask-caret[hidden] { display: none; }
.ask-a {
  margin-top: var(--s-5); padding-top: var(--s-5); border-top: 1px solid var(--line-soft);
  opacity: 0; transform: translateY(6px);
  transition: opacity var(--d-3) var(--glide), transform var(--d-3) var(--glide);
}
.ask-a.is-on { opacity: 1; transform: none; }
#ask-a-text { font-size: var(--t-base); color: var(--ink-2); max-width: 62ch; line-height: var(--lh-body); }
#ask-a-text b { color: var(--ink); font-weight: 650; }
.ask-table { margin-top: var(--s-4); width: 100%; max-width: 480px; border-collapse: collapse; }
.ask-table[hidden] { display: none; }
.ask-table th, .ask-table td { padding: 7px 10px; text-align: left; font-size: var(--t-sm); border-bottom: 1px solid var(--line-soft); }
.ask-table thead th { font-family: var(--font-mono); font-size: 10.5px; font-weight: 600; letter-spacing: .07em; text-transform: uppercase; color: var(--ink-3); }
.ask-table td.n, .ask-table th.n { text-align: right; font-family: var(--font-mono); font-variant-numeric: tabular-nums; color: var(--ink); }
.ask-table tbody tr:last-child td { border-bottom: 0; }
.ask-chips { margin-top: var(--s-6); display: flex; flex-wrap: wrap; gap: 8px; }
.ask-chip {
  font: 500 var(--t-sm)/1 var(--font-ui); padding: 10px 15px; border-radius: 999px;
  background: var(--surface); border: 1px solid var(--line); color: var(--ink-2);
  cursor: pointer; transition: background var(--d-1) var(--snap), color var(--d-1) var(--snap),
    border-color var(--d-1) var(--snap), transform var(--d-1) var(--snap);
}
.ask-chip:hover { border-color: var(--ink-3); color: var(--ink); }
.ask-chip:active { transform: translateY(1px) scale(.98); }
.ask-chip.on { background: var(--n-900); border-color: var(--n-900); color: var(--bone); }
.ask-chip:focus-visible { outline: 2px solid var(--o-600); outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) {
  .ask-caret { animation: none; opacity: 1; }
  .ask-a { transition: none; }
  .ask-chip { transition: none; }
}

/* ---- evidence strip ----------------------------------------------------------
   Quiet on purpose: mono labels, a hairline top border, no card, no icons
   doing the talking. Sits between the CTA band and the footer. */
.evid { border-top: var(--hair); background: var(--n-900); }
.evid-in {
  display: flex; flex-wrap: wrap; gap: var(--s-3) var(--s-6); align-items: center; justify-content: space-between;
  padding-block: var(--s-5);
}
.evid-line {
  display: flex; flex-wrap: wrap; gap: var(--s-3) var(--s-6); align-items: center;
  font-family: var(--font-mono); font-size: var(--t-xs); color: var(--ui-ink-3);
}
.evid-line a { color: #B5B2AE; }
.evid-line a:hover { color: var(--bone); }
.evid-line span.evid-sep { opacity: .5; }

/* ---- status.html -------------------------------------------------------------
   Borrows css/site.css and css/base.css wholesale; these are the handful of
   classes the page needs that nothing else on the site already provides. */
.status-list { margin-top: var(--s-6); display: grid; gap: 1px; background: var(--line-soft); border: var(--hair); border-radius: var(--r-4); overflow: hidden; }
.status-row { display: flex; align-items: center; justify-content: space-between; gap: var(--s-4); padding: var(--s-4) var(--s-5); background: var(--surface); }
.status-row b { font-weight: 600; color: var(--ink); }
.status-ok { display: flex; align-items: center; gap: 8px; font-family: var(--font-mono); font-size: var(--t-xs); color: var(--ok-ink); }
.status-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--ok); flex: none; }
.status-note {
  margin-top: var(--s-6); padding: var(--s-5); border-radius: var(--r-4);
  background: var(--surface-2); border: var(--hair); color: var(--ink-2); font-size: var(--t-sm);
}

/* ============================================================================
   MOBILE SCROLL PASS (≤700px only). Desktop (min-width:901px) is untouched --
   nothing in this block loads above 700px, and nothing above it in the file
   was edited. The strategy: turn the sibling card grids that used to stack
   one-per-screen into swipeable horizontal rails (native momentum + snap, a
   small "Swipe · n of n" mono line above each one, updated on scroll), merge
   the five .moment sections into one rail, cap a few tall interactive blocks,
   and tighten the section rhythm everywhere else. See index.html's
   #moment-rail / #things-rail and js/site.js's rail affordance code.
   ========================================================================= */

/* ---- default-hidden, mobile-only elements --------------------------------
   These exist in the markup at every width (so no HTML differs by
   viewport), but they are only ever meant to be seen ≤700px. Hiding them by
   default here -- outside any media query, and before the ≤700 block below
   so its own display rules still win at mobile width -- is what keeps
   desktop pixel-identical. A rail affordance line is inserted by JS only
   once the mobile media query already matches, so it needs no such default;
   a static element like the things-rail does. */
.ask-a-more {
  display: none;
}

@media (max-width: 700px) {

  /* ---- section rhythm, home page only --------------------------------------
     2026-09: this used to tighten --sec-y here (a smaller clamp), then
     widen it again (a bigger one), on the theory that the home page's
     twenty-plus chapters needed a mobile rhythm of their own either way.
     Both passes missed that a `.section`'s top AND bottom padding both
     read this variable, so any page-scoped value here doubles up at every
     seam. Removed: the home page reads the exact same mobile --sec-y as
     every other page (css/tokens.css), no exception. */
  .hb { min-height: min(360px, 56vh); }

  /* ---- rail affordance ("Swipe · n of n"), inserted by js/site.js -------- */
  .rail-affordance {
    display: flex; align-items: center; gap: 5px; margin-top: 10px;
    font-family: var(--font-mono); font-size: 11px; letter-spacing: .04em;
    text-transform: uppercase; color: var(--ink-3);
  }
  .rail-affordance b { color: var(--ink-2); font-weight: 700; }
  .on-dark .rail-affordance, .rail-affordance.on-dark { color: rgba(247,243,238,.6); }
  .on-dark .rail-affordance b { color: var(--bone); }
  [hidden] + .rail-affordance { display: none; }

  /* ---- shared rail mechanics: rooms, module tiles, plans, news, service
     list, module index cards. Each one keeps its own card component --
     only the container's layout mode changes, from a stacking grid to a
     horizontal flex row with scroll-snap. Specificity, not !important, is
     what keeps [hidden] panels (the rooms back-of-house grid, the inactive
     sv-panel) actually hidden -- see .rooms-grid[hidden] and .sv-panel[hidden]
     above, both more specific than the plain-class rules below. */
  .rooms-grid, .news {
    display: flex;
    flex-wrap: nowrap;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scroll-snap-type: x mandatory;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    gap: 12px;
    min-width: 0;
    padding-bottom: 4px;
  }
  .rooms-grid::-webkit-scrollbar, .news::-webkit-scrollbar { display: none; }

  .rooms-grid > .room, .news > .news-card {
    flex: 0 0 82%;
    scroll-snap-align: start;
    align-self: stretch;
  }

  /* ---- rail cards must not depend on the scroll-reveal observer ---------
     [data-reveal] (css/base.css) starts every marked element at opacity:0
     until an IntersectionObserver sees it cross the *vertical* viewport
     (js/site.js). A card sitting three or four card-widths to the right in
     one of these horizontal rails has no vertical problem but is still
     entirely outside the viewport on the X axis, so it never intersects and
     stays invisible -- a 2.5s sitewide safety net eventually forces it
     back, but a visitor who scrolls straight to a rail and swipes within
     that window sees mostly blank cards. Rail children just show up
     already-in, the same guarantee the codebase already gives no-JS
     visitors above. 2026-09: .plans, .bento-wall, .sv-list and .modcards
     dropped out of the rail family (see the base.css note above) -- they
     are plain stacking grids again, so their [data-reveal] children go back
     through the ordinary vertical observer like everything else on the
     page. #moment-rail never scrolls sideways either now; its own children
     get the same "just show up" treatment below for a different reason
     (see the ".moment stacks, never rails" block), not because it is a
     horizontal rail. */
  .rooms-grid [data-reveal], .news [data-reveal] {
    opacity: 1; transform: none; transition: none;
  }

  /* ---- photo bands run edge to edge on phones ----------------------------
     .hb bands already are (they are section-level elements, never inside a
     .wrap), but a photo sitting inside a .wrap/.wrap-wide column -- the
     "which one are you" venue photo -- still carried the section's own side
     gutter plus a rounded corner, reading as a framed inset rather than a
     photograph. Pull each one out by exactly the gutter it is sitting
     inside (no intermediate ancestor adds its own padding, so one negative
     margin reaches the viewport edge) and drop the corner radius and shadow
     that only made sense with a margin around it. Device frames (.pad,
     .phone) and real cards (.hb-frame's own card use inside #moment-rail,
     .bx, .mm2-feat-img) are exempt by not being targeted here. */
  .sv-photo {
    margin-inline: calc(-1 * var(--gutter)); width: calc(100% + 2 * var(--gutter));
    border-radius: 0;
  }
  /* ---- chapters stack, never rail (2026-09) ------------------------------
     #moment-rail used to become one swipeable strip here -- giant type, one
     chapter per screen, the next chapter's edge always peeking in, cards
     hidden entirely. That was the single biggest complaint in hand: "the
     mobile website is a complete mess". Decision: on phones nothing scrolls
     sideways except the three galleries (highlights, what just shipped,
     which one are you). Every .moment chapter is a plain, full-width block
     again, and reads in one fixed order -- kicker, headline, lede, media,
     cards -- with no exception for a `.flip` chapter. The 920px rule above
     (`.moment-in, .moment.flip .moment-in { grid-template-columns: 1fr }`,
     `.moment.flip .moment-copy { order: 0 }`) already puts copy before
     stage in document order at this width; nothing here has to repeat it,
     only add the type scale and card grid the brief calls for. */
  #moment-rail { display: block; }
  #moment-rail > .moment { padding-block: clamp(16px, 4.4vw, 26px); }
  #moment-rail > .moment .moment-copy .lede { max-width: none; }
  .moment-copy .kicker, .eyebrow { font-size: 11px; }
  .moment-copy h2 { font-size: clamp(30px, 8.5vw, 38px); }
  .moment-copy .lede { font-size: 17px; }
  /* the fixed mobile order is kicker, headline, lede, media, cards -- the
     three-line .points list said the same thing the plus-bento cards below
     the stage already say, just in prose instead of a card; keeping both
     read as repetition once every chapter is stacked full length instead of
     hidden behind a swipe. */
  #moment-rail > .moment .points { display: none; }

  /* ---- rooms: tab switch still drives which grid is in the rail ---------- */
  .rooms-head { flex-direction: column; align-items: flex-start; gap: var(--s-4); }
  .room-cap b { font-size: 18px; }

  /* ---- the things: the desktop float field of drifting drawn objects
     stays on phones too -- .fo already has its own ≤720px pass (wider
     objects, the far/blurred layer culled for performance). Only the
     section padding tightens here to match the rest of the mobile rhythm. */
  .float-in { padding-block: clamp(32px, 7vw, 56px); }

  /* ---- Ask delEat: the answer table starts collapsed to 3 rows ----------- */
  .ask-table.is-collapsed tbody tr:nth-child(n+4) { display: none; }
  .ask-a-more {
    display: none; margin-top: 10px; font: 600 12px/1 var(--font-mono);
    letter-spacing: .04em; text-transform: uppercase; color: var(--accent-text);
    background: none; border: 0; padding: 6px 0; cursor: pointer;
    min-height: 32px;
  }
  .ask-a-more[hidden] { display: none !important; }
  #ask-a-table:not([hidden]) ~ .ask-a-more { display: block; }

  /* ---- FAQ: closed by default on mobile (js/site.js removes `open`) ------ */
  .faq details summary { min-height: 44px; display: flex; align-items: center; }

  /* ---- hero: headline, one lede line, both buttons, a 42vh machine, the
     two mono trust lines flow as one ---------------------------------------- */
  .hero-visual { height: min(90vw, 400px); }
  .hero-note, .hero-trust { display: inline; margin-top: 0; }
  .hero-note::after { content: " \00b7 "; }
  .hero-trust { margin-left: 0; }

  /* ---- the pocket: same pin choreography, a shorter scroll to get there -- */

  /* ---- the outlet tree: two columns of outlets instead of one ------------ */
  .tree-kids { grid-template-columns: repeat(2, minmax(0,1fr)); }

  /* ---- the sample receipt: same lines, a tighter typeset ------------------ */
  .receipt { font-size: 10.5px; line-height: 1.45; padding: var(--s-5) var(--s-4) calc(var(--s-5) + var(--tear)); }
  .receipt hr { margin: 7px 0; }

  /* ---- the decorative section illustrations (serve, one-system, mid-shift):
     kept, just smaller -- the words next to them do the explaining. ------- */
  .head-art { max-width: 220px; }

  /* ---- the stat strip: same four numbers, less padding around them ------- */
  .stat { padding: var(--s-4) var(--s-3); }

  /* ---- the module rail (#everything): the illustration inside each card
     stops driving the whole rail's height -- every card in a flex row
     stretches to the tallest one otherwise. ---------------------------------- */
  .bento-wall > .bx { min-height: 0; }
  .bento-wall .bx-obj { max-height: 96px; width: auto; margin-inline: auto; }

  /* ---- the pocket: pulled in once more -- the pin choreography still
     plays in full, just over a shorter run of scroll. ----------------------- */
  .ph.pinned .ph-scroller { height: 95vh; }

  /* ---- the decorative section illustrations: off on mobile, the copy next
     to them already carries the point. ------------------------------------- */
  .head-art { display: none; }

  /* ---- the screens showcase: the caption under the stage repeats what the
     active tab already said above it. -------------------------------------- */
  .sc-cap { display: none; }

  /* ---- "read more": a plain native <details>, closed by default here, that
     collapses into ordinary flowing text again above 700px (the summary
     hides and `display:contents` drops its own box, so nothing about the
     desktop paragraph run changes). Used where a section's copy runs past
     about six lines -- currently the offline band's second paragraph. ------ */
  details.more-copy summary {
    display: inline-flex; align-items: center; gap: 6px; margin-top: var(--s-3);
    cursor: pointer; list-style: none; min-height: 32px;
  }
  details.more-copy summary::-webkit-details-marker { display: none; }
  details.more-copy[open] summary .arw { transform: rotate(180deg); }

  /* ---- tap targets ---------------------------------------------------- */
  .ask-chip, .sc-tab, .modcard, .bx, .plan .btn { min-height: 44px; }
}

/* `<details class="more-copy">` is how the offline band's second paragraph
   collapses behind "Read more" on mobile -- closed by default, so the
   browser's own UA rule (details:not([open]) > :not(summary) { display:
   none }) hides that paragraph everywhere unless something says otherwise.
   Above 700px the summary hides and the paragraph is forced back on,
   regardless of the (irrelevant, above 700px) open/closed state. */
@media (min-width: 701px) {
  details.more-copy summary { display: none; }
  details.more-copy > :not(summary) { display: block; }
}

@media (max-width: 700px) {
  /* ---- the pocket: one more pass -- the choreography is proportional to
     the scroller's own height, so a shorter one just means less scroll to
     see all three poses, not a different animation. ----------------------- */
  .ph.pinned .ph-scroller { height: 78vh; }
}

@media (max-width: 700px) {
  /* ---- short-label facts lists (bill-band, group chains): back to a label +
     value row instead of two stacked lines -- opt-in via .facts-tight so
     the longer labels elsewhere (pricing.html's hardware list) keep their
     safe, wrap-free stacked layout. ----------------------------------------- */
  .facts-tight > div { grid-template-columns: 96px minmax(0,1fr); gap: var(--s-3); }
  .facts-tight dt { font-size: 11px; }
}

@media (max-width: 700px) {
  /* ---- product.html's index layout: the column beside the (hidden ≤980px)
     jump-nav needs min-width:0 like any grid item that holds a row-based
     rail -- without it, a flex row's intrinsic content width can push the
     whole grid column wider than its own track and the page scrolls
     sideways. Same fix as .modcards itself, one level up. -------------- */
  .prod-layout > div { min-width: 0; }
}

/* ============================================================================
   #product-stage -- Toast-style horizontal product carousel. Real studio
   photography (img/stage/*) standing in for the old built-not-shot rooms;
   the floating UI still borrows .rm-card and its children verbatim.
   ========================================================================= */
.ps-panel[hidden] { display: none; }
.ps-track {
  display: flex; gap: clamp(12px, 1.6vw, 20px); margin-top: var(--s-7);
  overflow-x: auto; scroll-snap-type: x mandatory;
  scrollbar-width: none; -ms-overflow-style: none;
  padding-bottom: 2px; scroll-padding-inline: var(--gutter);
}
.ps-track::-webkit-scrollbar { display: none; }
.ps-track:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; }

.ps-card {
  position: relative; flex: 0 0 auto; width: min(300px, 27vw);
  aspect-ratio: 4 / 5; border-radius: var(--r-5); overflow: hidden; isolation: isolate;
  scroll-snap-align: start; display: block; text-decoration: none; color: #FFFFFF;
  background: var(--o-600); box-shadow: var(--lift-2);
  opacity: 0; translate: 0 16px;
  transition: transform var(--d-3) var(--glide), box-shadow var(--d-3) var(--glide);
}
.ps-panel.is-in .ps-card, html:not(.js) .ps-card { animation: roomIn .65s var(--glide) both; animation-delay: calc(var(--i, 0) * 70ms); }
@keyframes roomIn { to { opacity: 1; translate: 0 0; } }
@media (min-width: 901px) { .ps-card { width: calc((100% - 3 * clamp(12px, 1.6vw, 20px)) / 3.4); } }
@media (max-width: 900px) { .ps-card { width: 60vw; } }
@media (max-width: 700px) { .ps-card { width: 84vw; } }
.ps-card:hover { transform: translateY(-5px); box-shadow: var(--lift-4); }
.ps-card:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

/* the stage photograph fills the card; the object sits small in the native
   1600x2000 frame (the ledge is real at ~75% down it already), so a modest
   scale anchored near the ledge is what pushes the object up to reading as
   the lower two-thirds of the render, Toast's own crop. */
.ps-media {
  position: absolute; inset: 0; z-index: 0; width: 100%; height: 100%;
  object-fit: cover; object-position: 50% 78%;
  transform: scale(1.26); transform-origin: 50% 80%;
  transition: transform var(--d-3) var(--glide);
}
.ps-card:hover .ps-media { transform: scale(1.32); }
.ps-card::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 48%; z-index: 1;
  background: linear-gradient(to top, rgba(0,0,0,.6), rgba(0,0,0,0)); pointer-events: none;
}
.ps-cap { position: absolute; left: 0; right: 0; bottom: 0; z-index: 2; display: block; padding: var(--s-5) var(--s-4) var(--s-4); }
.ps-cap b { display: block; font-family: var(--font-display); font-weight: 700; font-size: clamp(17px, 1.3vw, 21px); letter-spacing: -.01em; line-height: 1.15; }
.ps-cap span { display: block; margin-top: 5px; font-size: var(--t-xs); line-height: 1.4; opacity: .92; }

/* ---- the pager: a row of dots, the active one a pill ----------------------- */
.ps-pager { display: flex; align-items: center; justify-content: center; gap: 7px; margin-top: var(--s-5); }
.ps-pager button {
  width: 7px; height: 7px; border-radius: 999px; border: 0; padding: 0;
  background: var(--n-200); cursor: pointer; flex: none;
  transition: width var(--d-2) var(--snap), background var(--d-2) var(--snap);
}
.ps-pager button[aria-current="true"] { width: 22px; background: var(--ink); }
.ps-pager button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }


/* ---- tap-loop, a small floating clip beside the payments copy ------------- */
.bill-tap {
  position: relative; margin-top: var(--s-6); width: min(260px, 60%);
  aspect-ratio: 4 / 3; border-radius: var(--r-4); overflow: hidden; box-shadow: var(--lift-2);
}
.bill-tap video { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block; }
.bill-tap-cap {
  position: absolute; left: 10px; bottom: 9px; z-index: 1;
  font-family: var(--font-mono); font-size: 10px; font-weight: 600;
  letter-spacing: var(--tr-caps); text-transform: uppercase; color: var(--bone);
  text-shadow: 0 1px 6px rgba(0,0,0,.55);
}

/* ============================================================================
   Round 54: THE PHONE CHROME. One sticky bar, in the page's own ground.

   Proven on Parth's iPhone (iOS 26) with striped test pages: Safari paints a
   solid "pocket" behind the status bar or the bottom toolbar whenever a
   fixed or sticky full-width element sits at that edge; the pocket takes
   that element's background, and a transparent wrapper is filled BLACK.
   With nothing pinned at an edge the page runs edge to edge under the
   floating bar. Rounds 53l, 53n, 53o (the CTA part), 53p and 53r each
   worked around that without a bar; they are replaced by this block.

   Apple's answer, and now this page's: one ground per page (css/base.css,
   `html[data-pg]`), and ONE sticky bar in that ground, opaque, no radius,
   no blur, no gradient, no opacity animation, starting at the very top with
   the status bar inside its own padding. The strip Safari paints above the
   bar is then the bar's own colour and cannot be seen.
     * a page with a local nav (the home page): the global .nav stays in
       flow and scrolls away; the local nav is the bar, solid black, the
       capsule (Round 55; both black bars carry it), the Menu chevron and a
       Start free pill;
     * every other page: the global .nav is the bar, solid eggshell, the ink
       wordmark, a Start free pill and the menu button. Its glass pill
       styling and the `over-dark` switch (js/site.js) are neutralised here.
   Nothing fixed or sticky may touch the BOTTOM edge on a phone: the floating
   Start-free pill (#sticky-cta) is gone from the site (Round 54, item 2).
   Every other fixed layer stays `visibility: hidden` while inactive (Round
   53o, kept below).

   Scoped to coarse pointers at the hamburger widths (1020px and under, the
   breakpoint where .nav-links already folds into the menu button), so a
   tablet in landscape with a trackpad keeps the desktop navs as they are.
   ========================================================================= */
/* Round 55, item A: the capsule that the phone's black local-nav bar shows
   (below). It carries the `hidden` attribute, but the site's own `img`
   rules outrank the UA's [hidden], so it is put away here explicitly and
   only the phone block turns it on. */
.local-nav .ln-cap { display: block; height: 30px; width: auto; flex: none; }

@media (any-pointer: coarse) {
  /* Round 53o, kept: nothing FIXED and full-width sits over the page unless
     it is actually on screen. The crossfade layer draws nothing on a
     one-ground phone page; closed sheets, their backdrop and the glow stop
     occupying the screen at opacity 0. */
  .ground34 { display: none !important; }
  .ch-sheet:not(.is-open), .ch-sheet-backdrop:not(.is-open) { visibility: hidden; }
  .siri-glow:not(.is-on):not(.is-static) { visibility: hidden; }
  /* Round 53q, kept: the Ask-delEat glow keeps off the status bar and the
     home indicator while it is live. */
  .siri-glow { top: env(safe-area-inset-top, 0px); bottom: env(safe-area-inset-bottom, 0px); }

  /* Round 54, item 1: an eggshell page paints its black sections itself.
     html.g34 makes every section transparent (css/apple.css) because on a
     desktop the fixed layer is the ground; that layer is hidden here. A
     black section that sits between eggshell sections is an inset tile:
     12px side margins, the large radius, clipped, so the page's eggshell
     ground runs past it on both sides. The closing dark sections (the last
     CTA band and the footer, nothing eggshell after them) stay full bleed.
     The home page is a dark page and is not touched by either rule. */
  html.g34:not([data-pg="dark"]) [data-ground="black"]:not(.hero13) { background-color: #000000 !important; }
  html:not([data-pg="dark"]) main > [data-ground="black"]:has(~ [data-ground="eggshell"]) {
    margin-inline: 12px; border-radius: var(--r-6); overflow: clip;
  }
}

@media (any-pointer: coarse) and (max-width: 1020px) {
  :root { --nav-h: calc(52px + env(safe-area-inset-top, 0px)); }
  html { scroll-padding-top: calc(64px + env(safe-area-inset-top, 0px)); }


  /* Round 55b (24 Sep 2026, T1/T2 on Parth's iPhone): a sticky bar, even with every
     other fixed layer removed, makes Safari 26 paint opaque pockets at BOTH edges;
     the same page with the bar in flow is clean. So the bars keep their look and
     scroll away. Never pin a nav on phones. */
  /* Round 55b: the design-system page's section nav was the one other sticky
     full-width element on phones; it scrolls with the page there. */
  html .modnav { position: static; }
  /* Round 55b: the Ask delEat glow is never fixed on phones (see js/site.js siriGlow) */
  html .siri-glow { position: absolute; bottom: auto; height: 100lvh; }
  /* ---- the global nav is the bar on every page without a local nav ---- */
  html .nav {
    position: relative; top: 0; z-index: 60;
    margin: 0; padding: env(safe-area-inset-top, 0px) 0 0;
    background: var(--bone); border: 0; border-radius: 0; box-shadow: none;
    transition: none;
    /* the bar is eggshell, so it takes the light-ground inks whatever the
       header's own .on-dark says */
    --ink: var(--n-900); --ink-2: var(--n-600); --ink-3: var(--n-500);
    --line: var(--n-200); --surface: var(--n-0); color: var(--ink);
  }
  html[data-pg="dark"] .nav {
    background: #000000;
    --ink: var(--bone); --ink-2: #A1A1A6; --ink-3: #6E6E73; --line: #313131; --surface: var(--n-800);
  }
  html .nav.is-stuck { padding-top: env(safe-area-inset-top, 0px); }
  html .nav .nav-in, html .nav.over-dark .nav-in, html .nav.is-stuck .nav-in {
    height: 52px; padding-inline: var(--gutter); gap: var(--s-3);
    background: none; border: 0; border-radius: 0; box-shadow: none;
    border-bottom: 1px solid var(--line);
    -webkit-backdrop-filter: none; backdrop-filter: none;
    transition: none;
  }
  /* the ink wordmark in the eggshell bar (Parth's call for the glass nav).
     Round 55, item A: the BLACK bar of a dark page carries the capsule
     itself (bone key, deep orange edge, the bite), 30px tall, left where the
     wordmark sits. The inverted wordmark it used to show is gone. */
  html .nav .cap-lg, html .nav.over-dark .cap-lg { display: block; height: 30px; width: auto; }
  html .nav .nav-trigger, html .nav .nav-links a, html .nav .nav-toggle,
  html .nav.over-dark .nav-trigger, html .nav.over-dark .nav-links a { color: var(--ink); }
  html .nav-cta .btn { height: 36px; padding-inline: 14px; font-size: var(--t-xs); }
  /* the pill reads "Start free" on the bar; the desktop keeps its full label */
  html .nav-cta .nav-cta-long { display: none; }
  html .nav-toggle span, html .nav-toggle span::before, html .nav-toggle span::after { background: var(--ink); }
  html .nav.is-open .nav-toggle span { background: transparent; }
  /* the open menu sheet starts where the bar ends, not at the old pill's 70px */
  html #nav.nav.is-open { position: fixed; top: 0; }
  html .nav.is-open .nav-in { background: none; }
  html .nav.is-open .nav-links {
    top: calc(52px + env(safe-area-inset-top, 0px));
    height: calc(100vh - 52px - env(safe-area-inset-top, 0px));
    height: calc(100dvh - 52px - env(safe-area-inset-top, 0px));
    border-radius: 0;
  }

  /* ---- a page with a local nav: the local nav is the bar ------------------ */
  html body:has(#localnav) .nav { position: relative; top: 0; }
  html .local-nav, html .local-nav:not(.is-menu) {
    position: relative; top: 0; z-index: 55;
    margin: 0; padding: env(safe-area-inset-top, 0px) 0 0;
    background: #000000; pointer-events: auto;
  }
  html .local-nav::before { display: none; }
  html .local-nav-in,
  html .local-nav.is-armed:not(.is-in) .local-nav-in,
  html .local-nav.is-in .local-nav-in,
  html .local-nav.is-menu .local-nav-in {
    min-height: 52px; height: 52px; max-width: none; margin: 0; padding: 0 var(--gutter);
    gap: var(--s-3);
    background: none; border: 0; border-radius: 0; box-shadow: none;
    border-bottom: 1px solid #313131;
    -webkit-backdrop-filter: none; backdrop-filter: none;
    opacity: 1; filter: none; transform: none; transition: none;
  }
  /* Round 55, item A: the capsule on the black bar, 30px, not the bone wordmark */
  html .local-nav .ln-cap { display: block; height: 30px; width: auto; flex: none; margin-right: auto; }
  html .local-nav .ln-links { display: none; }
  html .local-nav .ln-more { display: inline-flex; color: var(--bone); padding: 6px 4px; }
  html .local-nav .ln-cta { display: inline-flex; height: 36px; padding-inline: 14px; font-size: var(--t-xs); }
  html .local-nav.is-menu { position: fixed; top: 0; left: 0; right: 0; }
  html .local-nav .ln-sheet { left: 10px; right: 10px; margin-top: 4px; }
  /* Round 56, item 1: one header on the home page. Since 55b both bars scroll
     away, so the capsule, a menu button and Start free sat twice inside the
     first screen. On a phone the local nav and its sheet are not rendered at
     all; the global nav's menu covers navigation. Desktop keeps the local
     nav exactly as it is. */
  html #localnav, html #localnav .ln-sheet { display: none; }
}

/* Round 56, item 8: no white area under the footer on phones. On an eggshell
   page the html canvas is bone, so a black footer ended and Safari showed
   bone under the bottom bar. The page now ends in the canvas colour: the
   footer is light on eggshell pages (bone ground, the ink wordmark, --n-700
   text, --n-900 links, --n-200 hairlines, the same content), with room under
   it for the home indicator. The home page (dark) and every desktop keep the
   black footer. The markup is one (tools/build-nav.py, FOOTER); only the CSS
   is keyed on the page ground. The selector out-ranks the Round 54 rule that
   paints every [data-ground="black"] section black on an eggshell page. */
.foot .cap-foot-ink { display: none; }
@media (any-pointer: coarse) {
  html:not([data-pg="dark"]) footer.foot[data-ground="black"].on-dark {
    background-color: var(--bone) !important; color: var(--n-700);
    --ground: var(--bone); --ink: var(--n-900); --ink-2: var(--n-700); --ink-3: var(--n-700);
    --line: var(--n-200); --line-soft: var(--n-200); --hair: 1px solid var(--n-200);
    --accent-text: var(--o-800); --surface: var(--n-0);
    border-top-color: var(--n-200);
    padding-bottom: calc(var(--s-7) + env(safe-area-inset-bottom, 0px));
  }
  html:not([data-pg="dark"]) .foot .cap-foot { display: none; }
  html:not([data-pg="dark"]) .foot .cap-foot-ink { display: block; height: 26px; width: auto; }
  html:not([data-pg="dark"]) .foot h5, html:not([data-pg="dark"]) .foot .fine,
  html:not([data-pg="dark"]) .foot-base p, html:not([data-pg="dark"]) .foot-3d-credit { color: var(--n-700); }
  html:not([data-pg="dark"]) .foot a { color: var(--n-900); }
  html:not([data-pg="dark"]) .foot-base { border-top-color: var(--n-200); }
}

/* ═════════════════════════════════════════════════════════════════════════
   Round 27, item F: four Magic UI effects, ported.

   Parth asked for github.com/magicuidesign/magicui to be installed. It is a
   React + Tailwind component library and this site is zero-dependency static
   HTML by design, so it is NOT installed and nothing from it is vendored.
   Each component's source was read on GitHub and its MECHANISM rewritten in
   plain CSS (and, where it needs state, a few lines in js/site.js). Notes on
   each mechanism are in scratchpad/r10/archetypes27-notes.md.
   ═══════════════════════════════════════════════════════════════════════ */

/* ---- 1. progressive blur ------------------------------------------------
   magicui/progressive-blur.tsx stacks N absolutely-positioned layers, each
   with its own `backdrop-filter: blur(2^n px)` and a mask whose opaque
   window slides 12.5 percent per layer, so the blur RAMPS rather than
   stepping at one hard edge. Six layers here instead of eight: past about
   15px of backdrop blur on a 40px band the last two layers are doing no
   visible work and are two more compositor passes per frame. */
.pblur { position: absolute; left: 0; right: 0; pointer-events: none; }
.pblur > i {
  position: absolute; inset: 0; display: block;
  /* --pb-k scales the whole ramp for a band that sits ON an image rather
     than over a page: at full strength the hero photo's bottom quarter
     reads as a smear rather than a soft edge. */
  backdrop-filter: blur(calc(var(--b) * var(--pb-k, 1) * 1px));
  -webkit-backdrop-filter: blur(calc(var(--b) * var(--pb-k, 1) * 1px));
  --m: linear-gradient(var(--pb-dir, to bottom),
        rgba(0, 0, 0, 0) calc(var(--s) * 1%),
        #000 calc((var(--s) + 16.6) * 1%),
        #000 calc((var(--s) + 33.2) * 1%),
        rgba(0, 0, 0, 0) calc((var(--s) + 49.8) * 1%));
  -webkit-mask-image: var(--m); mask-image: var(--m);
}
.pblur > i:nth-child(1) { --b: .4;  --s: -16.6; z-index: 1; }
.pblur > i:nth-child(2) { --b: .9;  --s: 0;     z-index: 2; }
.pblur > i:nth-child(3) { --b: 1.9; --s: 16.6;  z-index: 3; }
.pblur > i:nth-child(4) { --b: 3.8; --s: 33.2;  z-index: 4; }
.pblur > i:nth-child(5) { --b: 7.5; --s: 49.8;  z-index: 5; }
.pblur > i:nth-child(6) { --b: 15;  --s: 66.4;  z-index: 6; }
.pblur.to-t { --pb-dir: to top; }

/* Round 29, item 1: the band under the nav is gone. The nav pill carries a
   backdrop-filter of its own (the round 22 material), and stacking a second,
   six-layer backdrop-filter directly beneath a translucent pill means the
   pill samples an already-blurred backdrop: on a phone that reads as the
   pill being blurred and its mark and wordmark smeared. One blur pass now,
   on the pill itself. `.pblur` is left only at the trailing edge of a
   horizontal rail; round 33 took it off the module hero photograph, where
   it smeared the photo's foot. Nothing in the stack may reach a control or a
   label, so no `.pblur` layer is above z-index 3 and the nav stays at 60. */
/* the bottom edge of a gallery or a rail */
.pblur-rail { bottom: 0; height: 30px; z-index: 3; }

@media (prefers-reduced-transparency: reduce), (prefers-contrast: more) {
  .pblur { display: none; }
}

/* ---- 2. card light (Round 72) ------------------------------------------
   Replaces the orange border beam and the Next card's orange shine border
   (Apple never runs a border). js/site.js gives every bento card, figure tile
   and the Next card a .clight layer and tracks the mouse into --mx/--my. A wide
   faint pool of light follows the pointer UNDER the words (the card isolates,
   the layer sits at -1), a 1px rim brightens only near the pointer, and the
   card lifts 2px on a spring (the site's card lift; where a card already has
   its own :hover lift in apple.css or modules.css, that rule wins). White light on dark cards; on white cards a soft
   graphite rim and the lift carry it. Reduced motion keeps the light and
   drops the lift. */
.cl-card {
  --mx: 50%; --my: 50%;
  isolation: isolate;
  transition: transform .55s cubic-bezier(.2, .8, .2, 1), box-shadow .55s cubic-bezier(.2, .8, .2, 1);
}
.clight {
  position: absolute; inset: 0; z-index: -1; border-radius: inherit; pointer-events: none;
  opacity: 0; transition: opacity .45s ease;
}
.clight::after {
  content: ""; position: absolute; inset: 0; border-radius: inherit;
  background: radial-gradient(460px circle at var(--mx) var(--my), rgba(255,255,255,.09), rgba(255,255,255,0) 60%);
}
.clight::before {
  content: ""; position: absolute; inset: 0; border-radius: inherit; padding: 1px;
  background: radial-gradient(240px circle at var(--mx) var(--my), rgba(255,255,255,.6), rgba(255,255,255,0) 70%);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor; mask-composite: exclude;
}
.cl-card.is-hot .clight { opacity: 1; }
.cl-card.cl-dark.is-hot { transform: translateY(-2px); box-shadow: 0 22px 44px -22px rgba(0,0,0,.9); }
.cl-card.cl-bright .clight::after {
  background: radial-gradient(460px circle at var(--mx) var(--my), rgba(255,255,255,.55), rgba(255,255,255,0) 60%);
}
.cl-card.cl-bright .clight::before {
  background: radial-gradient(240px circle at var(--mx) var(--my), rgba(20,20,20,.22), rgba(20,20,20,0) 70%);
}
.cl-card.cl-bright.is-hot { transform: translateY(-2px); box-shadow: 0 24px 48px -24px rgba(40,28,20,.35), 0 2px 6px rgba(40,28,20,.06); }
@media (prefers-reduced-motion: reduce) {
  .cl-card, .cl-card.is-hot { transform: none !important; transition: box-shadow .2s linear; }
}
@media (prefers-reduced-transparency: reduce), (prefers-contrast: more) {
  .clight { display: none; }
}

/* ---- 4. animated grid pattern ------------------------------------------
   magicui/animated-grid-pattern.tsx draws an SVG `<pattern>` of `M.5 H V.5 H
   W` lines and scatters N squares that fade in and out and jump to a new
   cell each cycle. The lines are a repeating gradient here (one paint, no
   SVG) and js/site.js re-seeds the squares on `animationiteration`, which is
   the same loop the component runs on a React state update. */
.grid-anim {
  position: absolute; inset: 0; z-index: 0; pointer-events: none;
  --g: 42px;
  background-image:
    linear-gradient(to right, rgba(0, 0, 0, .045) 1px, transparent 1px),
    linear-gradient(to bottom, rgba(0, 0, 0, .045) 1px, transparent 1px);
  background-size: var(--g) var(--g);
  /* Round 56, item 5: a second mask fades the grid out over the last 96px so
     no row of it can ever meet the section's clipped bottom edge as a line */
  -webkit-mask-image: radial-gradient(120% 100% at 70% 0%, #000 12%, rgba(0, 0, 0, .35) 58%, transparent 92%),
                      linear-gradient(to bottom, #000 calc(100% - 96px), transparent);
  -webkit-mask-composite: source-in;
  mask-image: radial-gradient(120% 100% at 70% 0%, #000 12%, rgba(0, 0, 0, .35) 58%, transparent 92%),
              linear-gradient(to bottom, #000 calc(100% - 96px), transparent);
  mask-composite: intersect;
}
.grid-anim > i {
  position: absolute; width: var(--g); height: var(--g);
  background: rgba(255, 107, 61, .1); opacity: 0;
  animation: gridSq 5s ease-in-out infinite;
  animation-delay: calc(var(--d) * 1s);
}
@keyframes gridSq { 0%, 100% { opacity: 0; } 40%, 60% { opacity: 1; } }
@media (prefers-reduced-motion: reduce) {
  .grid-anim > i { animation: none; opacity: .55; }
}
@media (prefers-contrast: more) { .grid-anim { display: none; } }

/* ---- Round 27, item D: support.html's "on every plan" list --------------- */
.inclist {
  list-style: none; margin: var(--s-7) 0 0; padding: 0;
  display: grid; gap: 10px; grid-template-columns: repeat(2, minmax(0, 1fr));
}
.inclist li {
  display: flex; gap: 12px; align-items: flex-start;
  background: var(--n-0); border-radius: var(--r-lg); padding: 16px 18px;
  box-shadow: 0 4px 14px rgba(0, 0, 0, .05), inset 0 0 0 1px rgba(0, 0, 0, .05);
}
.inclist .ic {
  flex: none; width: 20px; height: 20px; margin-top: 2px; color: #14683F;
  fill: none; stroke: currentColor; stroke-width: 2.2;
  stroke-linecap: round; stroke-linejoin: round;
}
.inclist span { display: block; font-size: 15px; line-height: 1.45; color: var(--ink-2); }
.inclist b {
  display: block; font-family: var(--font-display); font-weight: 600; font-size: 17px;
  line-height: 1.24; letter-spacing: var(--tr-head); color: var(--ink); margin-bottom: 2px;
}
@media (max-width: 700px) { .inclist { grid-template-columns: 1fr; } }

/* ---- Round 27, item D: the home page's support band --------------------- */
.sup-grid {
  display: grid; gap: 12px; margin-top: var(--bento-top);
  grid-template-columns: repeat(3, minmax(0, 1fr));
}
.sup-grid .offline-tile { min-height: 168px; }
.sup-grid .offline-tile b {
  font-family: var(--font-display); font-weight: 600; font-size: 21px;
  line-height: 1.2; letter-spacing: var(--tr-head);
}
.sup-grid .offline-tile p { font-size: 15px; line-height: 1.45; color: var(--nt-ink-2); margin: 0; }
.sup-more { margin-top: var(--s-6); }
@media (max-width: 860px) { .sup-grid { grid-template-columns: 1fr; } }

/* ═════════════════════════════════════════════════════════════════════════
   Round 28: the two vendored effects and the ported mesh gradient.
   js/vendor/liquid-logo/ (MIT, collidingScopes) draws the liquid metal over
   a logo; js/meshgrad.js is the native port of shadergradient's mesh, which
   is React and three.js upstream and therefore not installed. Both degrade
   to what was already there: no WebGL, no canvas, and the static logo or the
   CSS gradient underneath is what you see.
   ═══════════════════════════════════════════════════════════════════════ */
[data-liquid] { position: relative; display: inline-block; line-height: 0; }
[data-liquid] > img { display: block; transition: opacity .3s ease-out; }
[data-liquid].is-liquid > img { opacity: 0; }
.lq-canvas {
  position: absolute; inset: 0; pointer-events: none;
  opacity: 0; transition: opacity .45s ease-out;
  /* the shader's colour comes out of u_colorFactors, which are exponents on
     p.y rather than an RGB triple, so a specific hue is not something it can
     be asked for directly. The luminance IS the metal; the hue is pushed to
     the brand family here, at the presentation layer, rather than by editing
     a vendored MIT shader. */
  filter: sepia(1) hue-rotate(-22deg) saturate(2.6) brightness(1.04);
}
.lq-canvas.is-live { opacity: 1; }
@media (prefers-reduced-motion: reduce) {
  [data-liquid].is-liquid > img { opacity: 1; }
  .lq-canvas { display: none; }
}

/* the support hero's mark */
/* Round 29, item 3: the mark is the `<img>` itself now, not a span wrapping
   one, since the liquid canvas that needed the wrapper is gone. */
.lq-mark { display: block; margin-bottom: clamp(14px, 1.8vw, 24px);
           width: clamp(88px, 9.5vw, 136px); height: auto; }

/* the mesh gradient canvas: it sits under whatever CSS ground the element
   already has, so the ground is the fallback rather than a blank box */
.mgrad {
  position: absolute; inset: 0; width: 100%; height: 100%;
  display: block; z-index: 0; pointer-events: none;
  opacity: 0; transition: opacity .6s ease-out;
}
.mgrad.is-live { opacity: 1; }
.kb-hero .mgrad { inset: 0; border-radius: inherit; }
.pagehead.has-mesh { position: relative; isolation: isolate; overflow: hidden; }
.pagehead.has-mesh > .wrap { position: relative; z-index: 2; }
.pagehead.has-mesh .mgrad-scrim {
  position: absolute; inset: 0; z-index: 1; pointer-events: none;
  background: linear-gradient(105deg, rgba(0,0,0,.82) 0%, rgba(0,0,0,.62) 46%, rgba(0,0,0,.34) 100%);
}
@media (prefers-contrast: more) {
  .mgrad { display: none; }
  .pagehead.has-mesh .mgrad-scrim { background: #000; }
}

/* ═════════════════════════════════════════════════════════════════════════
   Round 31: the support photographs (img/photo/support/, NOTES.md).
   Placed the way Apple's support pages place their advisors: the picture
   stands on its own and no type ever sits on a face. One radius for all of
   them, --r-lg, and each "sizes" in tools/srcset.py states the width the
   file is drawn at inside its crop, not the width of the frame.
   ═══════════════════════════════════════════════════════════════════════ */
.sup-hero-photo, .sup-advisor-photo, .sup-photo { margin: 0; overflow: hidden; border-radius: var(--r-lg); }
.sup-hero-photo img, .sup-advisor-photo img, .sup-photo img {
  display: block; width: 100%; height: 100%; object-fit: cover;
}

/* support.html: the team, the column's full width under the lede. The
   ground behind the file is the tile ink, so the frame reads before it
   decodes rather than as a hole in the mesh. */
.sup-hero-photo {
  margin-top: clamp(28px, 3.6vw, 48px); aspect-ratio: 16 / 9; background: #1C1C1E;
  box-shadow: 0 24px 60px rgba(0,0,0,.35);
}
/* the phone crop is 4:5; the headset wearer's face is at 36 percent across
   the file, and 22 percent of the free travel centres it in the frame
   with her laptop and hands under it and no one cut at the edge */
@media (max-width: 700px) {
  .sup-hero-photo { aspect-ratio: 4 / 5; }
  .sup-hero-photo img { object-position: 22% 50%; }
}

/* support.html: the advisor, a row of its own under the three cards, with
   the one line the page already says about him beside the picture */
.sup-advisor {
  display: grid; grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
  gap: clamp(28px, 4.5vw, 64px); align-items: center;
  margin-top: clamp(56px, 7vw, 96px); text-align: left;
}
.sup-advisor-photo { aspect-ratio: 4 / 5; background: #E9E3DA; }
.sup-advisor-line {
  margin: 0; max-width: 18ch; text-wrap: balance;
  font-family: var(--font-display); font-weight: 600; font-size: var(--t-xl);
  line-height: 1.18; letter-spacing: var(--tr-head); color: var(--ink);
}
@media (max-width: 700px) {
  .sup-advisor { grid-template-columns: minmax(0, 1fr); gap: 20px; margin-top: 44px; }
  .sup-advisor-photo { aspect-ratio: 1 / 1; }
  .sup-advisor-photo img { object-position: 50% 16%; }
  .sup-advisor-line { font-size: 24px; max-width: 22ch; }
}

/* index.html #support: the team as the band's wide tile, its label over a
   bottom scrim on the round 29 rule (#FFFFFF, measured, 4.5:1 or better).
   The band is gone by 46 percent of the tile's height, so the three faces,
   which sit above 60 percent in both crops, are never under it. */
.sup-grid .sup-photo {
  grid-column: 1 / -1; position: relative; isolation: isolate;
  display: flex; align-items: flex-end; aspect-ratio: 12 / 5;
  background: #1C1C1E; box-shadow: inset 0 0 0 1px rgba(255,255,255,.06);
}
.sup-photo img { position: absolute; inset: 0; z-index: 0; object-position: 50% 30%; }
.sup-photo::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 46%;
  z-index: 1; pointer-events: none;
  background: linear-gradient(to top, rgba(0,0,0,.80) 0%, rgba(0,0,0,.66) 34%,
              rgba(0,0,0,.30) 70%, rgba(0,0,0,0) 100%);
}
.sup-photo-l {
  position: relative; z-index: 2; margin: 0; padding: 0 28px 24px; max-width: 30ch; text-wrap: balance;
  font-family: var(--font-display); font-weight: 600; font-size: clamp(21px, 2vw, 28px);
  line-height: 1.2; letter-spacing: var(--tr-head); color: #FFFFFF;
}
@media (max-width: 860px) {
  .sup-grid .sup-photo { aspect-ratio: 16 / 10; }
  .sup-photo img { object-position: 40% 50%; }
  /* the label is two lines on a phone and its first line sits a third of
     the way up a 219px tile, over the bright window: measured 3.6:1 on the
     desktop ramp, so the phone's band is taller and carries its weight
     higher. It still stops short of the chin line at 64 percent. */
  .sup-photo::after {
    height: 58%;
    background: linear-gradient(to top, rgba(0,0,0,.86) 0%, rgba(0,0,0,.80) 30%,
                rgba(0,0,0,.62) 55%, rgba(0,0,0,.28) 80%, rgba(0,0,0,0) 100%);
  }
  .sup-photo-l { font-size: 21px; padding: 0 20px 18px; }
}

/* pricing.html, the onboarding answer: the desk, only when opened, 233px
   tall at 390 (under the 300px a phone can spare) */
.faq-photo {
  display: block; width: min(100%, 480px); height: auto; aspect-ratio: 3 / 2;
  object-fit: cover; border-radius: var(--r-lg); margin-top: var(--s-5);
}

/* ---- Round 32: six payment marks, one row --------------------------------
   Simple Icons v13 (CC0), inlined as <symbol>s in the sprite (docs/third-
   party.md). One colour throughout: the mark takes var(--ink), which is
   already eggshell on .on-dark and near-black ink on the bone ground, so
   the same rule reads correctly wherever the row is dropped. 28px tall,
   28px apart, 70% opacity at rest, 100% on hover/focus -- no other motion.
   role="list" restores the semantics flexbox strips from the container in
   Safari; each mark carries its own accessible name via aria-label since
   <title> inside a referenced <symbol> is not reliably announced through
   <use>. */
.pay-marks-band { margin-top: var(--s-6); }
.pay-marks-copy { max-width: 52ch; margin-bottom: var(--s-4); }
.pay-marks { display: flex; align-items: center; flex-wrap: wrap; gap: 28px; }
@media (max-width: 700px) { .pay-marks { justify-content: center; } }
.pay-mark { display: inline-flex; align-items: center; justify-content: center; line-height: 0; }
.pay-mark .pay-ic {
  display: block; height: 28px; width: auto; fill: var(--ink); opacity: .7;
  transition: opacity var(--d-1) var(--snap);
}
.pay-mark:hover .pay-ic, .pay-mark:focus-visible .pay-ic { opacity: 1; }

.trust .pay-marks-band { padding-bottom: var(--s-6); margin-top: 0; }

.mod-pay .pay-marks-band { margin-top: 0; }
.mod-pay .pay-marks { justify-content: flex-start; }
@media (max-width: 700px) { .mod-pay .pay-marks { justify-content: center; } }

/* ═════════════════════════════════════════════════════════════════════════
   Round 33, item 4: the page heads are eggshell.
   about, pricing, support, footnotes, status and the legal page opened on a
   black band, which Parth read as off the theme: the twenty-seven module
   pages open on eggshell with the round 14 ink gradient in the headline, and
   these now open the same way. `.ph-ink` replaces `.on-dark` on the head
   only, so every black section further down each page stays where it was.
   The nav pill is untouched. The home page's hero is black by design and is
   not a page head; it is not touched either.
   ═══════════════════════════════════════════════════════════════════════ */
.pagehead.ph-ink {
  position: relative; isolation: isolate;
  background: var(--bone); color: var(--ink);
}
.pagehead.ph-ink h1 {
  /* the module heroes' own headline ink (css/modules.css, `.mh3-h`), and the
     same `padding-bottom` so a clipped fill never cuts a descender */
  background-image: linear-gradient(180deg, #000000 0%, #2C2C2E 55%, #6E6E73 100%);
  -webkit-background-clip: text; background-clip: text;
  color: transparent; -webkit-text-fill-color: transparent;
  padding-bottom: .08em;
}
.pagehead.ph-ink .lede { color: var(--ink-2); }
/* support's team photograph was lit for a black mesh ground; on eggshell it
   takes the soft shadow every other photograph on a light ground carries */
.pagehead.ph-ink .sup-hero-photo {
  background: #E9E3DA;
  box-shadow: 0 30px 60px -34px rgba(0, 0, 0, .42), 0 2px 8px -4px rgba(0, 0, 0, .18);
}
/* start.html opens on its eggshell form column; its headline takes the same
   ink so the first words on every standalone page read alike. The keycap
   panel beside the form is the start and sign-in pair's own art and stays. */
.login.start .st-inner h1 {
  background-image: linear-gradient(180deg, #000000 0%, #2C2C2E 55%, #6E6E73 100%);
  -webkit-background-clip: text; background-clip: text;
  color: transparent; -webkit-text-fill-color: transparent;
  padding-bottom: .08em;
}

/* ==========================================================================
   Round 36, item 7: the sign-in page opens on eggshell like every other page
   head. The ground is eggshell, the headline takes the page heads' ink
   gradient (its one word in a darker orange that clears 3:1 on eggshell at
   that size), the beige machine sits on the homepage's own halftone
   shadows again, and the card is the site's white card: 28px radius, a 1px
   hairline and the layered soft shadow, with light fields. The CRT keeps
   its orange tube.
   ========================================================================== */
body.lg36 { background: #F7F3EE; }
.lg36 .login.lg-boot { background: #F7F3EE; }
.lg36 .lg-headline {
  background-image: linear-gradient(180deg, #000000 0%, #2C2C2E 55%, #6E6E73 100%);
  -webkit-background-clip: text; background-clip: text; color: transparent; -webkit-text-fill-color: transparent;
  padding-bottom: .06em;
}
.lg36 .lg-headline .accent { -webkit-text-fill-color: #D9480F; color: #D9480F; }
.lg36 .lg-copy-lede { color: #4D4D4D; }
.lg36 .lg-scene .hp-shadow { background-image: radial-gradient(circle, rgba(0,0,0,.28) 1.1px, transparent 1.4px); background-size: 4px 4px; filter: blur(.4px); }
.lg36 .lg-glass {
  background: #FFFFFF; border: 1px solid rgba(0,0,0,.07); border-radius: 28px;
  -webkit-backdrop-filter: none; backdrop-filter: none;
  box-shadow: 0 1px 2px rgba(0,0,0,.04), 0 10px 30px rgba(0,0,0,.07), 0 30px 70px rgba(0,0,0,.07);
}
.lg36 .lg-glass .lg-fields input[type="email"],
.lg36 .lg-glass .lg-fields input[type="password"] { background: #F7F3EE; border-color: #DEDEDE; color: #000000; }
.lg36 .lg-glass .lg-fields input:focus { background: #FFFFFF; }
@media (prefers-contrast: more) { .lg36 .lg-glass { border: 2px solid #000000; } }
/* the machine's parts fly in from past the page's edge; the page never
   scrolls sideways while they do */
.login.lg-boot { overflow-x: clip; }

/* ═════════════════════════════════════════════════════════════════════════
   Round 43: the lean footer.

   The footer went from a brand block and three columns to a brand block and
   four (Product, Solutions, Company, Legal), so the grid needs the fourth
   track. The brand block keeps the widest share and the four columns split
   what is left evenly; below 860px the whole thing falls to two columns as
   it always did, and below 560px to one, because four headings on one phone
   row read as a table of contents rather than a footer.
   ══════════════════════════════════════════════════════════════════════ */
.foot-in { grid-template-columns: minmax(0, 1.5fr) repeat(4, minmax(0, 1fr)); gap: var(--s-6) var(--s-5); }
@media (max-width: 1100px) { .foot-in { grid-template-columns: minmax(0, 1.4fr) repeat(2, minmax(0, 1fr)); } }
/* Round 62: two more columns, In the UAE and Compare (tools/build-nav.py).
   Six link columns beside the brand block on a wide screen; between the
   tablet break and 1100px the brand block takes its own row and the six sit
   three by two under it. The phone rules below are unchanged. */
@media (min-width: 1101px) {
  .foot-in { grid-template-columns: minmax(0, 1.3fr) repeat(6, minmax(0, 1fr)); gap: var(--s-6) var(--s-4); }
}
@media (min-width: 861px) and (max-width: 1100px) {
  .foot-in { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .foot-in > div:first-child { grid-column: 1 / -1; }
}
@media (max-width: 860px)  { .foot-in { grid-template-columns: 1fr 1fr; } }
@media (max-width: 560px)  { .foot-in { grid-template-columns: 1fr; gap: var(--s-6); } }
/* Round 51: on a phone the one column ran to 1,630px, four headings and
   twenty-six links stacked end to end. The brand line spans the top and the
   four columns sit in a grid under it: three across from 430px up to the
   tablet break, two on a narrower phone, with the links on a tighter step
   and the fine print closer. Still no accordion: every link stays one tap. */
@media (max-width: 860px) {
  .foot { padding-top: var(--s-7); }
  .foot-in { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--s-5) var(--s-4); }
  .foot-in > div:first-child { grid-column: 1 / -1; }
  .foot-in > div:first-child .fine { margin-top: var(--s-3) !important; max-width: 46ch !important; }
  .foot h5 { margin-bottom: var(--s-2); }
  .foot ul { gap: 2px; }
  .foot ul a { display: inline-block; padding-block: 5px; font-size: 13.5px; line-height: 1.3; }
  .foot-base { margin-top: var(--s-6); padding-top: var(--s-4); gap: 4px; }
}
@media (max-width: 429px) {
  .foot-in { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s-5) var(--s-4); }
}
/* one line of fine print, three sentences, each on its own line rather than
   flowed together: the copyright, round 39's no-affiliation line and the
   payment marks' trademark line. */
.foot-base { flex-direction: column; align-items: flex-start; gap: 6px; }

/* ═════════════════════════════════════════════════════════════════════════
   Round 43: the four new pages' own rules, and the Start page's.

   They were written as a <style> block in each page's own head, which is
   where start.html's have lived since round 42. tools/build-artifact.py takes
   <header>, <main> and <footer> out of a page and nothing else, so a rule in
   a page's head never reached either artifact: inside the one-file build the
   knowledge base's jump row lost its wrap and ran 90px past a 390px screen,
   and the Start page's card had been missing its own radius and padding since
   round 42 for the same reason. They live here instead. Nothing about how any
   of them looks has changed; the rules are the same rules, in a file the
   artifact reads.
   ══════════════════════════════════════════════════════════════════════ */

/* ---- start.html (round 42, item 4; the two routes are round 43) --------- */
.st-card { padding: clamp(20px, 3.4vw, 40px); border-radius: var(--r-lg); box-shadow: 0 4px 14px rgba(0,0,0,.05); }
.st-next { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--s-4); margin-top: var(--s-6); }
.st-next .mini-card { height: 100%; }
@media (max-width: 760px) { .st-next { grid-template-columns: 1fr; } }
.st-caps { display: flex; align-items: center; justify-content: center; margin-top: var(--s-7); }
.st-caps .kc-stack { --kc-w: 150px; }
@media (max-width: 700px) { .st-caps .kc-stack { --kc-w: 104px; gap: var(--s-3); } }
/* the two routes into the trial: the Google button is the site's own clear
   glass button at full width, and the divider is one hairline with the label
   sitting in a gap in it rather than a box on top of it. */
.st-routes { display: grid; gap: var(--s-4); margin-bottom: var(--s-6); }
.st-routes .btn { width: 100%; justify-content: center; }
.st-or { display: flex; align-items: center; gap: var(--s-4); margin: 0;
         font-family: var(--font-mono); font-size: var(--t-xs); text-transform: uppercase;
         letter-spacing: var(--tr-caps); color: var(--ink-3); }
.st-or::before, .st-or::after { content: ""; flex: 1; height: 1px; background: var(--line); }

/* ---- a four-card mini bento --------------------------------------------
   .mini-bento is three columns, which is right for the three-card rows the
   site is full of. Two of round 43's pages have four cards, and 3 + 1 leaves
   a third of a row empty next to the last card. Where a page says its bento
   holds four, they sit two by two above 900px and fall to the component's own
   two columns below it, which is where it was already going. The component
   itself is untouched. */
.mini-bento.mb-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.mini-bento.mb-4 .mini-card { height: 100%; }

/* ---- contact.html ------------------------------------------------------ */
.ct-card { padding: clamp(20px, 3.4vw, 40px); border-radius: var(--r-lg); box-shadow: 0 4px 14px rgba(0,0,0,.05); }
.ct-ways .mini-card { height: 100%; }
/* round 62: WhatsApp renders only once the number exists (src/facts.json),
   so the ways in are three until then: one row of three, not two by two */
@media (min-width: 901px) {
  .mini-bento.mb-4.ct-ways:not(:has(> :nth-child(4))) { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

/* ---- academy.html ------------------------------------------------------ */
.ac-path { padding: clamp(18px, 2.6vw, 30px); border-radius: var(--r-lg); box-shadow: 0 4px 14px rgba(0,0,0,.05); margin-bottom: var(--s-6); }
.ac-path-top { display: flex; align-items: center; gap: var(--s-3); margin-bottom: var(--s-4); }
.ac-path-top h3 { margin: 0; }

/* ---- kb.html ----------------------------------------------------------- */
.kb-search { max-width: 520px; margin-top: var(--s-6); }
.kb-search input { width: 100%; }
.kb-jump { display: flex; flex-wrap: wrap; gap: var(--s-2); margin-top: var(--s-5); }
.kb-jump-link { font-family: var(--font-mono); font-size: var(--t-xs); text-transform: uppercase;
                letter-spacing: var(--tr-caps); color: var(--ink-2); border: var(--hair);
                border-radius: 999px; padding: 7px 13px; }
.kb-jump-link:hover { color: var(--ink); border-color: var(--ink-3); }
.kb-list { list-style: none; padding: 0; display: grid; gap: var(--s-4); }
.kb-entry { background: var(--surface); border: var(--hair); border-radius: var(--r-lg);
            padding: clamp(16px, 2.2vw, 24px); box-shadow: 0 4px 14px rgba(0,0,0,.05); }
.kb-entry h3 { margin: 0 0 var(--s-2); font-size: var(--t-md); }
.kb-entry p { margin: 0; color: var(--ink-2); }
.kb-links { margin-top: var(--s-3) !important; font-size: var(--t-sm); }
/* base.css gives every <a> `color: inherit` and no underline, which is right
   for a nav and wrong for a row of links at the foot of a card: they read as
   grey caption text. Same treatment the legal pages give a link in prose. */
.kb-links a { color: var(--accent-text); font-weight: 600; }
.kb-links a:hover { color: var(--ink); }
.kb-count { margin-top: var(--s-4); }
.kb-empty { color: var(--ink-3); font-size: var(--t-sm); margin-top: var(--s-4); }
.kb-fam .section-head .ic-tile { margin-bottom: var(--s-3); }
@media (min-width: 900px) { .kb-list { grid-template-columns: 1fr 1fr; } }

/* ═════════════════════════════════════════════════════════════════════════
   Round 44 — the inner pages on a phone.

   Every defect below was found on a real iPhone on a page other than the
   home page, so the phone work of rounds 39 to 41 never reached it. The
   fixes are here rather than in a page's own style block so the one-file
   artifacts read them too (round 43's trap).
   ═════════════════════════════════════════════════════════════════════════ */

/* ---- item 2: the room the floating pill needs, only where it exists -----
   `.foot` reserved 90px plus the safe area at the foot of EVERY page at
   700px and under, but only eight pages carried `#sticky-cta`. On the other
   fifty that reserve is a band of empty ground between the last line of the
   footer and the bottom of the document, which is the gap Parth saw under
   "Take back the Ctrl key." The reserve now follows the pill. (The pill
   itself is written onto every page by tools/build-nav.py, so in practice
   this selector matches; it is here so the two can never drift apart.) */
@media (max-width: 700px) {
  .foot { padding-bottom: calc(var(--s-5) + env(safe-area-inset-bottom, 0px)); }
}

/* ---- item 3: the plan comparison on a phone ----------------------------
   Two faults, one cause. `.scroll-x` is `overflow-x: auto`, which makes the
   element a scroll container on BOTH axes, so a sticky `<th>` inside it
   resolves `top` against the container and not the page: `top: 68px` (meant
   to clear the nav) parked the header row 68px down, printed over the first
   two body rows, at every width. It sticks to the top of its own scrollport
   now. And the feature column is pinned, so a row still says what it is
   after the plans have scrolled past. */
.compare thead th { top: 0; }
.cmp-scroll { position: relative; overscroll-behavior-x: contain; }
.cmp-scroll:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.compare thead th:first-child,
.compare tbody th[scope="row"] {
  position: sticky; left: 0; background: var(--surface);
}
.compare thead th:first-child { z-index: 4; }
.compare tbody th[scope="row"] { z-index: 1; }
/* a group heading spans all five columns, so there is nothing to pin: it
   scrolls with its rows and keeps the ground behind the pinned column */
.compare .grp th { position: static; }
/* the pinned column needs an edge of its own once the plans slide under it */
.compare thead th:first-child::after,
.compare tbody th[scope="row"]::after {
  content: ""; position: absolute; top: 0; right: 0; bottom: 0; width: 1px;
  background: var(--line);
}
/* the hint: the site's own mono caption, phones only, with the chevron the
   rails use. It is aria-hidden because the container itself is a labelled,
   focusable region, so a screen reader is already told it scrolls. */
.cmp-hint {
  display: none; align-items: center; gap: 6px; margin-top: var(--s-6);
  font-family: var(--font-mono); font-size: var(--t-xs);
  letter-spacing: var(--tr-caps); text-transform: uppercase; color: var(--ink-3);
}
.cmp-hint .ic { width: 14px; height: 14px; animation: cmpNudge 1.9s var(--glide) infinite; }
@keyframes cmpNudge { 0%, 62%, 100% { transform: translateX(0); } 78% { transform: translateX(4px); } }
@media (prefers-reduced-motion: reduce) { .cmp-hint .ic { animation: none; } }
@media (max-width: 900px) {
  .cmp-hint { display: flex; }
  .cmp-scroll { margin-top: var(--s-3) !important; }
  /* at 320 a 720px table over five columns leaves the feature column no
     room to say what the row is, so the whole grid is tightened rather than
     the text being cut: the pinned column keeps a readable measure and the
     four plan columns are as narrow as a tick needs. */
  .compare { min-width: 588px; }
  .compare th, .compare td { padding: 10px var(--s-3); }
  .compare thead th:first-child, .compare tbody th[scope="row"] { width: 180px; min-width: 180px; }
  .compare .grp th { width: auto; }
}

/* ---- item 4: the family jump row is a chip rail -------------------------
   44px targets, the round 29 edge fades through `.rail36`, 8px gaps, and
   the family the reader is in marked. The gap, the pill shape and the pad
   are restated here, in the one stylesheet every page loads, because a row
   of jump links that loses its rule set reads as one run-together word
   ("ServiceBusinessCatalogue...") and that is what a phone showed. */
.fam-jump, .kb-jump { display: flex; gap: 8px; }
.fam-jump a, .kb-jump-link {
  display: inline-flex; align-items: center; min-height: 44px;
  padding-block: 0; padding-inline: 14px; border-radius: 999px;
  white-space: nowrap; text-decoration: none;
}
.fam-jump a[aria-current="true"], .kb-jump-link[aria-current="true"] {
  background: var(--ink); color: var(--n-0);
  box-shadow: none; border-color: transparent;
}
@media (max-width: 700px) {
  .kb-jump {
    flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none;
    width: 100vw; margin-inline: calc(50% - 50vw);
    padding-inline: var(--gutter); box-sizing: border-box; --fade-w: 40px;
  }
  .kb-jump::-webkit-scrollbar { display: none; }
  .kb-jump-link { flex: none; }
}

/* ---- the sweep: the fine-print token on a phone ------------------------
   `--t-xs` is 11.52 to 11.63px, and it carries more than fine print: the
   plan feature lists, the "who it is for" line under every plan name, the
   footer's legal lines, the nav's own CTA. 486 text nodes across the site
   render under 13px on a phone because of it. The scale itself is not
   wrong -- at a desk, at 1440, 11.6px is a caption -- so the token is
   raised on phones only and the desktop type scale is untouched. Checked
   after: no horizontal overflow at 320, 390 or 430 on any page. */
@media (max-width: 700px) {
  :root { --t-xs: 13px; }
}

/* ---- the sweep: two labels the phone could not read --------------------
   `.ramp div` is the step number printed in each colour swatch on
   design-system.html, set in px rather than on the type scale, so it never
   moved with the token above. Phones only; the desktop size is untouched.
   (Its sibling, the device badge over each flow figure, is raised in
   css/modules.css, which this file is loaded before.) */
@media (max-width: 700px) {
  .ramp div { font-size: 11px; }
}

/* ---- Round 61: the status pill ------------------------------------------
   One component for a module that is not live yet, written by tools/facts.py
   (pill()) wherever a module is named: beside the module eyebrow, in the
   nav's mega menu, the product index, "Works with" rows, the plan lists.
   "Coming soon" is bone on ink on a light ground and ink on bone on a dark
   one (the semantic --ink / --ground pair flips inside .on-dark);
   "Certification pending" is quieter, an outline. Inter, sentence case, even
   inside a mono uppercase eyebrow. Tokens only. */
.stp {
  display: inline-flex; align-items: center; flex: none;
  vertical-align: 0.12em;
  padding: 3px 8px 3px;
  border-radius: var(--r-full);
  font-family: var(--font-ui); font-size: 11px; font-weight: 600; line-height: 1.2;
  letter-spacing: 0; text-transform: none; white-space: nowrap;
}
.stp-soon { background: var(--ink); color: var(--ground); }
.stp-cert { background: transparent; color: var(--ink-2); box-shadow: inset 0 0 0 1px var(--ink-3); }
.kb-dark .stp-soon, .mm2-feat .stp-soon { background: var(--bone); color: var(--n-900); }
.kb-dark .stp-cert { color: var(--nt-ink-2); box-shadow: inset 0 0 0 1px var(--n-500); }
/* the mega menu and the phone menu sit inside the dark nav bar but are
   drawn on a light sheet, so the pair is set, not inherited */
.nav-menu .stp-soon, .nav-links .stp-soon { background: var(--n-900); color: var(--bone); }
.nav-menu .stp-cert, .nav-links .stp-cert { color: var(--n-600); box-shadow: inset 0 0 0 1px var(--n-400); }
.eyebrow .stp { margin-left: 2px; align-self: center; vertical-align: 0; position: relative; top: -1px; }
h1 .stp, h2 .stp, h3 .stp, b .stp { margin-left: 6px; }
/* a component row on status.html that is not running yet reads as such */
.status-pending, .status-soon { display: flex; align-items: center; gap: 8px; font-family: var(--font-mono); font-size: var(--t-xs); color: var(--ink-2); }
.status-pending .status-dot { background: transparent; box-shadow: inset 0 0 0 1.5px var(--warn); }
.status-soon .status-dot { background: var(--n-300); }

/* ═════════════════════════════════════════════════════════════════════════
   Round 62: the search pages (tools/build-landing.py, tools/build-compare.py,
   tools/lp62.py). Everything here is the site's own parts re-used: the
   module hero, .facts, .mini-bento, the pricing page's .compare table in its
   sideways-scrolling region, the FAQ, the Works with cards. What is new is
   small: a text table, footnotes, a plain list, a live UI card in the hero
   figure, and the right-to-left page.
   ══════════════════════════════════════════════════════════════════════ */
.lp62-facts .facts, .lp62-dl { margin-top: var(--s-6); }
.lp62-facts dd a, .lp62-prose a, .lp62-fn a, .lp62-table .lede a, .mh3-copy .lede a {
  text-decoration: underline; text-underline-offset: 2px;
}
.lp62-copy .lede + .lede { margin-top: var(--s-4); }
.lp62-list { list-style: none; padding: 0; margin: var(--s-5) 0 0; display: grid; gap: var(--s-3); max-width: 72ch; }
.lp62-list li { position: relative; padding-left: 18px; font-size: var(--t-sm); line-height: 1.55; color: var(--ink-2); }
.lp62-list li::before { content: ""; position: absolute; left: 0; top: .62em; width: 6px; height: 6px; border-radius: 50%; background: var(--accent); }
.lp62-prose .lede + .lp62-list { margin-top: var(--s-3); }
.lp62-list + .lede { margin-top: var(--s-6); }

/* the text table: the pricing page's .compare, with sentences in its cells.
   Nothing in it is sticky: on a phone nothing is pinned while the page is
   read (redesign-brief section 0), and a text column scrolls with its row. */
.lp62-tbl { margin-top: var(--s-6); }
.compare.cmp62 { font-size: var(--t-xs); line-height: 1.5; min-width: 760px; }
.compare.cmp62 thead th, .compare.cmp62 tbody th[scope="row"] { position: static; }
.compare.cmp62 thead th:first-child::after, .compare.cmp62 tbody th[scope="row"]::after { display: none; }
.compare.cmp62 th, .compare.cmp62 td { vertical-align: top; text-align: left; padding: 12px var(--s-4); }
.compare.cmp62 thead th { font-size: var(--t-sm); }
.compare.cmp62 tbody th[scope="row"] { width: 22%; font-weight: 600; color: var(--ink); }
.compare.cmp62 td { color: var(--ink-2); }
.compare.cmp62 td .stp { margin-inline: 2px 4px; vertical-align: 1px; }
.compare.cmp62.cmp62-wide { min-width: 1100px; }
.compare.cmp62.cmp62-wide tbody th[scope="row"] { width: 12%; }
.fn62 { font-size: .72em; line-height: 0; margin-inline-start: 2px; }
.fn62 a { color: var(--accent-text); text-decoration: none; }
.fn62 a:hover { text-decoration: underline; }
.lp62-fn { margin: var(--s-6) 0 0; padding-inline-start: 1.6em; display: grid; gap: var(--s-2); }
.lp62-fn li { font-size: var(--t-xs); line-height: 1.5; color: var(--ink-2); scroll-margin-top: 96px; }
.lp62-fn li:target { color: var(--ink); }
.fn62-url { font-family: var(--font-mono); font-size: 11px; color: var(--ink-3); word-break: break-all; }

/* a live product-UI card in the hero figure (the Arabic till), sized like
   the module heroes' devices and drawn from css/product-ui.css's parts */
.lp62-uifig { display: flex; align-items: center; justify-content: center; }
.lp62-ui { width: min(100%, 440px); border-radius: var(--r-lg); box-shadow: var(--lift-3); overflow: hidden; background: var(--surface); }
.lp62-ui .ui { min-height: 0; }
.lp62-ar-order { display: grid; gap: 10px; padding-top: 6px; }
.lp62-ar-order .svc { display: flex; gap: 6px; }
.lp62-ar-order .svc b { font-weight: 600; font-size: 12px; padding: 5px 10px; border-radius: var(--r-full); background: var(--surface-2); color: var(--ink-2); }
.lp62-ar-order .svc b.on { background: var(--ink); color: var(--ground); }
.lp62-ar-order .osum { display: grid; gap: 4px; font-size: 13px; color: var(--ink-2); border-top: var(--hair); padding-top: 8px; }
.lp62-ar-order .osum > div { display: flex; justify-content: space-between; }
.lp62-ar-order .osum .tot { color: var(--ink); font-weight: 700; }
.lp62-ar-order .opay .pb { display: block; text-align: center; padding: 10px; border-radius: var(--r-3); background: var(--accent); color: var(--on-accent); font-weight: 700; font-size: 13px; }

/* the Arabic page: the page reads right to left; the site's own chrome (the
   nav and the footer, English) keeps its left-to-right layout */
html[dir="rtl"] .nav, html[dir="rtl"] .foot, html[dir="rtl"] .cmp-note { direction: ltr; }
:lang(ar) { font-family: "Geeza Pro", "SF Arabic", "Noto Sans Arabic", "Segoe UI", Tahoma, var(--font-ui); }
html[dir="rtl"] .lp62-list li { padding-left: 0; padding-right: 18px; }
html[dir="rtl"] .lp62-list li::before { left: auto; right: 0; }
html[dir="rtl"] .compare.cmp62 th, html[dir="rtl"] .compare.cmp62 td { text-align: right; }
html[dir="rtl"] .mh3-h, html[dir="rtl"] .section-head h2 { letter-spacing: 0; }

/* Round 70 (Parth, 1 Oct: "the nav bar colours on hover seem off"): over a dark section the pill is
   light glass with dark words, so an open menu keeps it light (a touch more opaque) instead of
   turning it black under dark words, which hid every link and the Book a demo label */
.nav.over-dark .nav-in:has(.has-menu:hover), .nav.over-dark .nav-in:has(.has-menu:focus-within),
.nav.over-dark .nav-in:has(.has-menu.force-open) {
  background: color-mix(in srgb, #FFFFFF 90%, transparent);
}
.nav.over-dark .has-menu:hover > .nav-trigger, .nav.over-dark .has-menu:focus-within > .nav-trigger,
.nav.over-dark .has-menu.force-open > .nav-trigger, .nav.over-dark .nav-trigger[aria-expanded="true"] {
  background: rgba(0, 0, 0, .07); color: var(--n-900);
}

/* ---- Round 71: the pill is Liquid Glass (regular variant) -------------------
   Apple HIG, Materials > Liquid Glass and Color > Liquid Glass color. Glass is
   the functional layer that floats over content and lets it show through; the
   regular variant is the one for bars that carry text (the clear variant is for
   controls over photographs and film, and lost the links under the big module
   headlines in the comparison, comp/captures/nav-liquid-glass-comparison.png).
   Glass has no colour of its own: the labels go light over a black ground and
   dark over eggshell, and the orange button is the one tinted piece. The 18-20px
   frost becomes 6-9px with a light dimming, a specular rim and top sheen, and in
   Chromium a 16px refracting bezel (js/site.js draws the map; Safari and Firefox
   keep the same glass without the bend). The open menu is the thicker variant.
   Wherever the pill exists, never the open sheet: touch devices up to 1020px
   have the in-flow opaque bar instead (the iOS pocket rule, `(any-pointer:
   coarse) and (max-width: 1020px)` above), so that is the one place it is off. Parth chose regular on 2 Oct, from
   comp/nav-glass-proto.html. */
@media (min-width: 1021px), not all and (any-pointer: coarse) {
  html .nav:not(.is-open) .nav-in {
    position: relative; isolation: isolate;     /* the pill is now the menu's containing block */
    border: 0;
    background:
      linear-gradient(180deg, rgba(255,255,255,.10), rgba(255,255,255,.02) 46%, rgba(255,255,255,.05)),
      rgba(12,12,12,.46);
    -webkit-backdrop-filter: blur(11px) saturate(1.7) brightness(.78);
            backdrop-filter: blur(11px) saturate(1.7) brightness(.78);
    box-shadow:
      inset 0 1px 1px rgba(255,255,255,.55),     /* light catching the top edge */
      inset 0 -1px 1px rgba(255,255,255,.16),    /* and the far edge, through the glass */
      inset 0 0 22px rgba(255,255,255,.07),
      0 10px 30px -12px rgba(0,0,0,.50),
      0 1px 2px rgba(0,0,0,.18);
    transition: box-shadow var(--d-3) var(--glide);   /* never the background: it would fade through grey */
  }
  html .nav.lg-refract:not(.is-open) .nav-in {
    -webkit-backdrop-filter: url(#lg-refract) blur(11px) saturate(1.7) brightness(.78);
            backdrop-filter: url(#lg-refract) blur(11px) saturate(1.7) brightness(.78);
  }
  /* the rim: bright at top-left and bottom-right where light strikes the curve */
  html .nav:not(.is-open) .nav-in::before {
    content: ""; position: absolute; inset: 0; z-index: -1; border-radius: inherit; padding: 1px; pointer-events: none;
    background: linear-gradient(115deg,
      rgba(255,255,255,.85) 0%, rgba(255,255,255,.22) 14%, rgba(255,255,255,0) 32%,
      rgba(255,255,255,0) 68%, rgba(255,255,255,.22) 86%, rgba(255,255,255,.65) 100%);
    -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
    -webkit-mask-composite: xor; mask-composite: exclude;
  }
  html .nav:not(.is-open) .nav-in::after {
    content: ""; position: absolute; inset: 1px 18px auto; height: 45%; z-index: -1; pointer-events: none;
    border-radius: 999px 999px 40% 40% / 999px 999px 100% 100%;
    background: linear-gradient(180deg, rgba(255,255,255,.16), rgba(255,255,255,0));
  }

  /* over a black ground: light labels */
  html .nav.over-dark:not(.is-open) .nav-in,
  html .nav.over-dark:not(.is-open) .nav-trigger,
  html .nav.over-dark:not(.is-open) .nav-links > a { color: var(--bone); }
  html .nav.over-dark:not(.is-open) .nav-trigger:hover,
  html .nav.over-dark:not(.is-open) .has-menu:hover > .nav-trigger,
  html .nav.over-dark:not(.is-open) .has-menu:focus-within > .nav-trigger,
  html .nav.over-dark:not(.is-open) .has-menu.force-open > .nav-trigger,
  html .nav.over-dark:not(.is-open) .nav-trigger[aria-expanded="true"],
  html .nav.over-dark:not(.is-open) .nav-links > a:hover { color: #FFFFFF; background: rgba(255,255,255,.12); }
  html .nav.on-dark.over-dark:not(.is-open) .nav-cta .btn.btn-ghost {
    color: var(--bone); background: rgba(255,255,255,.08); border-color: transparent;
    box-shadow: inset 0 1px 0 rgba(255,255,255,.35), inset 0 0 0 1px rgba(255,255,255,.18);
  }
  html .nav.on-dark.over-dark:not(.is-open) .nav-cta .btn.btn-ghost:hover { background: rgba(255,255,255,.16); }

  /* over eggshell: the same glass, lighter, with dark labels */
  html .nav:not(.over-dark):not(.is-open) .nav-in {
    background: linear-gradient(180deg, rgba(255,255,255,.50), rgba(255,255,255,.22) 50%, rgba(255,255,255,.34));
    -webkit-backdrop-filter: blur(6px) saturate(1.8) brightness(1.06);
            backdrop-filter: blur(6px) saturate(1.8) brightness(1.06);
    box-shadow:
      inset 0 1px 1px rgba(255,255,255,.95),
      inset 0 -1px 1px rgba(255,255,255,.5),
      inset 0 0 0 .5px rgba(0,0,0,.06),
      0 10px 30px -14px rgba(0,0,0,.28),
      0 1px 2px rgba(0,0,0,.08);
  }
  html .nav.lg-refract:not(.over-dark):not(.is-open) .nav-in {
    -webkit-backdrop-filter: url(#lg-refract) blur(6px) saturate(1.8) brightness(1.06);
            backdrop-filter: url(#lg-refract) blur(6px) saturate(1.8) brightness(1.06);
  }
  html .nav:not(.over-dark):not(.is-open) .nav-in,
  html .nav:not(.over-dark):not(.is-open) .nav-trigger,
  html .nav:not(.over-dark):not(.is-open) .nav-links > a { color: var(--n-900); }
  html .nav:not(.over-dark):not(.is-open) .nav-trigger:hover,
  html .nav:not(.over-dark):not(.is-open) .has-menu:hover > .nav-trigger,
  html .nav:not(.over-dark):not(.is-open) .has-menu:focus-within > .nav-trigger,
  html .nav:not(.over-dark):not(.is-open) .has-menu.force-open > .nav-trigger,
  html .nav:not(.over-dark):not(.is-open) .nav-links > a:hover { color: var(--n-900); background: rgba(0,0,0,.06); }
  html .nav:not(.over-dark):not(.is-open) .nav-links > a[aria-current="page"] { color: var(--n-900); }
  html .nav:not(.over-dark):not(.is-open) .nav-cta .btn.btn-ghost {
    color: var(--n-900); background: rgba(255,255,255,.35); border-color: transparent;
    box-shadow: inset 0 1px 0 rgba(255,255,255,.9), inset 0 0 0 1px rgba(0,0,0,.10);
  }

  html .nav.over-dark:not(.is-open) .nav-toggle { color: var(--bone); }
  html .nav:not(.over-dark):not(.is-open) .nav-toggle { color: var(--n-900); }

  /* the primary action is the one tinted piece of glass */
  html .nav:not(.is-open) .nav-cta .btn-primary {
    box-shadow: inset 0 1px 0 rgba(255,255,255,.45), inset 0 -1px 0 rgba(0,0,0,.12), 0 6px 16px -8px rgba(255,107,61,.9);
  }

  /* menu open: the pill gives up its filter so the menu can sample the page
     (an element with backdrop-filter is the backdrop root of its children),
     stays glass, and the menu is the regular, thicker variant */
  html .nav:not(.is-open) .nav-in:has(.has-menu:hover),
  html .nav:not(.is-open) .nav-in:has(.has-menu:focus-within),
  html .nav:not(.is-open) .nav-in:has(.has-menu.force-open) {
    -webkit-backdrop-filter: none; backdrop-filter: none;
  }
  html .nav.over-dark:not(.is-open) .nav-in:has(.has-menu:hover),
  html .nav.over-dark:not(.is-open) .nav-in:has(.has-menu:focus-within),
  html .nav.over-dark:not(.is-open) .nav-in:has(.has-menu.force-open) {
    background:
      linear-gradient(180deg, rgba(255,255,255,.12), rgba(255,255,255,.04) 46%, rgba(255,255,255,.07)),
      rgba(12,12,12,.55);
  }
  html .nav:not(.over-dark):not(.is-open) .nav-in:has(.has-menu:hover),
  html .nav:not(.over-dark):not(.is-open) .nav-in:has(.has-menu:focus-within),
  html .nav:not(.over-dark):not(.is-open) .nav-in:has(.has-menu.force-open) {
    background: color-mix(in srgb, #FFFFFF 88%, transparent);
  }
  html .nav:not(.is-open) .nav-menu {
    left: 0; right: 0;
    background: color-mix(in srgb, #FFFFFF 84%, transparent);
    -webkit-backdrop-filter: blur(30px) saturate(1.9) brightness(1.04);
            backdrop-filter: blur(30px) saturate(1.9) brightness(1.04);
    box-shadow:
      inset 0 1px 1px rgba(255,255,255,.95),
      inset 0 0 0 .5px rgba(255,255,255,.6),
      0 30px 60px -20px rgba(0,0,0,.45),
      0 2px 6px rgba(0,0,0,.10);
  }
}
/* reduced transparency or more contrast: no glass, a solid light pill, ink labels */
@media (min-width: 1021px) and (prefers-reduced-transparency: reduce), (min-width: 1021px) and (prefers-contrast: more),
       (pointer: fine) and (prefers-reduced-transparency: reduce), (pointer: fine) and (prefers-contrast: more) {
  html .nav:not(.is-open) .nav-in, html .nav.over-dark:not(.is-open) .nav-in, html .nav:not(.over-dark):not(.is-open) .nav-in {
    background: var(--n-0); -webkit-backdrop-filter: none; backdrop-filter: none;
  }
  html .nav:not(.is-open) .nav-in::before, html .nav:not(.is-open) .nav-in::after { display: none; }
  html .nav.over-dark:not(.is-open) .nav-in, html .nav.over-dark:not(.is-open) .nav-trigger,
  html .nav.over-dark:not(.is-open) .nav-links > a, html .nav.over-dark:not(.is-open) .nav-toggle { color: var(--n-900); }
  html .nav.on-dark.over-dark:not(.is-open) .nav-cta .btn.btn-ghost { color: var(--n-900); background: transparent; box-shadow: inset 0 0 0 1.5px var(--n-900); }
}
