/* ============================================================
   Whispr - landing page
   Design system: "The Band"

   The device has one signature: a lit fabric band with warm light
   bleeding above and below it. That band is the whole visual system
   here. It is the section rule, the divider, the progress seam, the
   glow behind every dark panel. Nothing else on the page is decorated.
   ============================================================ */

/* ---------- tokens ---------- */
:root{
  /* ==========================================================
     THE LOGO PALETTE                              v14, 2026-08-21

     Every colour below is the logo's, or derived from it by lightening
     or darkening one of its three. Sampled, not eyeballed:
       #001B3D navy · #38B6FF sky · #FAF7F1 cream
     ========================================================== */

  /* surfaces - CLEAN WHITE pivot (2026-09-01, Mike's ask: white aesthetic,
     Weylo benchmark). Off the logo's warm cream to a true white ground with
     cool near-neutral steps, so the product reads as hardware-on-white.
     Kept as tokens so every rule that referenced --paper/--linen still works. */
  --paper:      #FFFFFF;   /* page ground - clean white */
  --paper-2:    #F5F6F8;   /* cards / raised panels - faint cool grey */
  --paper-3:    #ECEEF1;
  --linen:      #E4E7EC;   /* the #how ground - cool light grey, was warm linen */

  /* night - the logo's navy, taken down to a ground */
  --night:      #04142B;
  --night-2:    #0A2142;

  /* ink - the logo's navy, exact, and two steps up from it */
  --ink:        #001B3D;   /* exact: the wordmark */
  --ink-2:      #294057;
  --ink-3:      #5F7188;
  --on-night:   #F6F8FB;   /* cool near-white on navy (was warm cream) */
  --on-night-2: #B7C6D8;

  /* THE ACCENT IS THE BUBBLE. In v13 these three were ember orange,
     drawn from the light in the device's woven band. That is the
     product's colour, not the brand's, and it appears nowhere in either
     logo. Here they are the logo's blue: the base, a lit step, and a pale
     step, so every rule that referred to --ember keeps working. */
  --ember:      #38B6FF;   /* exact: the speech bubble */
  --ember-lit:  #7ACCFF;
  --ember-hot:  #C2E7FF;

  /* kept so nothing referring to it breaks; it is now the accent proper */
  --sky:        #38B6FF;

  --shadow-1: 0 1px 2px rgba(4,20,43,.06), 0 8px 24px -12px rgba(4,20,43,.20);
  --shadow-2: 0 2px 6px rgba(4,20,43,.07), 0 28px 60px -28px rgba(4,20,43,.32);

  --display: "Poppins", "Segoe UI", system-ui, sans-serif;   /* Welyto-match rounded geometric sans (2026-09-01) */
  --ui: "Poppins", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;

  --wrap: 1120px;
  --gut: clamp(20px, 5vw, 56px);

  --nav-h: 64px;
}

/* ---------- reset ---------- */
*,*::before,*::after{box-sizing:border-box}
/* scroll-padding-top is not polish. The header is position:sticky, so
   without it every one of the six menu links lands its section heading
   underneath the bar, and the visitor arrives at a section whose title
   they cannot read. The 12px is breathing room, not a fudge. */
html{-webkit-text-size-adjust:100%; scroll-behavior:smooth; scroll-padding-top:calc(var(--nav-h) + 12px)}
body{
  margin:0;
  background:var(--paper);
  color:var(--ink);
  font-family:var(--ui);
  font-size:17px;
  line-height:1.6;
  -webkit-font-smoothing:antialiased;
  overflow-x:hidden;
}
img{max-width:100%; display:block}
[hidden]{display:none !important}
h1,h2,h3,h4,p,figure,blockquote{margin:0}
button{font:inherit; color:inherit}
a{color:inherit}
:focus-visible{outline:3px solid var(--ember); outline-offset:3px; border-radius:4px}

/* ---------- type ---------- */
.hero h1{font-weight:800; letter-spacing:-.025em}
.display{
  font-family:var(--display);
  font-weight:400;
  font-optical-sizing:auto;
  font-variation-settings:"SOFT" 40,"WONK" 0;
  letter-spacing:-.015em;
  line-height:1.06;
}
.eyebrow{
  font-size:12px; font-weight:600; letter-spacing:.16em; text-transform:uppercase;
  color:var(--ink-3);
}
.eyebrow.on-dark{color:var(--ember-lit)}
.lede{font-size:clamp(14px,1.1vw,15px); line-height:1.5; color:var(--ink-2)}
.fine{font-size:13.5px; line-height:1.55; color:var(--ink-3)}
.fine.on-dark{color:var(--on-night-2)}

/* ---------- layout ---------- */
.wrap{width:100%; max-width:var(--wrap); margin-inline:auto; padding-inline:var(--gut)}
.wrap-narrow{max-width:760px}
section{position:relative}
.pad{padding-block:clamp(24px,2.6vw,36px)}
.pad-sm{padding-block:clamp(30px,3.6vw,52px)}

/* ============================================================
   THE BAND - the one motif.
   Glow line / fabric / glow line. It is the device, flattened.
   ============================================================ */
/* THE BAND RULE, THINNED  2026-08-20.
   It was 16px tall with 1.5px ember borders top and bottom, a linen
   fill between them and a glow off both edges: a filled bar, not a
   rule, and it read as a piece of UI chrome dropped into a legal page
   twice. The idea was right, the weight was not. The device's band is
   LIGHT ESCAPING A SEAM, so this is a seam: one hairline, ember,
   fading out at both ends so it never collides with the text measure,
   with the bloom carried by a pseudo element rather than by two
   shadows on a box. Sub-pixel on a 2x display, which is where the
   thing it is imitating actually lives. */
.band-rule{
  position:relative; height:1px; margin-block:0; border:0;
  background:linear-gradient(90deg,
    rgba(56,182,255,0) 0%, rgba(56,182,255,.62) 14%,
    rgba(56,182,255,.62) 86%, rgba(56,182,255,0) 100%);
}
.band-rule::after{
  content:""; position:absolute; left:0; right:0; top:-3px; height:7px;
  pointer-events:none;
  background:linear-gradient(90deg,
    rgba(122,204,255,0) 0%, rgba(122,204,255,.20) 18%,
    rgba(122,204,255,.20) 82%, rgba(122,204,255,0) 100%);
  filter:blur(3px);
}
.band-rule.short{width:min(340px,60%); margin:clamp(28px,4.5vw,46px) auto}
.rule-gap{padding-block:clamp(10px,2vw,22px)}
.band-rule.on-dark{
  background:linear-gradient(90deg,
    rgba(122,204,255,0) 0%, rgba(122,204,255,.75) 14%,
    rgba(122,204,255,.75) 86%, rgba(122,204,255,0) 100%);
}
.band-rule.on-dark::after{
  background:linear-gradient(90deg,
    rgba(122,204,255,0) 0%, rgba(122,204,255,.30) 18%,
    rgba(122,204,255,.30) 82%, rgba(122,204,255,0) 100%);
}

/* The seam (a warm vertical light line down the left of the viewport that
   grew as you scrolled) was removed 2026-08-19: it bisected photographs and
   read as a rendering fault rather than a device motif. */

/* ---------- buttons ---------- */
.btn{
  display:inline-flex; align-items:center; justify-content:center; gap:10px;
  padding:15px 26px; min-height:52px;
  border:none; border-radius:999px;
  font-size:16.5px; font-weight:600; letter-spacing:-.005em;
  text-decoration:none; cursor:pointer;
  transition:transform .18s ease, box-shadow .18s ease, background .18s ease;
}
/* THE BUTTON HAD ORANGE LEFT IN IT. The palette conversion caught the
   tokens and the rgba literals, and missed two hexes written straight
   into this rule: a #F2A24A top stop and #2A1704 text. The result was a
   button that faded orange into blue, which the owner spotted at once.

   Both stops are the logo blue now, the top one lifted. Text is the
   logo's navy, not white: white on #38B6FF measures about 2.2:1 and
   fails, navy measures about 7:1 and passes comfortably. */
.btn--ember{
  background:linear-gradient(180deg,#6FC6FF,var(--ember));
  color:#001B3D;
  box-shadow:0 2px 0 rgba(255,255,255,.35) inset, 0 10px 26px -10px rgba(56,182,255,.9);
}
.btn--ember:hover{transform:translateY(-2px); box-shadow:0 2px 0 rgba(255,255,255,.35) inset, 0 16px 34px -12px rgba(56,182,255,1)}
.btn--ink{background:var(--ink); color:var(--paper)}
.btn--ink:hover{transform:translateY(-2px); background:#062A55}
.btn--ghost{background:transparent; color:var(--ink); border:1.5px solid rgba(0,27,61,.22); padding:13px 22px; min-height:48px}
.btn--ghost:hover{border-color:var(--ink); background:rgba(0,27,61,.04)}
.btn--sm{padding:11px 20px; min-height:44px; font-size:15px}
.btn--block{width:100%}

/* ---------- nav ---------- */
.nav{
  position:sticky; top:0; z-index:60;
  transition:transform .32s cubic-bezier(.3,.7,.2,1);
  background:rgba(250,247,241,.86);
  backdrop-filter:saturate(150%) blur(14px);
  border-bottom:1px solid rgba(0,27,61,.08);
}
.nav__in{display:flex; align-items:center; gap:20px; height:var(--nav-h)}
.nav__logo img{height:26px; width:auto}
.nav__links{display:flex; gap:26px; margin-left:auto; font-size:15px; font-weight:500}
.nav__links a{text-decoration:none; color:var(--ink-2)}
.nav__links a:hover{color:var(--ink)}
.nav__cta{margin-left:auto}
.nav__links + .nav__cta{margin-left:22px}
.nav.hid{transform:translateY(-102%)}

/* ============================================================
   THE MOBILE MENU                                   2026-08-21

   What was here: @media (max-width:900px){ .nav__links{display:none} }.
   One line, and it deleted the entire navigation on every phone. A
   visitor on the channel this page is bought for got a wordmark, one
   button, and no way to reach five of the six sections without
   scrolling the whole page looking for them.

   THREE STATES, and the middle one is the one usually skipped.

   1. Wide (901px and up). Unchanged. Links inline, no button.

   2. Narrow, with the menu script running. The links leave the bar and
      become a panel under it, opened by a real button.

   3. Narrow, with the script NOT running. The links stay on the bar and
      wrap onto a second row. It is not the prettiest header on the
      internet and it does not have to be: it has to be navigable, and
      it is. Every state above is keyed off .js-nav, which the script
      adds to <html> as its first act. So the code that hides the links
      is the same code that can show them again. Nothing can put this
      header into a state where six links exist and nothing can reach
      them, which is the state it shipped in.

   .js-nav is deliberately NOT no-js/has-js. The head script sets has-js
   before app.js has run or failed, so has-js proves a script ran, not
   that THIS one did.
   ============================================================ */

/* The button does not exist above 900px and does not exist without the
   script. Both are handled by leaving it off and switching it on once. */
.nav__burger{display:none}

@media (max-width:900px){

  /* --- state 3: no script. The links wrap onto a second row. --- */
  .nav__in{
    flex-wrap:wrap; height:auto; min-height:var(--nav-h);
    padding-block:8px; gap:14px; row-gap:4px;
  }
  .nav__links{
    order:4; flex-basis:100%; margin-left:0;
    flex-wrap:wrap; gap:2px 18px; font-size:14.5px;
  }
  /* 36px rather than 44 here on purpose: this row only ever renders with
     JavaScript switched off, where there is no touch keyboard, no menu,
     and the cost of a taller row is the hero CTA leaving the fold. */
  .nav__links a{display:inline-flex; align-items:center; min-height:36px}
  /* .nav__links + .nav__cta is (0,2,0) and beats .nav__cta{margin-left:auto}
     at (0,1,0), which is why the button has been sitting against the
     wordmark with the whole right half of the bar empty. Restored at
     matching weight rather than with !important. */
  .nav__in .nav__cta{margin-left:auto}

  /* --- state 2: the script is running. The bar goes back to one row. --- */
  .js-nav .nav__in{
    flex-wrap:nowrap; height:var(--nav-h); min-height:0;
    padding-block:0; gap:12px; row-gap:0;
  }
  .js-nav .nav__cta{order:2}
  .js-nav .nav__burger{order:3}

  /* THE BUTTON. 46px square, over the 44 floor, and it is the last thing
     on the bar because that is where a thumb is. It sits BEFORE the panel
     in the markup, so one Tab past it walks straight into the first link
     and Shift+Tab out of that link comes back to it. That ordering is why
     this needs no focus trap: the panel is display:none when shut, so
     there is nothing inside it to catch a Tab in the first place. */
  .js-nav .nav__burger{
    display:inline-flex; align-items:center; justify-content:center;
    width:46px; height:46px; flex:none; padding:0; margin-right:-9px;
    background:transparent; border:none; border-radius:12px;
    color:var(--ink); cursor:pointer;
    -webkit-tap-highlight-color:transparent;
  }
  .js-nav .nav__burger:hover{background:rgba(0,27,61,.05)}
  .js-nav .nav__burger:focus-visible{outline:2px solid var(--ember); outline-offset:2px}

  /* Three bars drawn from one element and its two pseudos, so the shape
     can become an X without a second icon existing anywhere. */
  .nav__burger i{
    position:relative; display:block; width:21px; height:2px; border-radius:2px;
    background:currentColor;
    transition:background .18s ease, transform .22s cubic-bezier(.3,.7,.2,1);
  }
  .nav__burger i::before,
  .nav__burger i::after{
    content:""; position:absolute; left:0; width:21px; height:2px;
    border-radius:2px; background:currentColor;
    transition:transform .22s cubic-bezier(.3,.7,.2,1);
  }
  .nav__burger i::before{top:-6.5px}
  .nav__burger i::after{top:6.5px}
  .nav__burger[aria-expanded="true"] i{background:transparent}
  .nav__burger[aria-expanded="true"] i::before{transform:translateY(6.5px) rotate(45deg)}
  .nav__burger[aria-expanded="true"] i::after{transform:translateY(-6.5px) rotate(-45deg)}

  /* THE PANEL. Absolute inside .nav, which is position:sticky and so is
     the containing block. Not fixed: a fixed child of a transformed or
     backdrop-filtered parent positions against the parent anyway, and
     this header carries backdrop-filter. */
  .js-nav .nav__links{
    display:none;
    position:absolute; top:100%; left:0; right:0;
    order:0; flex-basis:auto; flex-direction:column; gap:0;
    margin:0; padding:4px var(--gut) 12px;
    font-size:17px;
    background:var(--paper);
    border-bottom:1px solid rgba(0,27,61,.10);
    box-shadow:0 20px 44px -26px rgba(4,20,43,.6);
    /* dvh, not vh: on a phone vh is the tallest the viewport ever gets,
       so a vh ceiling is no ceiling at all while the browser chrome is
       on screen. If the list ever outgrows the space it scrolls, and
       overscroll-behavior keeps that scroll off the page behind it. */
    max-height:calc(100dvh - var(--nav-h) - 8px);
    overflow-y:auto; overscroll-behavior:contain;
  }
  /* Shut means display:none, not opacity:0. An invisible menu you can
     still Tab into is worse than no menu, because the focus ring goes
     somewhere the visitor cannot see. */
  .js-nav .nav__links[data-open]{display:flex}

  .js-nav .nav__links a{
    display:flex; align-items:center;
    min-height:52px; padding:0 4px;
    color:var(--ink); font-weight:500;
    border-top:1px solid rgba(0,27,61,.08);
  }
  .js-nav .nav__links a:first-child{border-top:none}
  .js-nav .nav__links a:active{background:rgba(0,27,61,.045)}
  .js-nav .nav__links a:focus-visible{
    outline:2px solid var(--ember); outline-offset:-2px; border-radius:8px;
  }
}

@media (max-width:900px) and (prefers-reduced-motion:reduce){
  .nav__burger i, .nav__burger i::before, .nav__burger i::after{transition:none}
}

/* ============================================================
   HERO
   Mobile-first: the CTA must be on screen without a scroll.
   ============================================================ */
.hero{
  position:relative; isolation:isolate;
  background:linear-gradient(180deg,#FDFDFC 0%, #F4F6F9 100%);  /* white, with a whisper of tone - not a dead void */
  color:var(--ink);
  overflow:hidden;
  /* The device sits at ~88% down her-kitchen-wide.jpg and her face at ~15%.
     object-fit:cover shows 1.50/(hero aspect) of the photo's height, so at the
     old ~2.4:1 hero only ~61% was visible and the two could never both be in
     frame - which is why the counter, and the product on it, were cropped away
     while the alt text still claimed it was there. At ~1.8:1 the window is
     ~84% and both fit. */
  /* Capped by viewport HEIGHT, not a pixel value: a pixel cap stops the hero
     growing while the screen keeps widening, so on a 1920 or 2560 display the
     aspect runs back past 2:1 and crops the counter away again. 100vh keeps
     the hero near 1.8:1 at every width while never taller than the screen. */
  min-height:auto;   /* was 100vh - let the hero be its content height so it fits */
}
.hero__media{position:absolute; inset:0; z-index:-2}
.hero__media img{
  width:100%; height:100%; object-fit:cover;
  object-position:52% 56%;
}
/* Mobile: text sits on top of everything, so the scrim carries the contrast. */
.hero__scrim{
  position:absolute; inset:0; z-index:-1;
  background:
    linear-gradient(180deg, rgba(4,16,34,.46) 0%, rgba(4,16,34,.26) 30%, rgba(4,16,34,.50) 66%, rgba(4,16,34,.74) 100%);
}
/* Desktop: weight it left so her face, the counter and the lit speaker survive. */
@media (min-width:960px){
  .hero__scrim{
    background:
      linear-gradient(96deg, rgba(4,16,34,.93) 0%, rgba(4,16,34,.80) 30%, rgba(4,16,34,.42) 58%, rgba(4,16,34,.30) 100%),
      linear-gradient(180deg, rgba(4,16,34,.42) 0%, transparent 26%, transparent 74%, rgba(4,16,34,.58) 100%);
  }
}
.hero__in{padding-block:clamp(18px,4vw,64px) clamp(30px,5vw,72px)}
.hero__grid{display:grid; gap:clamp(20px,4vw,56px)}
.hero h1{
  /* Down from 64px. Three tiers now sit above the paragraph rather than
     two, so the headline no longer has to carry the whole hero on its own
     and can stop shouting. Leading tightened with it: at 1.06 the two
     display lines read as one block instead of a list. */
  font-size:clamp(26px,4.3vw,42px);
  line-height:1.06;
  margin-block:10px 0;
  text-wrap:balance;
  text-shadow:none;   /* removed - hero is white now, no scrim to survive */
}
.hero h1 .h1a,.hero h1 .lit{display:block}
.hero h1 .lit{
  color:var(--ember);
  font-weight:800;
}
.hero__sub{
  margin-top:16px;
  font-size:clamp(15px,2.05vw,17.5px);
  line-height:1.5;
  color:var(--ink-2);          /* readable on white (was the dark-hero light colour) */
  max-width:34em;
}
/* white hero: dark text needs no shadow, and bold reads navy not white */
.hero__sub b{color:var(--ink); font-weight:600}
/* THE PRODUCT STAGE. A cream product on flat white washes out; give it a
   graduated cove to read against, a grounded contact shadow so it sits on a
   surface instead of floating, and a whisper of its own amber band-glow -
   the one spark of life in the calm. Sized to support the headline, not shout. */
.hero__stage{
  position:relative; isolation:isolate;
  width:min(290px, 66%);
  margin-top:clamp(16px,2vw,24px);
  padding:clamp(30px,3.6vw,52px) clamp(26px,3vw,44px) clamp(34px,4vw,58px);
  border-radius:28px;
  background:radial-gradient(130% 98% at 50% 14%, #F4F6F9 0%, #DBE1EA 52%, #C6CFDA 100%);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.9), 0 34px 64px -36px rgba(4,20,43,.30);
}
.hero__stage::before{   /* warm ambient from the device's light band */
  content:""; position:absolute; left:50%; bottom:20%; transform:translateX(-50%);
  width:62%; height:30%; z-index:0; pointer-events:none;
  background:radial-gradient(50% 60% at 50% 50%, rgba(232,163,61,.30), transparent 72%);
  filter:blur(16px);
}
.hero__stage::after{    /* grounded contact shadow (an ellipse on the "floor") */
  content:""; position:absolute; left:50%; bottom:14%; transform:translateX(-50%);
  width:58%; height:9%; z-index:0; pointer-events:none;
  background:radial-gradient(50% 50% at 50% 50%, rgba(4,20,43,.30), transparent 72%);
  filter:blur(9px);
}
.hero__product{
  position:relative; z-index:1;
  display:block; width:100%; height:auto;
  filter:drop-shadow(0 10px 14px rgba(4,20,43,.14));
}
.hero__facts{
  display:flex; flex-wrap:wrap; gap:8px 18px; margin-top:16px;
  font-size:13.5px; color:var(--on-night-2);
}
.hero__facts span{display:inline-flex; align-items:center; gap:7px}
.hero__facts span::before{
  content:""; width:6px; height:6px; border-radius:50%;
  background:var(--ember-lit); box-shadow:0 0 8px 1px rgba(122,204,255,.8);
}
@media (min-width:960px){
  /* Product forward: the photo column is the wider of the two now, so the
     device reads as the subject of the page, not a sidebar. */
  .hero__grid{grid-template-columns:.92fr 1.08fr; align-items:center}
  .hero__media img{object-position:52% 56%}
  .hero__photo{margin-top:0}
}

/* ---------- Welyto-style hero: real product photo + one CTA button ---------- */
.hero__cta{
  margin-top:clamp(22px,2.6vw,30px);
  font-size:16.5px; font-weight:700; letter-spacing:-.01em;
  padding:15px 32px; border-radius:999px;
}
.hero__photo{margin-top:clamp(22px,3.4vw,26px)}
.hero__photo img{
  display:block; width:100%; height:auto;
  border-radius:22px;
  box-shadow:0 42px 80px -42px rgba(4,20,43,.48), 0 10px 24px -14px rgba(4,20,43,.28);
}

/* signup modal: the form card IS the dialog, so the box is a bare positioned
   wrapper and .card-form supplies the surface. */
.modal__box--signup{
  position:relative;
  width:100%; max-width:432px;
  padding:0; background:transparent; box-shadow:none; overflow:visible;
}
.modal--signup .modal__x{background:#fff; box-shadow:var(--shadow-2); z-index:2}

/* ---------- form card ---------- */
.card-form{
  background:rgba(250,247,241,.97);
  color:var(--ink);
  border-radius:18px;
  padding:clamp(18px,3vw,26px);
  box-shadow:var(--shadow-2);
  border:1px solid rgba(0,27,61,.07);
}
.card-form h3{font-size:17px; font-weight:650; letter-spacing:-.01em}
.card-form .fine{margin-top:4px}
.field{margin-top:10px}
.field label{display:block; font-size:12.5px; font-weight:600; color:var(--ink-2); margin-bottom:5px}
.field input{
  width:100%; padding:12px 14px; min-height:46px;
  font:inherit; font-size:16px;
  color:var(--ink); background:#fff;
  border:1.5px solid rgba(0,27,61,.16); border-radius:11px;
  transition:border-color .15s ease, box-shadow .15s ease;
}
.field input::placeholder{color:#67748A}
.field input:focus{outline:none; border-color:var(--ember); box-shadow:0 0 0 4px rgba(56,182,255,.16)}
.field input[aria-invalid="true"]{border-color:#B4372C; box-shadow:0 0 0 4px rgba(180,55,44,.12)}
.check{
  display:flex; gap:11px; align-items:flex-start;
  margin-top:14px; padding:11px 12px;
  background:var(--paper-2); border:1px solid rgba(0,27,61,.09); border-radius:11px;
  cursor:pointer;
}
.check input{width:20px; height:20px; margin:1px 0 0; accent-color:var(--ember); flex:none}
.check span{font-size:14px; line-height:1.4; font-weight:600; color:var(--ink)}
.check span i{display:block; font-style:normal; font-weight:400; font-size:12.5px; color:var(--ink-3); margin-top:2px}
.card-form .btn{margin-top:14px}
.form-note{margin-top:11px; font-size:12px; line-height:1.5; color:var(--ink-3)}
.form-note a{color:var(--ink-2)}
.form-err{
  display:none; margin-top:10px; padding:9px 12px; border-radius:9px;
  background:#FBE9E7; color:#8E2B21; font-size:13.5px; font-weight:600;
}
.form-err.on{display:block}

/* ============================================================
   THE MOMENT - the emotional core, on the warm dark
   ============================================================ */
.moment{background:var(--night); color:var(--on-night); overflow:hidden; position:relative}
.moment::before{
  content:""; position:absolute; inset:auto 0 -30% 0; height:70%;
  background:radial-gradient(60% 100% at 50% 100%, rgba(56,182,255,.20), transparent 70%);
  pointer-events:none;
}
.moment__media{position:relative}
.moment__media img{width:100%; height:clamp(200px,34vw,380px); object-fit:cover; object-position:center 46%}
.moment__media::after{
  content:""; position:absolute; inset:0;
  background:linear-gradient(180deg, var(--night) 0%, rgba(4,20,43,.15) 32%, rgba(4,20,43,.2) 62%, var(--night) 100%);
}
.scene{max-width:820px; margin-inline:auto; text-align:center}
.scene p{
  font-family:var(--display); font-weight:400;
  font-size:clamp(21px,3.5vw,34px); line-height:1.36; letter-spacing:-.012em;
  color:var(--on-night);
  margin-top:clamp(20px,3.2vw,34px);
  text-wrap:balance;
}
.scene p.dim{color:var(--on-night-2); font-size:clamp(18px,2.6vw,26px)}
.scene p .lit{color:var(--ember-hot); font-style:italic}
.scene .said{
  display:block; font-size:clamp(24px,4.6vw,46px); font-style:italic;
  color:var(--ember-hot); margin-top:8px;
  font-variation-settings:"SOFT" 70,"WONK" 1;
}
.scene__tail{
  margin-top:clamp(30px,4.5vw,52px); font-family:var(--ui);
  font-size:15.5px; color:var(--on-night-2); letter-spacing:.01em;
}

/* ============================================================
   generic section headers
   ============================================================ */
.head{max-width:720px}
.head h2{
  font-family:var(--display); font-weight:400;
  font-size:clamp(19px,1.9vw,23px); line-height:1.2; letter-spacing:-.02em;
  margin-top:6px; text-wrap:balance;
}
.head.on-dark h2{color:var(--on-night)}
.head p{margin-top:8px}
.head.center{margin-inline:auto; text-align:center}

/* ---------- two-up ---------- */
.two{display:grid; gap:clamp(28px,5vw,64px); align-items:center}
@media (min-width:900px){
  .two{grid-template-columns:1fr 1fr}
  .two.top{align-items:start}
  .two.wide-left{grid-template-columns:1.12fr .88fr}
  .two.wide-right{grid-template-columns:.88fr 1.12fr}
  .two.flip > :first-child{order:2}
}
.shot{border-radius:20px; overflow:hidden; box-shadow:var(--shadow-2); background:var(--paper-3)}
.shot img{width:100%; height:100%; object-fit:cover}
.shot--tall img{aspect-ratio:4/5}
.shot--wide img{aspect-ratio:3/2}

/* A media column that runs off the edge of the page. Used where the
   photograph is the argument and a polite card would undersell it. */
@media (min-width:900px){
  /* distance from this column's left edge back to the viewport edge */
  .bleed-l{margin-left:calc(-1 * max(var(--gut), (100vw - var(--wrap))/2 + var(--gut))); border-radius:0 20px 20px 0}
  .bleed-r{margin-right:calc(-1 * max(var(--gut), (100vw - var(--wrap))/2 + var(--gut))); border-radius:20px 0 0 20px}
  .bleed-l img,.bleed-r img{height:100%; min-height:clamp(420px,44vw,600px); object-fit:cover; aspect-ratio:auto}
  .two.stretch{align-items:stretch}
}

/* ---------- fact list ---------- */
.facts{margin-top:26px; display:grid; gap:2px; border-radius:14px; overflow:hidden}
.facts li{
  list-style:none; display:flex; gap:14px; align-items:flex-start;
  background:var(--paper-2); padding:15px 18px;
}
.facts li b{display:block; font-size:15.5px; font-weight:650}
.facts li span{font-size:14.5px; color:var(--ink-3); line-height:1.5}
.facts li::before{
  content:""; flex:none; width:9px; height:9px; margin-top:7px; border-radius:2px;
  background:var(--ember); box-shadow:0 0 0 3px rgba(56,182,255,.16);
}

/* ---------- device panel ---------- */
/* ---------- the film ----------
   Sits between the dark scene and the light product section, so the frame
   reads as a lit screen in a room rather than one more dark band. The box is
   already at the size the real film will occupy: nothing below it moves on
   the day it drops in, and no measurement taken before then is invalidated. */
.film{background:var(--paper); position:relative}
.film__fig{margin:34px 0 0}
.film__frame{
  position:relative; overflow:hidden;
  aspect-ratio:16/9; border-radius:20px;
  background:var(--night);
  box-shadow:var(--shadow-2), 0 0 0 1px rgba(0,27,61,.06);
  display:grid; place-items:center;
}
.film__frame video{
  display:block; width:100%; height:100%;
  object-fit:contain; background:#000; border:0;
}
/* THE EMPTY SEAT.
   The still, the play mark and a "1:30" runtime badge used to live in here,
   directly beneath copy promising we would rather leave the seat empty than
   fill it with stock. A photograph with a play button and a duration on it is
   a video; the film does not exist. So the seat is now actually empty, and the
   frame earns its keep the way the rest of this page does - as the band, unlit,
   waiting. Everything below it still holds at 16:9 at final size, and the swap
   instruction in index.html is unchanged. */
.film__frame--empty{
  background:
    radial-gradient(120% 140% at 50% 118%, rgba(56,182,255,.22), transparent 62%),
    var(--night);
}
.film__frame--empty::after{
  /* the band, flattened and unlit, sitting where the light will be */
  content:""; position:absolute; left:12%; right:12%; bottom:0; height:2px;
  background:linear-gradient(90deg, transparent, rgba(122,204,255,.45), transparent);
}
.film__mid{position:relative; text-align:center; padding:0 26px; max-width:460px}
.film__label{
  font-family:var(--display); font-size:clamp(21px,2.4vw,27px);
  color:var(--paper); margin:0;
}
.film__sub{
  font-size:14.5px; line-height:1.6; margin:12px 0 0;
  color:rgba(250,247,241,.62);
}
.film__cap{
  display:block; margin-top:18px; text-align:center;
  font-size:15px; line-height:1.65; color:var(--ink-3);
}
@media (max-width:640px){
  /* the sub used to be hidden here to make room for the play mark; with the
     seat empty there is nothing competing for the middle of the frame */
  .film__sub{font-size:13.5px}
}

/* honeypot - present to a bot, absent to everyone else. Not display:none,
   because some bots skip what is display:none. */
.hp{position:absolute; left:-9999px; width:1px; height:1px; overflow:hidden}

/* ---------- the spec note ----------
   Deliberately NOT another .facts list. Two columns that disagree with each
   other: what is settled, printed solid, and what is still open, printed on a
   dashed edge with no fill so the eye reads it as unfinished. A page for a
   product that does not exist yet should have a column like the right-hand
   one, and no template ships with it. */
.spec{
  margin-top:28px; display:grid; gap:20px;
  border-top:1px solid rgba(0,27,61,.14); padding-top:24px;
}
@media (min-width:820px){ .spec{grid-template-columns:1fr 1fr; gap:34px} }
.spec__h{
  font-size:11.5px; font-weight:700; letter-spacing:.15em; text-transform:uppercase;
  color:var(--ink-3); display:flex; align-items:center; gap:9px;
}
.spec__h::before{
  content:""; flex:none; width:9px; height:9px; border-radius:2px;
  background:var(--ember); box-shadow:0 0 0 3px rgba(56,182,255,.16);
}
.spec__col--open .spec__h::before{
  background:transparent; border:1.5px dashed var(--ink-3); box-shadow:none;
}
.spec dl{margin:14px 0 0}
.spec dl > div{padding:11px 0; border-top:1px solid rgba(0,27,61,.09)}
.spec dl > div:first-child{border-top:0; padding-top:2px}
.spec dt{font-size:14.5px; font-weight:650; color:var(--ink)}
.spec dd{margin:3px 0 0; font-size:14px; line-height:1.55; color:var(--ink-3)}
.spec dd b{font-weight:650; color:var(--ink-2)}
.spec__col--open{
  border-left:0; padding-left:0;
}
@media (min-width:820px){
  .spec__col--open{border-left:1px dashed rgba(0,27,61,.22); padding-left:34px}
}

.device{background:var(--paper-2)}
.device__strip{display:grid; grid-template-columns:repeat(3,1fr); gap:10px; margin-top:14px}
.device__strip img{border-radius:11px; aspect-ratio:3/2; object-fit:cover; background:var(--paper-3)}
.device__cap{margin-top:12px; font-size:12.5px; color:var(--ink-3); line-height:1.5}

/* ============================================================
   THE DEMO - mechanism made visible, and labelled as a simulation
   ============================================================ */
.demo{background:var(--night); color:var(--on-night)}
.demo__grid{display:grid; gap:clamp(24px,4vw,48px); margin-top:clamp(28px,4vw,48px)}
@media (min-width:940px){ .demo__grid{grid-template-columns:1fr 1fr; align-items:stretch} }

.panel{
  background:var(--night-2); border:1px solid rgba(255,255,255,.08);
  border-radius:20px; padding:clamp(16px,2.4vw,22px);
  /* A grid item defaults to min-width:auto, so this panel was being sized by
     the min-content of the compose row inside it - an <input> carries a
     default `size` of 20 characters - and at 390px it stood 31px wider than
     its own track. That is where the page's 11px of horizontal scroll came
     from. Pre-existing; it reproduces with the original placeholder text. */
  min-width:0;
}
.panel__label{
  display:flex; align-items:center; gap:9px;
  font-size:11.5px; font-weight:700; letter-spacing:.14em; text-transform:uppercase;
  color:var(--on-night-2); margin-bottom:14px;
}
.panel__label::before{content:""; width:7px; height:7px; border-radius:50%; background:var(--on-night-2)}
.panel--live .panel__label::before{background:var(--ember-lit); box-shadow:0 0 9px 1px rgba(122,204,255,.9)}

.compose{display:flex; gap:9px; margin-top:12px}
.compose input{
  flex:1; min-width:0; padding:12px 14px; min-height:48px;
  font:inherit; font-size:16px; color:var(--on-night);
  background:rgba(255,255,255,.06); border:1.5px solid rgba(255,255,255,.14);
  border-radius:12px;
}
.compose input::placeholder{color:#A6998C}
.compose input:focus{outline:none; border-color:var(--ember-lit); box-shadow:0 0 0 4px rgba(122,204,255,.16)}
.thread{display:flex; flex-direction:column; gap:9px; min-height:96px}
.thread__empty{
  margin:auto 0; text-align:center; font-size:14.5px; color:#A6998C;
  font-style:italic; padding:18px 8px;
}
.thread.used .thread__empty{display:none}
.bub{
  max-width:82%; padding:11px 15px; border-radius:16px;
  font-size:15.5px; line-height:1.45; word-break:break-word;
  animation:pop .28s ease both;
}
.bub--you{align-self:flex-end; background:#3C4A5E; color:#fff; border-bottom-right-radius:5px}
.bub--her{align-self:flex-start; background:rgba(122,204,255,.14); color:var(--ember-hot); border:1px solid rgba(122,204,255,.28); border-bottom-left-radius:5px}
.bub small{display:block; margin-top:5px; font-size:11.5px; opacity:.72; font-weight:600; letter-spacing:.04em}
@keyframes pop{from{opacity:0; transform:translateY(8px) scale(.98)} to{opacity:1; transform:none}}

/* (the earlier photo-room block removed 2026-08-19: the rebuilt section reuses
   the .room name for a bare photograph, and the old rules put a dark fill
   and a 16px radius behind it, re-creating the card we just deleted) */

/* ============================================================
   A/B BLOCK - the one section that differs between versions.
   Both arms use the SAME chrome so the test measures the message,
   not the render.
   ============================================================ */
.arm{display:none}
body.v-text .arm--text{display:block}
body.v-app  .arm--app{display:block}
/* Without JS there is no body class, so the default arm must still render.
   A missing how-it-works block is worse than an unassigned test cell. */
html.no-js .arm--text{display:block}

.mock{
  background:var(--paper); border:1px solid rgba(0,27,61,.10);
  border-radius:22px; box-shadow:var(--shadow-2); overflow:hidden;
  max-width:380px; margin-inline:auto; width:100%;
}
.mock__bar{
  display:flex; align-items:center; gap:10px;
  padding:13px 16px; border-bottom:1px solid rgba(0,27,61,.09);
  background:var(--paper-2);
}
.mock__av{
  width:32px; height:32px; border-radius:50%; flex:none;
  background:var(--ink); color:var(--paper);
  display:grid; place-items:center; font-size:13px; font-weight:700;
}
.mock__who{font-size:15px; font-weight:650; line-height:1.2}
.mock__who small{display:block; font-size:11.5px; font-weight:500; color:var(--ink-3)}
.mock__body{padding:16px; display:flex; flex-direction:column; gap:9px; background:var(--paper)}
.mock__b{max-width:80%; padding:10px 14px; border-radius:15px; font-size:14.5px; line-height:1.45}
.mock__b--out{align-self:flex-end; background:var(--ink); color:var(--paper); border-bottom-right-radius:5px}
.mock__b--in{align-self:flex-start; background:var(--paper-3); color:var(--ink); border-bottom-left-radius:5px}
.mock__b em{display:block; font-style:normal; font-size:11.5px; opacity:.7; margin-top:4px}
.mock__voice{
  align-self:flex-start; display:flex; align-items:center; gap:10px;
  padding:10px 14px; border-radius:15px; background:var(--paper-3); border-bottom-left-radius:5px;
}
.mock__voice .bars{display:flex; align-items:center; gap:3px; height:20px}
.mock__voice .bars i{display:block; width:3px; border-radius:2px; background:var(--ember)}
.mock__foot{padding:12px 16px; border-top:1px solid rgba(0,27,61,.09); background:var(--paper-2); font-size:13px; color:var(--ink-3)}

/* ---------- steps ---------- */
.steps{display:grid; gap:18px; margin-top:clamp(28px,4vw,44px)}
@media (min-width:860px){ .steps{grid-template-columns:repeat(3,1fr); gap:22px} }
/* Two real jobs, side by side. The third is not a job and is not a card. */
@media (min-width:700px){ .steps--two{grid-template-columns:1fr 1fr; gap:22px} }
.step{
  background:var(--paper); border:1px solid rgba(0,27,61,.09);
  border-radius:18px; padding:24px 22px 26px; box-shadow:var(--shadow-1);
  position:relative; overflow:hidden;
}
.step::after{
  content:""; position:absolute; left:22px; right:22px; top:0; height:3px;
  background:var(--ember); box-shadow:0 3px 12px -1px rgba(56,182,255,.75); border-radius:0 0 3px 3px;
}
.step__n{
  font-family:var(--display); font-size:34px; line-height:1;
  color:var(--ember); margin-bottom:12px;
}
.step h3{font-size:19px; font-weight:650; letter-spacing:-.01em; line-height:1.25}
.step p{margin-top:9px; font-size:15.5px; color:var(--ink-2); line-height:1.55}

/* Step three: wide, unlit, no ember hairline, no card shadow. It is the one
   step that is not work, so it is not built like the two that are. The number
   sits beside the words rather than above them, which is the only place on
   this page a step number does that. */
.step--none{
  margin-top:18px; display:flex; gap:clamp(16px,3vw,30px); align-items:baseline;
  background:transparent; border:0; border-top:1px solid rgba(0,27,61,.14);
  border-radius:0; box-shadow:none; padding:24px 2px 0;
}
.step--none::after{display:none}
.step--none .step__n{margin-bottom:0; font-size:clamp(38px,6vw,58px); color:var(--linen); flex:none}
.step--none h3{
  font-family:var(--display); font-weight:400; letter-spacing:-.02em;
  font-size:clamp(24px,3.6vw,36px); line-height:1.12;
}
.step--none p{max-width:52ch; margin-top:11px}

/* ---------- the other rooms ----------
   The widen, and it is a LAYOUT problem before it is a copy problem. Four
   equal cards would have said "here are four segments" and the page would
   have read as a brochure listing markets. So the items are deliberately
   unequal: three sit in a two-up grid and one spans the whole width and
   carries a line of speech at display size. The reader is meant to find the
   one that is theirs, not to read all four.

   No icons, no parallel headings, no border on three sides. A hairline above
   each item and nothing else, so it reads as a column of notes rather than a
   component instanced four times. */
.rooms{background:var(--paper-2)}
.rooms__list{
  list-style:none; margin:clamp(28px,4vw,44px) 0 0; padding:0;
  display:grid; gap:0;
}
@media (min-width:860px){
  .rooms__list{grid-template-columns:1fr 1fr; column-gap:clamp(34px,5vw,64px)}
}
.rooms__item{
  min-width:0;
  border-top:1px solid rgba(0,27,61,.14);
  padding:22px 0 26px;
}
.rooms__who{
  font-size:11.5px; font-weight:700; letter-spacing:.15em; text-transform:uppercase;
  color:var(--ink-3); margin:0;
  display:flex; align-items:center; gap:9px;
}
.rooms__who::before{
  content:""; flex:none; width:9px; height:9px; border-radius:2px;
  background:var(--ember); box-shadow:0 0 0 3px rgba(56,182,255,.16);
}
.rooms__body{
  margin:12px 0 0; max-width:46ch;
  font-size:16.5px; line-height:1.6; color:var(--ink-2);
}
/* The one that spans. It holds the shortest thing on the section and prints
   it largest, which is the only way four items of similar word count stop
   looking like a set. */
.rooms__item--wide{grid-column:1/-1}
.rooms__said{
  overflow-wrap:anywhere;
  font-family:var(--display); font-weight:400; letter-spacing:-.02em;
  font-size:clamp(28px,5vw,52px); line-height:1.1;
  color:var(--ink); margin:14px 0 0; max-width:18ch;
}
.rooms__item--wide .rooms__body{margin-top:14px; color:var(--ink-3); max-width:52ch}
.rooms__tail{
  margin:26px 0 0; padding-top:22px;
  border-top:1px solid rgba(0,27,61,.14);
  font-family:var(--display); font-weight:400; letter-spacing:-.015em;
  font-size:clamp(20px,2.6vw,27px); line-height:1.28; color:var(--ink);
  max-width:34ch;
}

/* ---------- guest list ---------- */
.guests{display:flex; flex-wrap:wrap; gap:9px; margin-top:22px}
.guest{
  display:inline-flex; align-items:center; gap:9px;
  padding:9px 15px 9px 10px; border-radius:999px;
  background:var(--paper); border:1px solid rgba(0,27,61,.12);
  font-size:14.5px; font-weight:600;
}
.guest i{
  width:24px; height:24px; border-radius:50%; flex:none; display:grid; place-items:center;
  background:var(--linen); color:var(--ink); font-style:normal; font-size:11px; font-weight:700;
}
.guest b{font-weight:inherit}
.guest--off{opacity:.5; text-decoration:line-through}
.guest--none{border-style:dashed; color:var(--ink-3); font-weight:500}

/* the working list - added by app.js, so it never appears without JS */
.guest__x{
  all:unset; cursor:pointer; margin-left:2px; margin-right:-4px;
  width:20px; height:20px; border-radius:50%; display:grid; place-items:center;
  font-size:15px; line-height:1; color:var(--ink-3); flex:none;
  transition:background .15s ease, color .15s ease;
}
.guest__x:hover{background:rgba(0,27,61,.08); color:var(--ink)}
.guest__x:focus-visible{outline:2px solid var(--ember); outline-offset:2px}
.guest.is-off{opacity:.45}
.guest.is-off b{text-decoration:line-through}
.guest.is-off .guest__x{transform:rotate(45deg)}
.guest--new i{background:var(--ember); color:#fff}

.guest-add{max-width:420px; margin:26px auto 0; text-align:center}
.guest-add label{display:block; margin-bottom:8px; font-weight:600; color:var(--ink-2)}
.guest-add__row{display:flex; gap:9px}
.guest-add input{
  flex:1; min-width:0; padding:12px 14px; border-radius:12px;
  border:1.5px solid rgba(0,27,61,.18); background:var(--paper);
  font:inherit; font-size:15px; color:var(--ink);
}
.guest-add input::placeholder{color:#67748A}
.guest-add input:focus{outline:none; border-color:var(--ember); box-shadow:0 0 0 4px rgba(56,182,255,.16)}
.guest-add__note{margin-top:10px; text-align:left}

/* ---------- who is asking: the signed note ----------
   A letter, not a section. Narrow measure, no card, no grid, ragged right,
   and the one statistic set at the scale of a headline INSIDE the prose
   rather than boxed off beside a twin. The .stat / .stats card pair that used
   to live here was deleted with the plate it belonged to. */
.evidence{background:var(--paper-2)}
.note{max-width:640px}
.note h2{
  font-family:var(--display); font-weight:400;
  font-size:clamp(27px,4.1vw,44px); line-height:1.1; letter-spacing:-.02em;
  margin-top:12px; text-wrap:balance;
}
.note__p{
  margin-top:20px; font-size:clamp(16.5px,1.9vw,18.5px);
  line-height:1.7; color:var(--ink-2); max-width:58ch;
}
.note__fig{
  margin:30px 0 0; padding:24px 0 22px;
  border-top:1px solid rgba(0,27,61,.16); border-bottom:1px solid rgba(0,27,61,.16);
}
.note__fig b{
  display:block; font-family:var(--display); font-weight:400;
  font-size:clamp(58px,11vw,104px); line-height:.9; letter-spacing:-.04em;
  color:var(--ink);
}
.note__fig p{
  margin-top:14px; font-size:clamp(16px,1.9vw,18px); line-height:1.6;
  color:var(--ink-2); max-width:52ch;
}
.note__fig p em{font-style:normal; font-weight:650; color:var(--ink)}
.note__fig figcaption{
  margin-top:14px; font-size:12.5px; line-height:1.55; color:var(--ink-3); max-width:60ch;
}
.note__sign{
  margin-top:28px; font-family:var(--display); font-weight:400;
  font-size:clamp(22px,3vw,28px); line-height:1.2; color:var(--ink); letter-spacing:-.01em;
}
.note__sign span{
  display:block; margin-top:7px;
  font-family:var(--ui); font-size:12.5px; font-weight:500; letter-spacing:.01em;
  color:var(--ink-3);
}
.attrib{
  margin-top:26px; padding:16px 18px; border-radius:14px;
  background:var(--paper); border:1px dashed rgba(0,27,61,.20);
  font-size:14px; line-height:1.6; color:var(--ink-2);
}

/* ---------- faq ---------- */
.faq{max-width:820px; margin-inline:auto; margin-top:clamp(26px,4vw,42px)}
.faq details{border-bottom:1px solid rgba(0,27,61,.12)}
.faq summary{
  list-style:none; cursor:pointer; padding:20px 44px 20px 0; position:relative;
  font-size:clamp(17px,2.2vw,20px); font-weight:600; letter-spacing:-.01em;
}
.faq summary::-webkit-details-marker{display:none}
.faq summary::after{
  content:""; position:absolute; right:8px; top:50%; width:11px; height:11px;
  border-right:2px solid var(--ember); border-bottom:2px solid var(--ember);
  transform:translateY(-70%) rotate(45deg); transition:transform .22s ease;
}
.faq details[open] summary::after{transform:translateY(-30%) rotate(-135deg)}
.faq p{padding:0 0 22px; font-size:16px; line-height:1.62; color:var(--ink-2); max-width:62ch}

/* ---------- closing ---------- */
.close-cta{background:var(--night); color:var(--on-night); position:relative; overflow:hidden}
.close-cta::before{
  content:""; position:absolute; inset:-20% -10% auto -10%; height:80%;
  background:radial-gradient(50% 100% at 50% 0%, rgba(56,182,255,.22), transparent 70%);
}
.close-cta__grid{display:grid; gap:clamp(28px,4vw,56px); position:relative}
@media (min-width:940px){ .close-cta__grid{grid-template-columns:1.05fr .95fr; align-items:center} }
.close-cta h2{
  font-family:var(--display); font-weight:400;
  font-size:clamp(31px,5.6vw,60px); line-height:1.06; letter-spacing:-.022em;
  text-wrap:balance;
}
.close-cta h2 .lit{color:var(--ember-hot); font-style:italic}
.close-cta .lede{color:var(--on-night-2); margin-top:20px}

/* ---------- footer ---------- */
.foot{background:var(--night-2); color:var(--on-night-2); padding-block:40px 48px}
.foot__in{display:flex; flex-wrap:wrap; gap:20px 32px; align-items:center}
.foot img{height:24px; width:auto; opacity:.9}
.foot__links{display:flex; gap:22px; margin-left:auto; font-size:14px}
.foot__links a{text-decoration:none}
.foot__links a:hover{color:var(--on-night)}
.foot__legal{width:100%; font-size:12.5px; line-height:1.6; color:#A2968A; margin-top:6px}

/* ---------- modal (post-signup questionnaire) ---------- */
.modal{
  position:fixed; inset:0; z-index:100; display:none;
  align-items:center; justify-content:center; padding:16px;
  background:rgba(16,11,8,.72); backdrop-filter:blur(4px);
}
.modal.on{display:flex}
.modal__box{
  width:100%; max-width:600px; max-height:92vh; overflow:auto;
  background:var(--paper); border-radius:20px; box-shadow:var(--shadow-2);
  padding:clamp(22px,3.4vw,34px);
}
.modal__x{
  position:absolute; top:14px; right:14px; width:38px; height:38px;
  border-radius:50%; border:none; background:rgba(0,27,61,.07); cursor:pointer; font-size:19px;
}
.modal__head{display:flex; align-items:flex-start; gap:14px}
.modal__head img{height:26px; width:auto}
.modal h3{font-family:var(--display); font-weight:400; font-size:clamp(24px,3.4vw,32px); line-height:1.12; letter-spacing:-.02em}
.q-prog{height:5px; border-radius:3px; background:var(--paper-3); overflow:hidden; margin:20px 0 22px}
.q-prog i{display:block; height:100%; width:0; background:var(--ember); transition:width .35s ease}
.q-count{font-size:12px; font-weight:700; letter-spacing:.14em; text-transform:uppercase; color:var(--ink-3)}
.q-text{font-size:clamp(18px,2.6vw,22px); font-weight:600; line-height:1.35; margin-top:10px; letter-spacing:-.01em}
.q-hint{font-size:14px; color:var(--ink-3); margin-top:6px}
.q-opts{display:grid; gap:8px; margin-top:18px}
.q-opt{
  text-align:left; padding:14px 16px; border-radius:12px; cursor:pointer;
  background:var(--paper-2); border:1.5px solid rgba(0,27,61,.10);
  font-size:15.5px; font-weight:500; transition:border-color .14s, background .14s;
}
.q-opt:hover{border-color:var(--ember); background:#fff}
.q-open{
  width:100%; margin-top:18px; padding:13px 15px; min-height:110px;
  font:inherit; font-size:16px; border-radius:12px;
  border:1.5px solid rgba(0,27,61,.16); background:#fff; resize:vertical;
}
.q-open:focus{outline:none; border-color:var(--ember); box-shadow:0 0 0 4px rgba(56,182,255,.16)}
/* the conditional phone follow-up */
.q-tel{
  width:100%; margin-top:18px; padding:13px 15px;
  font:inherit; font-size:17px; letter-spacing:.4px; border-radius:12px;
  border:1.5px solid rgba(0,27,61,.16); background:#fff;
}
.q-tel:focus{outline:none; border-color:var(--ember); box-shadow:0 0 0 4px rgba(56,182,255,.16)}
.q-actions{display:flex; gap:12px; align-items:center; margin-top:20px}
.q-skip{background:none; border:none; cursor:pointer; font-size:14.5px; color:var(--ink-3); text-decoration:underline; padding:8px}

/* ---------- reveal ---------- */
.rv{opacity:0; transform:translateY(18px); transition:opacity .7s ease, transform .7s cubic-bezier(.2,.7,.3,1)}
.rv.in{opacity:1; transform:none}
.rv-1{transition-delay:.06s} .rv-2{transition-delay:.14s} .rv-3{transition-delay:.22s}
.rv-4{transition-delay:.30s} .rv-5{transition-delay:.38s} .rv-6{transition-delay:.46s}

/* ============================================================
   MOBILE FOLD
   Paid social is a mobile channel, so the primary button has to be on
   screen without a scroll. Below 760px the hero sheds everything that is
   not load-bearing and the signup ships collapsed to email + button; it
   opens to its full field set - name, and the reserve checkbox that is
   the strongest signal on the page - the instant anyone touches it, which
   is always before a submit is possible.
   Placed last on purpose: it has to win the cascade over the component
   rules above it.
   ============================================================ */
@media (max-width:759px){
  /* Was .nav__in{height:56px}. Moved onto the token because the bar is no
     longer the only thing that needs this number: the anchor offset and
     the menu panel's ceiling are both derived from it, and a hard height
     here would also have overridden the wrapping no-script header below
     900px, which is declared at the same weight and earlier in the file. */
  :root{--nav-h:56px}
  .m-drop{display:none !important}
  .hero__facts{display:none}
  .hero__sub{font-size:15.5px; line-height:1.46; margin-top:12px}
  /* The photograph stops being wallpaper and becomes the first thing on
     screen; the first headline line demotes to a kicker so the second one
     can stay large and the button can still clear the fold. */
  .hero{display:flex; flex-direction:column}
  /* her-kitchen.jpg is 928x1152 portrait. Measured on it: the top of her head
     is at ~29% and the device at ~69-79% - a 50% span. At the old 196px the
     band showed only ~40% of the image height, so whatever the position one of
     the two was always cut, and it was her head. 300px clears the span with
     margin on every common handset, including a 430px Pro Max where 272 put
     the device exactly on the window edge. */
  .hero__media{position:relative; order:-1; height:min(44vh,300px); flex:none}
  .hero__media img{object-position:50% 58%}
  .hero__media::after{
    content:""; position:absolute; inset:auto 0 -1px 0; height:58px;
    background:linear-gradient(180deg, transparent, var(--night));
  }
  .hero__scrim{display:none}
  .hero h1{font-size:29px; margin-top:0}
  /* Mike's 2026-08-27 headline is ONE sentence split across the two spans so
     the accent can fall on the payoff half. .h1--sentence opts out of the
     kicker re-skin below: half a sentence set as a small uppercase category
     label reads wrong, and phones are the channel this page is bought for. */
  .hero h1.h1--sentence .h1a{
    font-family:inherit; font-size:inherit; font-weight:inherit;
    letter-spacing:normal; text-transform:none; color:inherit;
    margin-bottom:0; line-height:inherit;
  }
  .hero h1 .h1a{
    font-family:var(--ui); font-size:11.5px; font-weight:700;
    letter-spacing:.14em; text-transform:uppercase; color:var(--ember-lit);
    margin-bottom:9px; text-shadow:none; line-height:1.5;
  }
  .hero__in{padding-top:16px; padding-bottom:22px}
  .eyebrow{font-size:11.5px; letter-spacing:.13em; line-height:1.5}
  .card-form{padding:16px}
  .card-form h3{font-size:16.5px; line-height:1.3}
  .field{margin-top:8px}
  .field label{font-size:12px; margin-bottom:4px}
  .jform.open .m-drop{display:block !important}
  .jform.open .check.m-drop{display:flex !important}
  .jform.open + .form-note{display:block}
}
@media (max-width:400px){
  /* Sized so the headline still fits one line in a fallback serif if the
     webfont never arrives - the fold cannot depend on a CDN. */
  .hero h1{font-size:25px; text-wrap:pretty}
  .hero__in{padding-top:13px}
  .hero__media{height:min(34vh,208px)}   /* was 126px: only ~26% of the photo, head always cut */
  .hero__sub{margin-top:10px}
}

@media (prefers-reduced-motion:reduce){
  html{scroll-behavior:auto}
  .rv{opacity:1; transform:none; transition:none}
  .room.on .room__wave i{animation:none; height:26px}
  .bub{animation:none}
  .btn:hover{transform:none}
}

/* print / no-js safety */
.no-js .rv{opacity:1; transform:none}

/* ============================================================
   v13 - classes replacing the inline style= attributes that were
   in the original markup. Type and colour live in CSS, never in HTML.
   ============================================================ */
.modal__box--rel{ position:relative; }
.q-h{ margin-top:16px; }
.q-p{ margin-top:12px; }

/* Blank page guard, the CSS half. If app.js never wired the reveal, the
   head script adds .rv-force after two seconds and the page shows itself.
   .no-js .rv above already covers JavaScript being switched off entirely. */
.rv-force .rv{ opacity:1; transform:none; transition:none }



/* ============================================================
   SECTION 3  -  THE DEMO, rebuilt without cards 2026-08-19

   There is no panel, no border, no rounded container. Both halves sit
   directly on the section's ground. A bordered box reads as a software
   widget, and the thing being sold is a ceramic cube on a kitchen
   counter. Six nested rounded rectangles were saying "app" on every
   edge while the copy said "furniture".

   The room is a PHOTOGRAPH. Every product shot we have ships with the
   band already lit, so quiet is made by dimming the picture and playing
   is made by letting it come back.
   ============================================================ */

.demo__two{
  display:grid; gap:clamp(22px,2.6vw,40px);
  margin-top:clamp(18px,2.2vw,30px); align-items:stretch;
}
@media (min-width:900px){ .demo__two{grid-template-columns:1fr 1fr} }

/* ---------- your side ---------- */
.side{min-width:0; display:flex; flex-direction:column}

/* The contact line does the explaining. A speaker with a phone number
   needs no sentence to say she does not own a phone. */
.to{
  display:flex; align-items:baseline; gap:9px; flex-wrap:wrap;
  font-size:13px; font-weight:600; color:var(--on-night);
  padding-bottom:9px; margin-bottom:11px;
  border-bottom:1px solid rgba(255,255,255,.10);
}
.to span{font-weight:400; color:var(--on-night-2); font-size:13px; letter-spacing:.01em}

.thread{display:flex; flex-direction:column; gap:7px; flex:1; justify-content:flex-end; padding-bottom:2px}

/* Ordinary SMS. Outgoing green because the device carries a real carrier
   number, so on a real phone that is exactly what this thread looks like.
   Incoming is neutral grey. No blue bubbles anywhere, ever. */
.bub{
  max-width:76%; padding:8px 12px; border-radius:15px;
  font-size:14px; line-height:1.4; word-break:break-word;
  animation:pop .26s ease both;
}
.bub--you{align-self:flex-end; background:#3F9E63; color:#fff; border-bottom-right-radius:5px}
.bub--her{align-self:flex-start; background:#33302D; color:var(--on-night); border-bottom-left-radius:5px}
.bub small{display:block; margin-top:5px; font-size:11px; opacity:.75; letter-spacing:.03em}

.compose{display:flex; gap:8px; margin-top:11px}
.compose input{
  flex:1; min-width:0; padding:10px 13px; font:inherit; font-size:14px;
  color:var(--on-night); background:rgba(255,255,255,.05);
  border:1px solid rgba(255,255,255,.16); border-radius:10px;
}
.compose input::placeholder{color:#9C9088}
.compose input:focus{outline:none; border-color:var(--ember-lit); box-shadow:0 0 0 3px rgba(122,204,255,.16)}
.demo__note{margin-top:9px}

/* ---------- her side ---------- */
/* No screen in this half. The absence is the argument, and it only reads
   because the other half is unmistakably a phone. */
.room{margin:0; position:relative}
.room{display:flex; flex-direction:column; justify-content:center}
/* The photograph is a DETAIL, not a backdrop. Given its own height it grew
   to 589px, dragged the section to 873px and became the biggest thing on the
   page. It is here to show the object honestly, nothing more. */

/* ============================================================
   SECTION 3  -  two true objects and one wait

   The previous shape was a glass pane and a cut-out floating on black.
   Neither was a real thing, so every treatment applied to them read as
   generic: the treatment was the only idea. A phone you would pick up
   and a room you would walk into carry the argument by themselves.
   ============================================================ */

.demo__two{
  display:grid; gap:clamp(26px,3vw,46px);
  margin-top:clamp(18px,2.2vw,30px);
  justify-items:center; align-items:stretch;
}
@media (min-width:900px){
  .demo__two{grid-template-columns:auto auto; justify-content:center}
}

/* ---------- the phone ---------- */
.phone{
  width:284px; padding:8px; border-radius:28px;
  background:linear-gradient(168deg,#1B2432,#15100E 62%,#16202C);
  box-shadow:
    0 0 0 1px rgba(255,255,255,.07),
    inset 0 1px 0 rgba(255,255,255,.13),
    0 34px 60px -34px rgba(0,0,0,1);
}
.phone__screen{
  height:300px; padding:12px 11px 11px; border-radius:20px;
  background:linear-gradient(180deg,#100C0A,#171110);
  display:flex; flex-direction:column;
  box-shadow:inset 0 0 0 1px rgba(255,255,255,.05);
}

.to{
  display:flex; align-items:baseline; gap:7px; flex-wrap:wrap;
  font-size:12px; font-weight:600; color:var(--on-night);
  padding-bottom:8px; margin-bottom:9px;
  border-bottom:1px solid rgba(255,255,255,.09);
}
.to span{font-weight:400; color:var(--on-night-2); font-size:11.5px}

.thread{display:flex; flex-direction:column; gap:6px; flex:1; justify-content:flex-end; overflow:hidden}
.bub{
  max-width:84%; padding:7px 11px; border-radius:14px;
  font-size:13px; line-height:1.38; word-break:break-word;
  animation:slide .32s cubic-bezier(.2,.85,.3,1) both;
}
.bub--you{align-self:flex-end; background:#2A8150; color:#fff; border-bottom-right-radius:4px}
.bub--her{align-self:flex-start; background:#2E2A27; color:var(--on-night); border-bottom-left-radius:4px}
.bub small{display:block; margin-top:3px; font-size:10px; opacity:.72; letter-spacing:.02em}
@keyframes slide{from{opacity:0; transform:translateY(6px)} to{opacity:1; transform:none}}

.chips{display:flex; flex-wrap:wrap; gap:5px; margin-top:9px}
.chip{
  font:inherit; font-size:11px; line-height:1; color:var(--on-night-2); cursor:pointer;
  padding:6px 10px; border-radius:999px;
  background:rgba(255,255,255,.05); border:1px solid rgba(255,255,255,.10);
  transition:background .16s, border-color .16s, color .16s;
}
.chip:hover{background:rgba(122,204,255,.14); border-color:rgba(122,204,255,.44); color:var(--ember-hot)}

.compose{display:flex; gap:6px; margin-top:9px}
.compose input{
  flex:1; min-width:0; padding:8px 11px; font:inherit; font-size:12.5px;
  color:var(--on-night); background:rgba(255,255,255,.055);
  border:1px solid rgba(255,255,255,.12); border-radius:999px;
}
.compose input::placeholder{color:#8E837B}
.compose input:focus{outline:none; border-color:rgba(122,204,255,.55); box-shadow:0 0 0 3px rgba(122,204,255,.13)}
.compose .btn{padding:8px 15px; font-size:12.5px; border-radius:999px}

.chip:focus-visible, .compose input:focus-visible, .compose button:focus-visible{
  outline:2px solid var(--ember-lit); outline-offset:2px;
}

/* ---------- the room ---------- */
.room{margin:0; width:300px; max-width:100%}
.room__frame{position:relative; border-radius:6px; overflow:hidden}
.room img{
  display:block; width:100%; height:378px; object-fit:cover; object-position:50% 52%;
  filter:brightness(.42) saturate(.7);
  transition:filter 1.5s cubic-bezier(.4,0,.2,1);
}
.room.hold img{filter:brightness(.34) saturate(.62)}
.room.on  img{filter:brightness(.94) saturate(1)}

/* The band is at 51-82% down, 52% across in this crop. Measured, not guessed. */
.room__band{
  position:absolute; left:26%; right:26%; top:56%; height:13%;
  pointer-events:none; opacity:0; border-radius:50%;
  background:linear-gradient(180deg,
      rgba(194,231,255,0), rgba(150,216,255,.9) 48%, rgba(194,231,255,0));
  filter:blur(7px);
  box-shadow:0 0 44px 18px rgba(122,204,255,.34);
  transition:opacity 1.5s cubic-bezier(.4,0,.2,1);
}
.room.on .room__band{opacity:1}
.room.hold .room__band{opacity:.12}

/* one line, under the room, in the display face */
.beat{
  margin-top:14px; min-height:3.4em;
  font-family:var(--display); font-weight:400;
  font-size:clamp(17px,1.6vw,21px); line-height:1.3; letter-spacing:-.012em;
  color:var(--on-night); text-wrap:balance;
  transition:opacity .45s ease;
}
.beat.fading{opacity:0}
.beat em{color:var(--ember-lit); font-style:normal}
.beat.said{color:var(--ember-hot)}

@media (prefers-reduced-motion:reduce){
  .bub{animation:none}
  .room img, .room__band, .beat, .chip, .compose input{transition:none}
}

.beat__who{display:block; margin-top:8px; font-family:var(--ui); font-size:11.5px;
  letter-spacing:.12em; text-transform:uppercase; color:var(--on-night-2)}

/* Visible to a screen reader, never to the eye. This was collateral in the
   stylesheet swap and its absence printed the silent beat's alt line, and
   the compose label, straight onto the page. */
.sr-only{
  position:absolute !important; width:1px; height:1px; overflow:hidden;
  clip:rect(0 0 0 0); clip-path:inset(50%); white-space:nowrap;
}

/* Restored, again: without this the dark section's subhead renders in the
   light-section ink colour, which is navy on near-black and unreadable. */
.head.on-dark .lede{color:var(--on-night-2)}

/* The device sits on the dark, centred in a box the same height as the
   phone, so the two objects are siblings without the phone being stretched
   to match a photograph's aspect. */
/* min(), not a bare 300px. An earlier .room carried max-width:100% and
   this re-declaration dropped it, so at 320px the box ran 20px past the
   gutter and sat flush against the right edge of the screen. The picture
   inside is 200px and centred, so nothing was visibly clipped, which is
   exactly why it survived: an invisible overflow one change away from
   being a horizontal scrollbar. */
.room{width:min(300px, 100%)}
.room__frame{
  height:300px; display:grid; place-items:center; border-radius:0; overflow:visible;
}
.room img{
  width:270px; height:205px; object-fit:contain;
  filter:brightness(.5) saturate(.78); transition:filter 1.5s cubic-bezier(.4,0,.2,1);
}
.room.hold img{filter:brightness(.4) saturate(.68)}
.room.on  img{filter:brightness(.86) saturate(.98)}
/* the two light strips, measured on THIS cut-out at 33% and 68% */
.room__band{left:14%; right:14%; top:auto; height:auto; border-radius:0;
  background:none; filter:none; box-shadow:none;}
.room__band::before, .room__band::after{
  content:""; position:absolute; left:0; right:0; height:9px;
  background:linear-gradient(180deg, rgba(194,231,255,0), rgba(150,216,255,.92) 46%, rgba(194,231,255,0));
  filter:blur(4px); box-shadow:0 0 26px 9px rgba(122,204,255,.30);
}
.room__band::before{top:calc(50% - 102.5px + 68px)}
.room__band::after {top:calc(50% - 102.5px + 140px)}

/* the line under it never runs wider than the object it belongs to */
.beat{max-width:300px; margin-left:auto; margin-right:auto; text-align:center}

/* BOTTOM ALIGNMENT.
   The right column was as tall as the device plus whatever the sentence
   happened to wrap to, so its last line always fell below the phone. Both
   columns are now the same height and the sentence is anchored to the
   bottom of its column, so the two objects finish on the same line no
   matter how long the beat text runs. */
.room{display:flex; flex-direction:column; height:100%}
.room__frame{flex:1; min-height:0}
.beat{
  margin-top:0; margin-bottom:0; min-height:0;
  text-align:left; max-width:none;
  display:flex; align-items:flex-end; min-block-size:3.1em;
}



/* ============================================================
   SECTION 4  -  THE TUESDAYS

   Light ground, so the page breathes after the dark demo, and the
   first tonal change since the hero.

   One device does the whole argument: seven days, three times. The
   reader is not told the middle week is emptier, they see it. The
   days are buttons because the thing worth pressing is what did or
   did not get said, and that is a sentence, not a tooltip.
   ============================================================ */
.tues{background:var(--paper-2)}
.tues__beats{list-style:none; margin:clamp(14px,1.8vw,20px) 0 0; padding:0}
.tues__beat{
  border-top:1px solid rgba(0,27,61,.13);
  padding:clamp(13px,1.5vw,18px) 0;
  display:grid; gap:clamp(9px,1vw,13px);
}
@media (min-width:820px){
  .tues__beat{grid-template-columns:1fr auto; align-items:center; column-gap:clamp(26px,4vw,56px)}
  .tues__beat .wk{grid-row:span 2; align-self:center}
  .tues__beat h3{grid-column:1}
  .tues__beat p{grid-column:1; margin-top:-6px}
}
.tues__beat h3{
  margin:0; font-family:var(--display); font-weight:400; letter-spacing:-.016em;
  font-size:clamp(18px,1.7vw,21px); line-height:1.24; color:var(--ink); max-width:24ch;
}
.tues__beat p{margin:0; font-size:14px; line-height:1.5; color:var(--ink-3); max-width:46ch}

/* the week */
.wk{display:flex; gap:6px}
.wk__d{
  font:inherit; font-size:11px; font-weight:600; letter-spacing:.04em; cursor:pointer;
  width:27px; height:33px; border-radius:6px; color:var(--ink);
  background:var(--ember); border:1px solid rgba(0,27,61,.10);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.35);
  transform:translateY(0);
  transition:transform .18s cubic-bezier(.2,.8,.3,1), background .2s, color .2s, opacity .5s;
}
.wk__d.is-off{
  background:transparent; color:rgba(0,27,61,.30);
  border-style:dashed; border-color:rgba(0,27,61,.20); box-shadow:none;
}
.wk__d:hover, .wk__d:focus-visible{transform:translateY(-3px)}
.wk__d:focus-visible{outline:2px solid var(--ink); outline-offset:2px}
.wk__d[aria-pressed="true"]{background:var(--ember-lit)}

/* the days fill in one at a time when the beat arrives, so the second
   week visibly fails to fill while you are looking at it */
.tues__beat .wk__d{opacity:0}
.tues__beat.in .wk__d{opacity:1}
.tues__beat.in .wk__d:nth-child(1){transition-delay:.04s}
.tues__beat.in .wk__d:nth-child(2){transition-delay:.10s}
.tues__beat.in .wk__d:nth-child(3){transition-delay:.16s}
.tues__beat.in .wk__d:nth-child(4){transition-delay:.22s}
.tues__beat.in .wk__d:nth-child(5){transition-delay:.28s}
.tues__beat.in .wk__d:nth-child(6){transition-delay:.34s}
.tues__beat.in .wk__d:nth-child(7){transition-delay:.40s}

.tues__said{
  margin:clamp(13px,1.6vw,18px) 0 0; padding-top:clamp(11px,1.4vw,15px);
  border-top:1px solid rgba(0,27,61,.13);
  font-family:var(--display); font-weight:400; letter-spacing:-.014em;
  font-size:clamp(17px,1.7vw,20px); line-height:1.3; color:var(--ink);
  min-height:2em; max-width:36ch;
}
.tues__said:empty::before{
  content:"Press a day."; color:var(--ink-3);
  font-family:var(--ui); font-size:14px; letter-spacing:.01em;
}
@media (prefers-reduced-motion:reduce){
  .wk__d{transition:none}
  .tues__beat .wk__d{opacity:1}
}

/* ============================================================
   THE DEMO, LIT  -  2026-08-20

   The section was dark on dark: the phone body was #1B2432 against a
   #04142B ground, so the object had no silhouette and the whole stage
   read as one flat sheet with some text on it.

   On a dark set you do not separate an object by filling it lighter,
   which just greys the picture out. You separate it with a LIGHT EDGE
   and a floor. That is how the object gets photographed in real life
   and it is the only thing that reads as depth rather than as fog.
   ============================================================ */

/* A stage: the middle of the section is lifted a little so the two
   objects sit in light rather than on a wall. */
.demo{position:relative; isolation:isolate}
.demo .wrap{position:relative; z-index:1}
.demo::before{
  content:""; position:absolute; inset:0; z-index:0; pointer-events:none;
  background:
    radial-gradient(58% 46% at 50% 56%, rgba(255,205,150,.075), transparent 72%),
    radial-gradient(90% 70% at 50% 50%, rgba(255,255,255,.028), transparent 76%);
}

/* THE PHONE. Body lifted off the ground, a real rim on the top edge
   where a light would catch it, and a floor shadow so it stands on
   something. */
.phone{
  background:linear-gradient(168deg,#463A31 0%,#2A221C 46%,#111A26 100%);
  box-shadow:
    0 0 0 1px rgba(228,242,255,.16),
    inset 0 1px 0 rgba(235,245,255,.30),
    inset 0 -1px 0 rgba(0,0,0,.65),
    0 40px 60px -38px rgba(0,0,0,1),
    0 0 60px -22px rgba(122,204,255,.20);
}
/* the screen stays the darkest thing on the page, which is what makes
   the body around it read as a body */
.phone__screen{
  background:linear-gradient(180deg,#0C0908,#141010);
  box-shadow:
    inset 0 0 0 1px rgba(255,255,255,.07),
    inset 0 2px 12px rgba(0,0,0,.75);
}

/* the bubbles and the compose row were sitting at nearly screen value */
.bub--her{background:#3A3430}
.compose input{background:rgba(255,255,255,.085); border-color:rgba(255,255,255,.20)}
.to{border-bottom-color:rgba(255,255,255,.16)}

/* THE DEVICE. It was rendered at brightness .5, which is darker than the
   product has ever looked. Lifted at rest, and given a floor. */
.room img{filter:brightness(.78) saturate(.92)}
.room.hold img{filter:brightness(.66) saturate(.84)}
.room.on img{filter:brightness(1.02) saturate(1)}
.room__frame::after{
  content:""; position:absolute; left:16%; right:16%; bottom:6%; height:16px;
  border-radius:50%; pointer-events:none;
  background:radial-gradient(50% 100% at 50% 50%, rgba(0,0,0,.55), transparent 70%);
  filter:blur(6px);
}

/* The device was swinging .78 -> .66 -> 1.02 -> .78 across one run, which
   reads as the photograph being turned down and up rather than as a lamp
   coming on in a room. The floor is raised so the object always looks like
   itself; the light still travels, over a much shorter distance. */
.room img{filter:brightness(.9) saturate(.96)}
.room.hold img{filter:brightness(.84) saturate(.92)}
.room.on img{filter:brightness(1.06) saturate(1.02)}


/* ============================================================
   SECTION 3  -  THE OBJECT ON THE LEFT IS A PHONE      2026-08-20

   Two faults were measured on the rendered page, not guessed.

   ONE. The thing called .phone was 284 x 316. That is not a phone,
   it is a tile. Proportion is the only signal a phone has left once
   the status bar, the carrier, the battery and the notch are all
   forbidden, and that signal was being thrown away. The silhouette
   is now 9:17.6 on the glass, near 1:1.9 on the body, which is what
   a phone is. Nothing was narrowed to get there; the height was
   added, because the compose input had to GROW to 16px and needs
   every pixel of width it already had.

   TWO. It blended. The body ran #1B2432 to #111A26 against a ground
   of #04142B, so the object had no edge. You do not separate an
   object on a dark set by filling it lighter, which fogs the picture.
   You separate it the way a photographer does: a polished chamfer
   down both long edges catching the key light, a rim on the top, a
   dark seam outside the chamfer, and a floor shadow underneath. The
   glass then stays the darkest thing in the frame, which is what
   makes the metal around it read as metal.

   Everything is declared, nothing is inherited by accident: there
   are two earlier .phone blocks and two earlier .phone__screen
   blocks in this file, and a property left out of this one stays
   live from those.
   ============================================================ */

/* ---------- the chassis ---------- */
.phone{
  position:relative;
  align-self:start;
  /* 46px is the gutter pair at 320px plus the 4px the side rails
     stand proud of the body. The old fixed 284px was 4px wider than
     the wrap at 320 and only looked fine because body{overflow-x:hidden}
     was quietly eating it.
     264 rather than 286: see THE SILHOUETTE at the foot of this file.
     The width comes down with the height so the object gets smaller
     as an object, not just shorter. */
  width:min(264px, calc(100vw - 46px));
  padding:16px 12px 13px;
  border-radius:46px;
  background:
    /* the key light lands on the top rim and dies at the base */
    linear-gradient(180deg,
      rgba(235,245,255,.17) 0%, rgba(235,245,255,.03) 9%,
      transparent 34%, transparent 78%, rgba(0,0,0,.42) 100%),
    /* the machined rail: two bright chamfers, a dark waist between */
    linear-gradient(90deg,
      #8FA6BE 0%, #54687E 1.4%, #2C3A4C 4.5%, #1E2A3A 12%,
      #1A2634 34%, #182432 52%, #1C2836 78%, #263445 90%,
      #455870 96.5%, #7A93AC 99%, #A8BFD4 100%);
  box-shadow:
    /* the seam. A dark ring outside a light chamfer is the whole
       trick: without it the chamfer dissolves into the ground. */
    0 0 0 1px rgba(14,9,7,.95),
    0 0 0 2px rgba(255,226,190,.07),
    inset 0 1.5px 0 rgba(238,247,255,.44),
    inset 0 -1.5px 0 rgba(0,0,0,.8),
    inset 1.5px 0 0 rgba(233,244,255,.13),
    inset -1.5px 0 0 rgba(233,244,255,.09),
    /* contact, then ambient, then the warm bounce off the section */
    0 4px 8px rgba(0,0,0,.5),
    0 26px 40px -24px rgba(0,0,0,.92),
    0 64px 92px -56px #000,
    0 0 120px -36px rgba(122,204,255,.28);
}

/* the side rails. Generic hardware, placed where the silhouette is
   straight rather than on the corner sweep. */
.phone::before,
.phone::after{
  content:""; position:absolute; width:3px; pointer-events:none;
  background:linear-gradient(90deg,#A6BDD2,#5C7089 46%,#242F3E 100%);
  box-shadow:inset 0 1px 0 rgba(240,248,255,.55), inset 0 -1px 0 rgba(0,0,0,.6);
}
.phone::before{ left:-2px;  top:21%; height:78px; border-radius:3px 0 0 3px; }
.phone::after{
  right:-2px; top:28%; height:46px; border-radius:0 3px 3px 0;
  background:linear-gradient(270deg,#A6BDD2,#5C7089 46%,#242F3E 100%);
}

/* the earpiece slit, cut into the top bezel */
.phone__ear{
  position:absolute; top:6px; left:50%; transform:translateX(-50%);
  width:44px; height:4px; border-radius:3px; pointer-events:none;
  background:linear-gradient(180deg,#080605,#151010);
  box-shadow:inset 0 1px 1.5px rgba(0,0,0,.95), 0 1px 0 rgba(235,245,255,.16);
}

/* ---------- the glass ---------- */
.phone__screen{
  position:relative; overflow:hidden;
  height:auto; aspect-ratio:9 / 17.6;
  padding:15px 14px 14px;
  /* concentric with the body: 46 outer radius minus the 12 side bezel */
  border-radius:34px;
  background:
    radial-gradient(120% 46% at 50% 104%, rgba(122,204,255,.11), transparent 64%),
    radial-gradient(110% 34% at 50% -4%, rgba(255,228,196,.05), transparent 72%),
    linear-gradient(180deg,#0B0807 0%, #110D0B 54%, #090706 100%);
  box-shadow:
    inset 0 0 0 1px rgba(235,245,255,.05),
    inset 0 0 30px rgba(0,0,0,.85),
    0 0 0 1px rgba(0,0,0,.92);
}
/* the reflection. It sits ABOVE the pixels, because that is where
   glass is. Kept low and measured through: the contact line under it
   still reads far above AA. */
.phone__screen::before{
  content:""; position:absolute; inset:0; z-index:3;
  pointer-events:none; border-radius:inherit;
  background:linear-gradient(147deg,
    rgba(255,255,255,.085) 0%, rgba(255,255,255,.028) 13%,
    rgba(255,255,255,0) 33%, rgba(255,255,255,0) 86%,
    rgba(233,244,255,.028) 100%);
}

/* ---------- what is on the glass ---------- */
/* The contact line stays. It is the only place on the page that says
   the device has its own number, and PAGE_REQUIREMENTS names it. */
.phone .to{
  display:flex; align-items:baseline; gap:8px; flex-wrap:wrap;
  font-size:13px; font-weight:600; letter-spacing:-.004em;
  color:var(--on-night);
  padding:1px 2px 11px; margin:0 0 2px;
  border-bottom:1px solid rgba(214,238,255,.14);
}
.phone .to span{font-size:12px; font-weight:400; color:var(--on-night-2); letter-spacing:.005em}

.phone .thread{gap:8px; padding-top:10px}

.phone .bub{
  max-width:80%; padding:9px 13px; border-radius:18px;
  font-size:14px; line-height:1.4;
}
/* Outgoing green, deepened. The old #2A8150 put the timestamp line at
   3.30:1 against its own fill, which is a fail; this fill carries the
   body text and the meta line above 4.5:1. Still green, because the
   device carries a real carrier number and that is what an outgoing
   text looks like. */
.phone .bub--you{
  background:linear-gradient(180deg,#20794A,#186338);
  border-bottom-right-radius:6px;
  box-shadow:0 1px 2px rgba(0,0,0,.45), inset 0 1px 0 rgba(255,255,255,.13);
}
.phone .bub--her{
  background:linear-gradient(180deg,#3C3632,#332E2A);
  border-bottom-left-radius:6px;
  box-shadow:0 1px 2px rgba(0,0,0,.45), inset 0 1px 0 rgba(255,255,255,.06);
}
.phone .bub small{font-size:11px; opacity:1; margin-top:4px; letter-spacing:.02em}
.phone .bub--you small{color:rgba(255,255,255,.9)}
.phone .bub--her small{color:var(--on-night-2)}

/* the composer. 16px is not a taste decision: below it, tapping the
   field zooms the whole page on iOS. */
.phone .compose{gap:8px; margin-top:12px}
.phone .compose input{
  font-size:16px; padding:11px 15px; min-height:46px; border-radius:999px;
  background:rgba(238,247,255,.07);
  border:1px solid rgba(233,244,255,.18);
  box-shadow:inset 0 1px 2px rgba(0,0,0,.5);
}
.phone .compose input::placeholder{color:#A2968C}
.phone .compose .btn{
  font-size:15.5px; padding:0 17px; min-height:46px; min-width:46px;
  border-radius:999px;
}

/* ============================================================
   SECTION 3  -  THE LAMP, NOT THE PHOTOGRAPH           2026-08-20

   The device was being turned down and up: .room img carried
   filter:brightness() on all three states AND a 1.5s transition that
   survived from the first .room block, so every run crossfaded the
   whole picture darker, then brighter, then back. It re-ran on scroll
   and on every send. Narrowing the swing was tried this morning and
   the swing is still what you see, because the swing is the fault.

   A lamp coming on does not change the body of the object. It changes
   the light. So the photograph is now frozen at one exposure and the
   BAND does all the work: unlit, it is held down by a soft dim over
   the fabric; lit, the dim lifts and the two strips come up. Same
   class toggles, same beats, app.js untouched.
   ============================================================ */

.room img,
.room.hold img,
.room.on img{
  filter:brightness(.96) saturate(.98);
  transition:none;
}

/* the dim that manufactures "off". Every product shot we own ships
   with the band already lit, so off has to be built. Sized to the
   fabric on THIS cut-out: the image is 270x205 centred in a 300px
   frame, so its top edge is 50% - 102.5px and the band runs from
   +58px to +162px down from there. */
.room__frame::before{
  content:""; position:absolute; z-index:2; pointer-events:none;
  left:12%; right:12%;
  top:calc(50% - 102.5px + 58px); height:104px;
  /* CEILING ON THE DIM, 2026-08-20. This peaked at .8 black and the
     wash sits exactly across the woven band, which is where the
     wordmark is: unlit, the name was unreadable and the object read
     as damaged rather than switched off. A lamp that is off does not
     erase the thing it is attached to. Peak cut to .40, shoulders to
     .34. The lit state still reads as a clear change because the two
     strips and the halo do that work, not the darkness before it. */
  background:linear-gradient(180deg,
    rgba(2,10,22,0) 0%, rgba(2,10,22,.34) 17%, rgba(2,10,22,.40) 50%,
    rgba(2,10,22,.34) 83%, rgba(2,10,22,0) 100%);
  filter:blur(6px);
  opacity:1; transition:opacity 1.2s cubic-bezier(.4,0,.2,1);
}
/* hold is the wait, so it is the darkest the object is ever allowed to
   be, and that is now .40 x .96, not .8 x .94 */
.room.hold .room__frame::before{opacity:.96}
.room.on   .room__frame::before{opacity:0}
/* the lit strips must paint above the dim */
.room__band{z-index:3}

/* Both columns still finish on the same line. The phone is now half
   again as tall as it was, so the cube sits at the FOOT of its column
   with the dark above it, which is the empty house the beat is about.
   The frame keeps its 300px so the band offsets above stay true. */
.room{align-self:stretch; justify-content:flex-end}
.room__frame{flex:none; height:300px}

@media (prefers-reduced-motion:reduce){
  .room__frame::before{transition:none}
}

/* ============================================================
   DEMO, TIGHTENED  -  2026-08-20
   The section did not fit a laptop screen, and the device was being
   drawn at 270px from a 313px cut-out, which on a 2x display is a
   1.7x upscale. That is the softness: it was being enlarged past the
   pixels it has. Smaller is sharper here, not weaker.
   ============================================================ */
.room__frame{width:200px; height:152px}
.room img{width:190px; height:144px}     /* native 313px, so 2x is still covered */
.room{justify-content:flex-start}
.room__frame{margin-top:6px}
.beat{margin-top:12px; min-height:2.4em; font-size:clamp(17px,1.6vw,20px)}
.demo__two{align-items:start}

/* Phone screen type, down a step. The compose field stays at 16px: any
   smaller and iOS zooms the whole page on focus, which throws the layout
   sideways mid-interaction. Everything that is not a real input comes down. */



/* ============================================================
   SECTION 3  -  THE TWO OBJECTS, BALANCED           2026-08-20

   The fault, stated once: the section's height was set by a phone
   aspect-ratio nobody chose for a reason, and neither column had
   enough in it to reach the bottom. The screen opened as 512px of
   black holding one contact line and a composer. The device sat at
   the top of a column the same height, with roughly 750px of nothing
   under it. Both symptoms, one cause.

   Three moves, and they only work together:

   1. THE THREAD IS SEEDED. No real thread is ever empty. One exchange
      from yesterday is in the markup, so the glass opens with a
      conversation already on it and the visitor's own message lands
      underneath. It also teaches the rail before they type: they
      wrote, she answered, it came back as a text.

   2. THE SCREEN IS SIZED TO ITS FULLEST STATE, not to a ratio. Seed
      plus the visitor's line plus her reply is four bubbles; four
      bubbles plus the contact line plus the composer is what 9/13.7
      holds. Beyond that the thread scrolls and the seed leaves at the
      top, which is what a phone does. The height stays FIXED so
      nothing reflows mid-sequence.

   3. THE COLUMNS CENTRE ON EACH OTHER. The device is not pinned to
      the top of a stretched column any more, and the column does not
      stretch. The cube stays at 190px because the cut-out is 313px
      native and it is already being drawn past its pixels on a 2x
      display; the way to make the two objects read as a pair is to
      bring the phone DOWN to it, not the cube up.
   ============================================================ */

/* The glass ratio used to be declared here. It is not any more: a later
   block re-declares it, so this one only ever lost, and a value that can
   only lose is a trap for the next person reading the file. The one live
   declaration is in THE SILHOUETTE at the foot. */

/* the day divider. Centred, small, dim: it is furniture, not copy. */
.thread__day{
  align-self:center; margin:2px 0 1px;
  font:600 10.5px/1 var(--ui); letter-spacing:.09em; text-transform:uppercase;
  color:var(--on-night-2); opacity:.72;
}
/* Yesterday's exchange sits back so today's reads forward. The setback
   is in the FILL ONLY. opacity on the bubble composites the text with
   it: at .66 the outgoing seed computed to about 4.0:1, under the 4.5
   floor for 14px. Darkening the fill instead moves white text further
   away from its ground, not closer. */
.phone .bub--you.thread__seed{background:linear-gradient(180deg,#1A5C39,#12472B)}
.phone .bub--her.thread__seed{background:#2B2825}

/* Both columns centre on one another. .room no longer stretches, so
   there is no column left over to be empty. */
.demo__two{align-items:center}
.room{align-self:center; justify-content:center; align-items:center}
.room__frame{margin-top:0}

/* The cube must not move while its light changes: that is the whole
   premise of the lamp block above. It was moving 28px, because the beat
   under it runs one line at rest and four when it quotes a 90 character
   message back, and the group is centred. Measured worst case across
   the full sequence: 104px at 1440 (font 20px), 88px at 960 (font 17px).
   Both are 5.2em, so the reservation is made in em and tracks the clamp
   instead of freezing one viewport's number. */
.beat{min-height:5.2em}

/* Older messages now scroll up behind the contact line, which is what a
   phone does, but a hard cut looks like a mistake. Fourteen pixels of
   fade at the top edge only. No fade at the foot: the newest message
   must never look uncertain. */
.phone .thread{
  -webkit-mask-image:linear-gradient(180deg, transparent 0, #000 14px);
          mask-image:linear-gradient(180deg, transparent 0, #000 14px);
}

/* ============================================================
   THE FOOT                                         2026-08-20

   It no longer mirrors the header. Repeating the four section links at
   the bottom of a 6000px page is redundant: the reader has just
   scrolled past every one of them. The foot carries the UTILITY layer
   instead, which is the half the header has no room for and which
   nothing on the site linked to at all until now.

   It gets the device's band as its top edge. Two hairlines of ember
   with the paper between them, which is what the object looks like,
   and it is the one place in the furniture of the page where the
   product's own light shows up.
   ============================================================ */
.foot{
  background:var(--paper-3);
  border-top:1px solid rgba(0,27,61,.10);
  padding:clamp(26px,3vw,38px) 0 clamp(20px,2.2vw,26px);
  position:relative;
}
.foot::before{
  content:""; position:absolute; left:0; right:0; top:-1px; height:3px;
  background:linear-gradient(180deg,
    var(--ember-lit) 0 1px, rgba(237,228,213,0) 1px 2px, var(--ember-lit) 2px 3px);
  opacity:.5;
}
.foot__in{
  display:flex; align-items:flex-start; gap:clamp(20px,3vw,44px);
  flex-wrap:wrap;
}
.foot__logo img{height:26px; width:auto; display:block}
.foot__line{
  margin:12px 0 0; max-width:330px;
  font-size:14.5px; line-height:1.5; color:var(--ink-2);
}

/* The utility links. They wrap rather than disappear: the header hides
   its links under 900px, so at the bottom of a long page these are the
   only route that is definitely there. 44px of target from padding. */
.foot__util{
  display:flex; flex-wrap:wrap; gap:0 26px; margin-left:auto;
  font-size:15px; font-weight:500;
}
.foot__util a{
  text-decoration:none; color:var(--ink-2);
  display:inline-block; padding-block:12px; line-height:20px; min-width:44px;
}
.foot__util a:hover{color:var(--ink)}
.foot__top{color:var(--ink-3)}

@media (max-width:620px){
  .foot__util{margin-left:0; width:100%; gap:0 22px}
}

.foot__legal{
  margin-top:clamp(16px,2vw,22px);
  padding-top:clamp(12px,1.5vw,16px);
  border-top:1px solid rgba(0,27,61,.10);
}
.foot__ship{margin:0; font-size:13px; color:var(--ink-3)}

/* ---- the legal and 404 pages ---- */
.page-narrow{padding-block:clamp(34px,5vw,64px)}
.page-narrow h1{margin:12px 0 0}
.page-404{text-align:center; padding-block:clamp(56px,9vw,120px)}
.page-404 h1{margin:12px auto 0; max-width:16ch; font-size:clamp(30px,5vw,52px);
  line-height:1.1; letter-spacing:-.02em}
.page-404 p{margin:16px auto 0; max-width:44ch; font-size:16px; line-height:1.55;
  color:var(--ink-2)}
.page-404__go{margin-top:clamp(20px,2.6vw,30px); display:flex; gap:12px;
  justify-content:center; flex-wrap:wrap}

/* TAP TARGETS ON THE TWO WORDMARKS AND THE FINE LINKS.
   Both logos measured 49x26 and "Terms" measured 38x44: wide enough,
   short enough to miss. The height comes from padding so the mark is
   not resized, and the fine links get a floor rather than a font
   change. Measured at 390 after: nothing on the page under 44 except
   the reserve checkbox, which is a native control the label already
   extends the hit area of. */
.nav__logo, .foot__logo{display:inline-flex; align-items:center; padding-block:9px}
.foot__fine a{min-width:44px; text-align:center}

/* The nav no longer hides on scroll (app.js), so it needs an edge once
   the page has moved under it. Nothing at rest, a hairline shadow after
   12px, because a translucent bar over a photograph with no edge reads
   as a rendering fault. `.nav.hid` above is kept unused. */
.nav{will-change:auto}
.nav--lifted{box-shadow:0 1px 0 rgba(0,27,61,.06), 0 8px 22px -16px rgba(4,20,43,.45)}

/* ============================================================
   THE CLOSE  -  second CTA, ahead of the foot        2026-08-20

   Night, because the hero is night and this is the other end of it:
   the page opens and closes in the same room. It also puts a real step
   between the paper section above it and the paper foot below, which
   is the same fault that made sections 5 and 6 run together.

   Mobile first. One column, the words then the card. Two columns is a
   promotion at 940, where the card and the sentence can sit side by
   side without either being squeezed.
   ============================================================ */
.close{
  background:var(--night); color:var(--on-night);
  position:relative; isolation:isolate; overflow:hidden;
  padding:clamp(38px,5vw,68px) 0 clamp(40px,5vw,72px);
}
/* the same warm source the voice section uses, mirrored to the other
   side so the two night panels are not the same picture twice */
.close::before{
  content:""; position:absolute; z-index:0; pointer-events:none;
  left:26%; right:-12%; top:-14%; bottom:-6%;
  background:radial-gradient(44% 54% at 68% 46%,
    rgba(122,204,255,.12) 0%, rgba(56,182,255,.05) 42%, transparent 70%);
}
.close > .wrap{position:relative; z-index:1}
.close__in{
  display:grid; gap:clamp(24px,3vw,44px);
  grid-template-columns:1fr; align-items:center;
}
.close__say h2{
  margin:8px 0 0; max-width:17ch; color:var(--on-night);
  font-size:clamp(27px,3.2vw,42px); line-height:1.1; letter-spacing:-.022em;
}
.close__lede{
  margin:14px 0 0; max-width:46ch;
  font-size:16px; line-height:1.55; color:var(--on-night-2);
}
@media (min-width:940px){
  .close__in{grid-template-columns:minmax(0,1.02fr) minmax(0,.98fr); gap:clamp(34px,4vw,64px)}
}

/* "Why" is 32px wide at 15px type, so it missed the 44px target the rest
   of the page holds to. A floor, not a font change. */
.foot__links a{min-width:44px; text-align:center}

/* The honeypot inherits 13.3px from the UA and every audit reports it as
   an input that will zoom iOS. It cannot: it is off screen, aria-hidden
   and tabindex -1, so nobody can tap it. 16px so the finding stops
   firing and a real one is never lost in the noise. */
.hp input{font-size:16px}

/* ---- the social marks ----
   Drawn as strokes in the ink colour, not filled glyphs in coloured
   circles. Circles are the median move and they turn three placeholder
   accounts into three loud badges; strokes sit at the weight of the
   type beside them and only warm up on hover, which is the ember the
   band above them is made of. Inline SVG, so nothing is fetched. */
.foot__social{
  list-style:none; display:flex; gap:6px; margin:16px 0 0; padding:0;
}
.foot__social a{
  display:inline-flex; align-items:center; justify-content:center;
  width:44px; height:44px; margin-left:-11px;   /* optical: align the first mark to the wordmark */
  border-radius:10px; color:var(--ink-3);
  transition:color .2s ease, background-color .2s ease;
}
.foot__social li:not(:first-child) a{margin-left:0}
.foot__social a:hover{color:var(--ember); background:rgba(56,182,255,.10)}
.foot__social a:focus-visible{
  outline:none; color:var(--ember);
  box-shadow:0 0 0 3px rgba(122,204,255,.45);
}
.foot__social svg{
  width:21px; height:21px; display:block;
  fill:none; stroke:currentColor; stroke-width:1.5;
  stroke-linecap:round; stroke-linejoin:round;
}
.foot__social svg .dot{fill:currentColor; stroke:none}
.foot__social svg .fill{fill:currentColor; stroke:none}
/* Facebook is one solid path; it has no stroke version that reads. */
.foot__social svg path:not(.fill){fill:currentColor; stroke:none}

.foot__ship b{color:var(--ink-2); font-weight:600}


/* ============================================================
   SECTION 3  -  THE PHONE, MEASURED AGAINST A REAL ONE   2026-08-20

   The object was already good. "Better" here is not taste, it is the
   list of places where the drawing and a real handset disagree, each
   one measured rather than felt. Everything below is declared, because
   three earlier .phone blocks and three earlier .phone__screen blocks
   sit above this one and anything left out stays live from those.

   THE SILHOUETTE. See THE SILHOUETTE, CORRECTED at the foot of this
   block: the ratio argued for here overshot, and the live numbers are
   the ones down there. The reasoning is kept because the correction
   only moves one term of it.

   Rendered, the body was 286 x 428, which is 1:1.50. Every iPhone ever
   shipped is between 1:2.06 and 1:2.09 (15: 147.6 x 71.6mm. 15 Pro Max:
   159.9 x 76.7. SE: 138.4 x 67.3). 1:1.50 is not a phone in any year,
   it is a small tablet, and once the notch, the carrier and the battery
   are all forbidden, proportion is the ONLY signal the object has left.
   It was the one being thrown away.

   THE BEZEL. It was 16 top, 12 sides, 13 bottom. Uneven, and too
   thick: a real bezel is about 2.2mm on a 71.6mm body, which is 0.031
   of the width, uniform on all four edges. 10px is within a thousandth
   of that at either width, uniform. The corners are then concentric by
   Apple's own rule, screen radius equals body radius minus bezel.
   Checked against the hardware, body radius is 12mm / 71.6mm = 0.168 of
   width, which at 264 is 44. Display radius is 55.3pt / 393pt = 0.141,
   which at 244 is 34.4. The two agree to within half a pixel. That is
   not a coincidence, it is the rule, and it is why the corners look
   right. Both values move with the width; the rule does not.

   CONTINUOUS CORNERS. A CSS border-radius is a circular arc. Apple has
   not used a circular arc on a corner since 2013. corner-shape lands
   the superellipse for real, and browsers without it fall back to the
   arc with nothing else disturbed.

   THE FOUR TELLS ON THE GLASS.
   1. The status text was INSIDE the bubble. It never is. It is a grey
      line UNDER the newest message on each side, and it appears once,
      not on every bubble. Fixing it also gives back most of the height
      the taller silhouette costs, which is why it is done first.
   2. Both bubbles had one squared corner. That is the web and Android
      convention. iOS rounds all four and hangs a tail off the newest
      one. Senders alternate here, so every bubble is newest in its run
      and every bubble gets a tail.
   3. The header was a left aligned name and number, which is the shape
      of an email header. A messages header is centred, and the thread
      scrolls UNDER it through a blur.
   4. There was nothing between her speaking and her words arriving. A
      real thread shows three dots.
   ============================================================ */

/* ---------- the chassis ---------- */
.phone{
  padding:10px;
  border-radius:44px;
}
/* The rails, and on the left split into the three separate buttons a real
   one has instead of a single 104px bar. Measured off a 147.6mm body: the
   small switch is about 6.5mm, the two volume keys about 9mm each, the
   gaps about 4mm. That whole run is 32.5 of 147.6, so 22% of the body,
   and inside it the five parts are 20 / 12.3 / 27.7 / 12.3 / 27.7 percent.
   Height and mask are both stated as percentages on purpose: stated in
   px they were correct on the desktop body and 18% oversized on the
   narrow one, because only the body scales at the breakpoint. The right
   rail is the side button, 11.5mm, so 7.8% of the body. */
.phone::before{
  top:23%; height:22%;
  -webkit-mask-image:linear-gradient(180deg,
    #000 0 20%, transparent 20% 32.3%, #000 32.3% 60%,
    transparent 60% 72.3%, #000 72.3% 100%);
          mask-image:linear-gradient(180deg,
    #000 0 20%, transparent 20% 32.3%, #000 32.3% 60%,
    transparent 60% 72.3%, #000 72.3% 100%);
}
.phone::after{ top:32%; height:7.8% }

/* ---------- the glass ---------- */
/* THE SILHOUETTE, CORRECTED.

   9:19.5 is the true display ratio of the current line and it was still
   the wrong number to draw, which is a distinction worth writing down
   rather than losing. A real handset is 147mm tall and you hold it in
   one hand. This drawing is 264px wide and sits beside a 190px product
   shot in a 1440px band, so it is read as a picture of a phone, not as
   a phone, and at 1:2.08 that picture read as a strip. Measured on the
   rendered page: the body went 428 to 596, a 39% jump in height, while
   the object it has to share the band with did not move at all.

   The band the numbers have to land in is measured, not felt. 9:13.7
   glass was live for a full review pass and drew no complaint about
   being squat; 9:19.5 drew one immediately. So the answer is inside
   those two endpoints and nearer the tall one. 9:17.7 puts the body at
   1:1.89, which is the closest the drawing gets to hardware proportion
   while still reading as an object rather than a strip.

   244 wide by 479.9 of glass, plus two 10px bezels, is a 264 x 499.9
   body. Against the 286 x 596 it replaces that is 16% off the height,
   8% off the width and 23% off the area, and the height comes down
   further than the width on purpose: the complaint was length.

   Width DOES move now, and the constraint that held it is checked on
   the rendered page rather than assumed. The compose field has to carry
   16px type, and at 244 of glass it measures 141.9px. The narrow
   breakpoint has been running that same field at 103.9 since the last
   pass, so 141.9 is 37 pixels of headroom over a width already in use.

   The rows own their own insets, because the header has to run the
   full width of the glass for the blur to read as a bar rather than a
   floating card, and the thread has to run underneath it. */
.phone__screen{
  /* 9/17.7 drew a 500px phone and #try at 693, still the second tallest
     section and still costing a scroll. 9/16.1 takes the glass to 437 and
     the body to 457, which is a 43px section saving without touching the
     width, so the object keeps its proportion and only loses thread it was
     clipping anyway. Real hardware is 9/19.5; this is a picture of a phone
     standing next to a 190px product shot, not a phone. */
  aspect-ratio:9 / 16.1;
  border-radius:34px;
  padding:0;
}

/* Superellipse corners, on the body and the glass together so they stay
   concentric. A superellipse reads tighter than a circular arc at the same
   radius, so both values go up, and both by the same 4px: 48 - 10 bezel =
   38, which is the rule holding. This block sits AFTER both radii are
   declared on purpose. Written above them, the glass rule below it reset
   the screen back to 34 while the body kept 48, and the corners came out
   4px off concentric. Browsers without corner-shape keep 44 / 34 above,
   which is the same rule at circular values. */
@supports (corner-shape: squircle){
  .phone{ corner-shape:squircle; border-radius:48px }
  .phone__screen{ corner-shape:squircle; border-radius:38px }
}

/* ---------- the header ---------- */
/* Out of the flow and translucent, so the thread passes beneath it. The
   name and the number stack centred, which costs nothing in height
   against the old single line and is the shape the real thing has. */
.phone .to{
  position:absolute; top:0; left:0; right:0; z-index:2;
  display:flex; flex-direction:column; align-items:center; gap:1px;
  flex-wrap:nowrap;
  padding:10px 14px 8px; margin:0;
  font-size:13px; line-height:1.24;
  font-weight:600; letter-spacing:-.012em;
  color:var(--on-night);
  background:rgba(5,18,38,.62);
  -webkit-backdrop-filter:blur(18px) saturate(1.6);
          backdrop-filter:blur(18px) saturate(1.6);
  border-bottom:1px solid rgba(214,238,255,.11);
}
/* Without the blur the bar has to carry itself on fill alone, or the
   messages passing behind it show through as ghosts. */
@supports not ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){
  .phone .to{background:rgba(22,17,14,.97)}
}
.phone .to span{
  font-size:10.5px; line-height:1.2;
  font-weight:400; letter-spacing:.005em;
  color:var(--on-night-2);
}

/* ---------- the thread ---------- */
/* Cleared past the bar, inset at the sides by 12 so the bubble tails have
   somewhere to hang, and the top fade moved down to the underside of the
   bar. Left at the glass edge it read as a rendering fault. */
/* The bar measures 49px. 66 of top padding puts a resting message clear of
   it AND clear of the fade below it, so nothing looks dimmed until it is
   genuinely travelling under the bar. isolation is explicit rather than
   inherited from the mask, which also makes one: the tails hang off a
   negative z-index pseudo and need a stacking context here whether the
   fade survives a future edit or not. */
.phone .thread{
  gap:8px;
  padding:66px 12px 0;
  isolation:isolate;
  -webkit-mask-image:linear-gradient(180deg, transparent 0 48px, #000 64px);
          mask-image:linear-gradient(180deg, transparent 0 48px, #000 64px);
}

/* The day divider. iOS writes it mixed case with the time beside it, the
   day semibold and the time regular. It never shouts it in caps, and the
   caps plus the tracking were the giveaway that a web designer set it. */
.thread__day{
  align-self:center; margin:2px 0 3px;
  font:400 11px/1.35 var(--ui); letter-spacing:0; text-transform:none;
  color:var(--on-night-2); opacity:.85;
}
.thread__day b{font-weight:600; color:var(--on-night); opacity:.9}

/* ---------- the bubbles ---------- */
/* Radius 18 on all four corners. On a 37px single line bubble that is a
   pill, which is exactly what the real one is. The corner does not square
   off, the tail hangs off it instead. */
/* border:0 is load bearing. The FIRST .bub--her block in this file, a long
   way above, gives incoming a 1px ember outline, and none of the three
   blocks between here and there ever took it off. It was still rendering:
   a warm hairline round a bubble that is now meant to be the one neutral
   thing on the glass, and the tail, which carries no border, would have
   hung off it unfenced. */
/* flex:none, because .thread is a flex column and its content is meant to
   overflow. Without it every bubble is a shrinkable flex item and the whole
   thread compresses as messages arrive instead of scrolling. */
.phone .bub{
  position:relative;
  flex:none;
  max-width:75%;
  padding:8px 13px 9px;
  border:0;
  border-radius:18px;
  font-size:14px; line-height:1.36;
  letter-spacing:-.011em;   /* SF Text tracking at this size */
}

/* Live green, and the whole decision is one number: how far down from
   the real SMS green the fill sits. The real one is #34C759. The hue and
   the saturation are held exactly (135.1 degrees, 0.739) at every value
   below, so every candidate IS the SMS green, brighter or darker.

     value  fill      white text on it
     0.78   #34C759   2.22:1   the real one
     0.66   #2CA84B   3.08:1   live, top stop
     0.62   #299E47   3.46:1   live, bottom stop
     0.52   #23863C   4.62:1   what this replaced

   4.5:1 is the WCAG floor for text under 18.66px bold, and 14px body
   type is under it, so the fill that was here cleared the floor and
   the one that is here does not. It clears 3:1, which is the floor for
   large text and for interface components. That is a judgement, not a
   rule, and it is written down so it can be reversed in one line: the
   drawing is a picture of an SMS thread and the green is the single
   thing in it that says SMS rather than app, which is the product's
   own distinction. Every candidate above is a drop-in for both stops.
   Nothing else on the glass moved, and the two readings that carry the
   rest of the thread were measured off the rendered page rather than
   assumed: incoming body text is 9.94:1 on its top stop, and the status
   line, which left the bubble in the previous pass and now sits on the
   glass itself, is 10.14:1. Only the outgoing fill is in question. */
.phone .bub--you{
  --bub:#299E47;
  background:linear-gradient(180deg,#2CA84B,var(--bub));
  border-bottom-right-radius:18px;
  box-shadow:0 1px 2px rgba(0,0,0,.42);
}
/* Incoming was a warm brown. The real one is a neutral dark grey and the
   neutrality is the point: it is the only cool thing on the glass, which
   is what lets the green read as colour. 9.9:1 with the body text. */
.phone .bub--her{
  --bub:#333335;
  background:linear-gradient(180deg,#3B3B3D,var(--bub));
  border-bottom-left-radius:18px;
  box-shadow:0 1px 2px rgba(0,0,0,.42);
}
/* Yesterday still sits back, and still in the fill only, because opacity
   composites the text with it. The setback is smaller than it was, twice
   over. The two days have to read as one thread at two moments rather
   than as two colours, and the shorter glass means every bubble on screen
   at rest is a seed: set yesterday back far and the visitor's first sight
   of the thread is the dark green that was just corrected. Two steps down
   the same value ramp, #289944 at 3.66:1 over #258F40 at 4.13:1, which is
   visibly behind today without being a different colour. */
.phone .bub--you.thread__seed{--bub:#258F40; background:linear-gradient(180deg,#289944,var(--bub))}
.phone .bub--her.thread__seed{--bub:#292A2B; background:linear-gradient(180deg,#2E2E30,var(--bub))}

/* THE TAIL. One pseudo, drawn as a path, with no eraser element behind
   it. The usual two element trick paints a second box in the colour of
   whatever is behind the bubble to bite the curl out, which means it has
   to know the background, and the background here is a gradient with an
   ember bloom in it. That is a seam waiting to show. A path has no such
   dependency and works over anything.

   The shape is measured off the corner it hangs from: 20px tall, which
   is the 18px radius plus the stroke, 6px of protrusion, and a concave
   underside that hooks back into the bubble's bottom edge. The path
   closes THROUGH the bubble's interior, which costs nothing, because
   .bub is positioned and so paints after a negative z-index child of the
   same stacking context: everything inside the bubble is covered by the
   bubble's own background. --bub is the gradient's bottom stop, the
   value at the exact height the tail hangs from, so the tail can never
   drift from the bubble it belongs to, including on the two seed pairs. */
.phone .bub::after{
  content:""; position:absolute; z-index:-1;
  bottom:0; width:24px; height:20px;
  background:var(--bub);
  pointer-events:none;
}
.phone .bub--you::after{
  right:-6px;
  -webkit-clip-path:path("M18,2 C18,11 19,16 24,20 C18,20.4 12,19 7,15.5 Z");
          clip-path:path("M18,2 C18,11 19,16 24,20 C18,20.4 12,19 7,15.5 Z");
}
.phone .bub--her::after{
  left:-6px;
  -webkit-clip-path:path("M6,2 C6,11 5,16 0,20 C6,20.4 12,19 17,15.5 Z");
          clip-path:path("M6,2 C6,11 5,16 0,20 C6,20.4 12,19 17,15.5 Z");
}

/* ---------- the status line ---------- */
/* Out of the bubble and under it, grey, small, and only under the newest
   message on each side. app.js writes a <small> into every bubble and is
   not ours to change, so the hiding is done here: any bubble that has a
   later bubble from the same sender drops its line, and drops the margin
   that was reserving room for it. */
.phone .bub small{
  position:absolute; top:100%; margin:0; padding-top:3px;
  display:block; white-space:nowrap;
  font-size:10.5px; font-weight:400; letter-spacing:0;
  color:var(--on-night-2); opacity:.9;
}
.phone .bub--you small{right:2px}
.phone .bub--her small{left:2px}
.phone .bub{margin-bottom:16px}
.phone .bub--you:has(~ .bub--you) small,
.phone .bub--her:has(~ .bub--her) small{display:none}
.phone .bub--you:has(~ .bub--you),
.phone .bub--her:has(~ .bub--her){margin-bottom:0}

/* ---------- the three dots ---------- */
/* order:1 pins it last however many bubbles app.js appends after it. It
   is shown only while .room carries "on", which is the stretch after she
   has spoken and before her words land as a text. .room is a later
   sibling of .phone, so this reaches it through the parent. */
.thread__typing{display:none}
.demo__two:has(.room.on) .thread__typing{
  display:flex; order:1; flex:none; align-self:flex-start;
  position:relative; align-items:center; justify-content:space-between;
  width:58px; height:34px; padding:0 11px; box-sizing:border-box;
  --bub:#333335;
  border-radius:18px; background:linear-gradient(180deg,#3B3B3D,var(--bub));
  box-shadow:0 1px 2px rgba(0,0,0,.42);
}
/* Same tail as an incoming bubble, because that is what it is. */
.demo__two:has(.room.on) .thread__typing::after{
  content:""; position:absolute; z-index:-1;
  bottom:0; left:-6px; width:24px; height:20px;
  background:var(--bub); pointer-events:none;
  -webkit-clip-path:path("M6,2 C6,11 5,16 0,20 C6,20.4 12,19 17,15.5 Z");
          clip-path:path("M6,2 C6,11 5,16 0,20 C6,20.4 12,19 17,15.5 Z");
}
.thread__typing i{
  width:7px; height:7px; border-radius:50%; flex:none;
  background:var(--on-night-2);
  animation:typing 1.25s ease-in-out infinite both;
}
.thread__typing i:nth-child(2){animation-delay:.18s}
.thread__typing i:nth-child(3){animation-delay:.36s}
@keyframes typing{
  0%, 60%, 100%{opacity:.34; transform:translateY(0)}
  30%          {opacity:1;   transform:translateY(-3px)}
}
@media (prefers-reduced-motion: reduce){
  .thread__typing i{animation:none; opacity:.62}
}

/* ---------- the composer ---------- */
/* Still a pill, and deliberately: the real field is a rounded rect only
   once it has grown past one line. At rest, at this height, its radius
   equals half its height and it IS a pill. 16px stays, because below it
   iOS zooms the whole page on focus. */
.phone .compose{
  gap:8px; margin-top:0;
  padding:7px 11px 11px;
}
.phone .compose input{
  font-size:16px; padding:11px 16px; min-height:44px; border-radius:999px;
  letter-spacing:-.008em;
}
.phone .compose .btn{min-height:44px; min-width:44px}

/* ---------- the arrival ---------- */
/* A message does not fade in on iOS, it springs: it comes up from under
   the composer, overshoots its resting size by a hair and settles, and it
   grows out of the corner the tail hangs from rather than out of its own
   middle. The overshoot is the whole character and it is in the curve,
   not in a keyframe. The reduced motion guard has to be restated here,
   because the two that already exist in this file sit above this rule and
   a later declaration of the same property would walk straight past them. */
.phone .bub{
  animation:bub-in .34s cubic-bezier(.22,1.2,.36,1) both;
}
.phone .bub--you{transform-origin:100% 100%}
.phone .bub--her{transform-origin:0 100%}
@keyframes bub-in{
  from{opacity:0; transform:translateY(10px) scale(.94)}
  to  {opacity:1; transform:none}
}
@media (prefers-reduced-motion: reduce){
  .phone .bub{animation:none}
}

/* ---------- the phone below the two column breakpoint ---------- */
/* Measured on the rendered page at 390x844, not reasoned about. Under
   900px the section stacks phone, then device, then the resting line, and
   the resting line is where the sequence actually pays off: it is the one
   that says the message played out loud in your voice. Keeping the phone
   narrower here is what holds that line on the fold, and the reason is
   the stack: every pixel of phone pushes the line down by one.

   226 stands. On the corrected 9:17.7 glass and the same 10px bezel that
   is a 206 x 405 screen in a 226 x 425 body, 1:1.88, which is the same
   proportion the desktop drawing now has, so the two viewports show the
   same object at two sizes rather than two different phones. It is also
   41px shorter than the 466 it replaces, which the resting line gets
   back. The compose field still clears its 16px floor, measured at
   103.9px wide, which is the width it has been carrying since the last
   pass and is unchanged by any of this. */
@media (max-width:899px){
  .phone{ width:min(226px, calc(100vw - 46px)) }
}

/* ============================================================
   THE TRAVELLING MESSAGE                            2026-08-21

   The client asked for an arrow showing the message going from the
   phone to the device and back, at logical points.

   It is drawn in the gutter BETWEEN the two objects rather than on
   either of them, so neither has to know the other exists. app.js adds
   .flow--out the instant the message leaves the phone and .flow--in the
   instant the reply lands, then removes it 1.4s later. Two beats in the
   whole sequence, both real.

   Not a clip-art arrow. A lit track with a head that runs along it, in
   the same ember the device's own band is made of, because that is the
   one visual language this page already owns. The chevron gives it
   direction; the track gives it a path.
   ============================================================ */
.demo__two{position:relative}

.flow{
  position:absolute; z-index:4; pointer-events:none;
  left:50%; top:38%; width:clamp(54px,7vw,96px);
  transform:translateX(-50%);
  height:2px; border-radius:2px;
  background:linear-gradient(90deg,
    rgba(122,204,255,0), rgba(122,204,255,.28), rgba(122,204,255,0));
  opacity:0; transition:opacity .25s ease;
}
.flow__head{
  position:absolute; top:50%; left:0; width:16px; height:2px;
  margin-top:-1px; border-radius:2px;
  background:linear-gradient(90deg, rgba(122,204,255,0), var(--ember-lit));
  box-shadow:0 0 10px 2px rgba(122,204,255,.55);
}
/* the chevron on the leading edge */
.flow__head::after{
  content:""; position:absolute; right:-1px; top:50%;
  width:6px; height:6px; margin-top:-3px;
  border-top:2px solid var(--ember-lit);
  border-right:2px solid var(--ember-lit);
  transform:rotate(45deg);
}

.demo__two.flow--out .flow,
.demo__two.flow--in  .flow{opacity:1}

.demo__two.flow--out .flow__head{animation:flowOut .82s cubic-bezier(.35,0,.25,1) both}
.demo__two.flow--in  .flow{transform:translateX(-50%) scaleX(-1)}
.demo__two.flow--in  .flow__head{animation:flowOut .82s cubic-bezier(.35,0,.25,1) both}

@keyframes flowOut{
  0%   {left:0;    opacity:0}
  18%  {opacity:1}
  82%  {opacity:1}
  100% {left:calc(100% - 16px); opacity:0}
}

/* Stacked layout: the objects sit one above the other, so the path is
   vertical. Same track, rotated, and the head still runs head-first. */
@media (max-width:899px){
  .flow{
    top:auto; bottom:auto;
    left:50%; top:50%;
    width:2px; height:clamp(40px,9vw,64px);
    transform:translate(-50%,-50%);
    background:linear-gradient(180deg,
      rgba(122,204,255,0), rgba(122,204,255,.28), rgba(122,204,255,0));
  }
  .flow__head{
    width:2px; height:16px; left:50%; top:0; margin-top:0; margin-left:-1px;
    background:linear-gradient(180deg, rgba(122,204,255,0), var(--ember-lit));
  }
  .flow__head::after{
    right:auto; left:50%; top:auto; bottom:-1px;
    margin-top:0; margin-left:-3px;
    border-top:none; border-right:2px solid var(--ember-lit);
    border-bottom:2px solid var(--ember-lit);
  }
  .demo__two.flow--in .flow{transform:translate(-50%,-50%) scaleY(-1)}
  .demo__two.flow--out .flow__head,
  .demo__two.flow--in  .flow__head{animation:flowDown .82s cubic-bezier(.35,0,.25,1) both}
}
@keyframes flowDown{
  0%   {top:0; opacity:0}
  18%  {opacity:1}
  82%  {opacity:1}
  100% {top:calc(100% - 16px); opacity:0}
}

/* A reader who asked for less motion gets the track lit at both beats
   and nothing running along it. */
@media (prefers-reduced-motion:reduce){
  .demo__two.flow--out .flow__head,
  .demo__two.flow--in  .flow__head{animation:none; left:50%; top:50%}
}


/* ============================================================
   THE HERO WORDMARK                                 2026-08-21

   "Whispr" on its own line above the eyebrow. Three tiers, each doing
   one job: the name, the promise, the payoff. Before this the hero
   opened on what the product does, which is a description; opening on
   the name makes the rest read as a statement about it.

   Set in the display face at roughly the eyebrow's weight in the
   hierarchy but larger, so it reads as a signature rather than a
   heading competing with the h1 underneath it.
   ============================================================ */
.hero__brand{
  /* The eyebrow was 2px under the wordmark and 10px above the headline, so
     it read as a subtitle attached to "Whispr" rather than its own tier.
     An introducing line belongs nearer the thing it introduces, so the gap
     above it is now the larger of the two. */
  margin:0 0 clamp(12px,1.3vw,17px);
  font-family:var(--display); font-weight:400;
  font-size:clamp(21px,2.4vw,27px);
  line-height:1.1; letter-spacing:-.018em;
  color:var(--on-night);
  text-shadow:none;
}


/* ============================================================
   THE DEMO STOPS MOVING                            2026-08-21

   The two columns were `auto auto`, so each track sized itself to
   whatever it happened to hold. The right one holds the beat line, and
   app.js rewrites that line five times a run with the VISITOR'S OWN
   MESSAGE, up to 90 characters. Every rewrite resized the track and
   `justify-content:center` re-centred the pair, so the phone slid left
   and snapped back on each beat. On a long message it moved twice as far,
   which is why it read as a glitch rather than a transition.

   THE ARROW IS NOT INVOLVED. .flow is position:absolute; it cannot push
   anything. It only looked responsible because it fires on the same two
   beats that were moving the phone.

   Fixed tracks, sized to the two objects standing in them: 264px for the
   phone (see THE SILHOUETTE) and 300px for the room. Nothing either
   column contains can change the layout now.
   ============================================================ */
@media (min-width:900px){
  .demo__two{grid-template-columns:264px 300px}
}

/* The line is capped to its own object again. The bottom-alignment pass
   set max-width:none, and that is what let a long message widen the track
   in the first place. */
.beat{max-width:300px}


/* ============================================================
   THE HERO WORDMARK IS THE REAL MARK           2026-08-21

   It was the word "Whispr" set in Fraunces. The client asked for the
   logo, and the reason it is better is the heart: the blue bubble over
   the "i" holds a white heart, and that is the only piece of the brand
   the page owns that says what the product is FOR rather than what it is
   called. Type cannot carry that.

   logo-reversed.png is the cream-on-dark cut. Sized by HEIGHT, not width,
   because the file carries the bubble's headroom above the letters: match
   the widths and the letters come out too small.

   The text-shadow that kept the word legible over the photograph becomes
   a drop-shadow filter, which follows the glyph edges instead of a box.
   ============================================================ */
.hero__brand{text-shadow:none}
.hero__brand img{
  display:block; width:auto; height:clamp(40px,4.6vw,58px);
  filter:drop-shadow(0 2px 14px rgba(3,12,26,.72)) drop-shadow(0 1px 3px rgba(3,12,26,.55));
}
@media (max-width:759px){
  .hero__brand img{height:clamp(34px,10vw,44px)}
}


/* ============================================================
   OWNER'S LIST                                    2026-08-21

   1. THE HERO MARK IS THE ICON ALONE. It carried the full
      cream wordmark, which put the word "Whispr" twice in the
      top of the page - once in the header logo and again three
      lines below it. The icon keeps the one piece of brand that
      says what the product is FOR (the heart in the bubble) and
      drops the repetition. Square, so it is sized by height and
      centred like a mark rather than a line of type.
   ============================================================ */
.hero__brand img{
  width:auto; height:clamp(46px,5vw,64px);
  filter:drop-shadow(0 2px 14px rgba(3,12,26,.72)) drop-shadow(0 1px 3px rgba(3,12,26,.55));
}
@media (max-width:759px){
  .hero__brand img{height:clamp(40px,11vw,52px)}
}

/* 2. THE TRAVELLING ARROW ON A PHONE. It was position:absolute
      against .demo__two with left:50%; top:50%, so on a stacked
      layout it landed in the middle of the whole two-object
      column - which on a tall phone is the middle of the screen,
      nowhere near either object. It now sits in the GAP between
      the phone and the room, which is what it is supposed to be
      drawn in: pinned to the bottom edge of the phone column
      rather than to the centre of the pair. */
@media (max-width:899px){
  /* THE TRAVELLING ARROW IS DESKTOP-ONLY. On the stacked mobile layout
     the phone height changes with the thread, so any absolute anchor
     lands inside the phone screen over the messages and reads as a
     glitch. The demo still carries the beat without it. */
  .flow{display:none !important}
}

/* 3. THE PICTURES KEEP THEIR SUBJECT ON A PHONE. The stacked
      layout cropped every step image to 16:9 to stop the section
      running past three screens, and 16:9 out of a square frame
      throws away the top and bottom - which is exactly where the
      device sits in two of the four. 3:2 is still short enough to
      keep the section to one scroll and keeps the object in
      frame. The owner could see the device on a desktop and not
      on a phone; this is why. */
@media (max-width:619px){
  #how .fstep__pic img{aspect-ratio:3 / 2}
}


/* ============================================================
   WHITE PIVOT  (2026-09-01, Mike): no dark navy sections, no dark
   overlays on images. Former "night" sections become light with dark
   text; the phone SCREEN in the demo stays dark (it is a device
   screen). Light-grey grounds give definition so the page reads clean,
   not washed out. Appended as overrides so originals stay reversible.
   ============================================================ */
.film__frame, .film__frame--empty{
  background:var(--paper-2) !important;
  box-shadow:var(--shadow-1), inset 0 0 0 1px rgba(0,27,61,.06) !important;
}
.film__frame--empty::after{opacity:.5}

.demo{background:var(--paper-2) !important; color:var(--ink) !important}
.demo .head h2, .demo h2{color:var(--ink) !important}
.demo .eyebrow, .demo .eyebrow.on-dark{color:var(--ember) !important}
.demo .head .lede, .demo .lede{color:var(--ink-2) !important}

.close, .close-cta{background:var(--paper) !important; color:var(--ink) !important}
.close .eyebrow.on-dark, .close-cta .eyebrow.on-dark{color:var(--ember) !important}
.close__lede, .close-cta .lede{color:var(--ink-2) !important}
.close h2 .lit, .close-cta h2 .lit{color:var(--ember) !important; font-style:normal}
.close::before, .close-cta::before{opacity:.5}

.foot{background:var(--paper-3) !important; color:var(--ink-2) !important}
.foot a, .foot__links a{color:var(--ink-2) !important}
.foot__links a:hover{color:var(--ink) !important}
.foot__legal{color:var(--ink-3) !important}

/* companion — was a dark "cozy room"; bring the ground to light. Inner glow
   elements may need a follow-up tweak once we see it. */
.companion{background:var(--paper-2) !important; color:var(--ink) !important}
.companion .eyebrow, .companion .eyebrow.on-dark{color:var(--ember) !important}
.companion h2, .companion h3{color:var(--ink) !important}

/* =====================================================================
   iOS MESSAGES SKIN — the "See it happen" phone (2026-09-01).
   Makes the demo read unmistakably as a modern iPhone: tall silhouette,
   Dynamic Island, 9:41 status bar, Messages header, home indicator, and
   iOS bubble + compose shapes. Bubbles stay GREEN — a Whispr text lands
   as a real SMS, never iMessage blue (the styles.css:1216 rule and the
   number in the header both hold). UI chrome (back, FaceTime) is iOS blue.
   One appended block so reverting to backup 21311cd stays a clean diff.
   app.js contract untouched — it only appends .bub into #thread. Selectors
   are .phone-scoped to win the cascade over the earlier breakpoint tiers.
   ===================================================================== */
.demo__two{align-items:center}   /* don't stretch the room photo to the taller phone */

.phone{
  width:270px; padding:11px; border-radius:46px; position:relative;
  background:linear-gradient(150deg,#4a4e54 0%,#1a1c1f 26%,#0e0f11 52%,#23262a 82%,#131417 100%);
  box-shadow:
    0 0 0 1px rgba(255,255,255,.10),
    inset 0 1px 0 rgba(255,255,255,.22),
    0 46px 74px -36px rgba(0,0,0,.92);
}
/* titanium side buttons: left ringer+volume, right side button */
.phone::after{content:""; position:absolute; left:-2px; top:118px; width:3px; height:92px; border-radius:2px;
  background:linear-gradient(180deg,#2c2f33 0 20%,#111316 20% 30%,#2c2f33 30% 58%,#111316 58% 68%,#2c2f33 68% 100%)}
.phone::before{content:""; position:absolute; right:-2px; top:150px; width:3px; height:56px; border-radius:2px;
  background:linear-gradient(180deg,#2c2f33,#14161a)}

.phone__screen{
  height:498px; padding:0; border-radius:35px; overflow:hidden;
  background:#000; position:relative; display:flex; flex-direction:column;
  box-shadow:inset 0 0 0 1px rgba(255,255,255,.06);
}

/* status bar */
.ios-status{display:flex; align-items:center; justify-content:space-between; padding:10px 22px 2px; color:#fff; z-index:3}
.ios-status__time{font-size:13.5px; font-weight:650; letter-spacing:.02em; padding-left:6px}
.ios-status__right{display:flex; align-items:center; gap:5px}
.ios-ic{fill:#fff; display:block}
.ios-batt{width:23px; height:11.5px; border-radius:3.5px; border:1px solid rgba(255,255,255,.5); padding:1.5px; position:relative; box-sizing:border-box}
.ios-batt::after{content:""; position:absolute; right:-3px; top:3.5px; width:1.7px; height:4px; border-radius:0 2px 2px 0; background:rgba(255,255,255,.5)}
.ios-batt__fill{display:block; width:76%; height:100%; border-radius:1.5px; background:#fff}

/* Dynamic Island */
.ios-island{position:absolute; top:10px; left:50%; transform:translateX(-50%); width:84px; height:25px; border-radius:14px; background:#000; z-index:6}

/* Messages nav header */
.phone__screen .to{
  display:grid; grid-template-columns:1fr auto 1fr; align-items:center; gap:0;
  padding:2px 10px 8px; margin:0; border:0; border-bottom:1px solid rgba(255,255,255,.10);
  background:rgba(22,22,24,.66); backdrop-filter:blur(10px); z-index:2;
}
.to__back{justify-self:start; width:10px; height:10px; margin-left:8px;
  border-left:2.2px solid #0A84FF; border-top:2.2px solid #0A84FF; transform:rotate(-45deg)}
.to__center{display:flex; flex-direction:column; align-items:center; gap:2px; min-width:0}
.to__avatar{width:34px; height:34px; border-radius:50%; display:flex; align-items:center; justify-content:center;
  font-size:15px; font-weight:600; color:#fff; background:linear-gradient(160deg,#9a9aa0,#5c5c62)}
.phone__screen .to__name{display:flex; align-items:center; gap:2px; font-size:11px; font-weight:600; color:#fff; letter-spacing:.01em; line-height:1.1; white-space:nowrap}
.to__chev{color:rgba(255,255,255,.45); font-size:13px; font-weight:700; line-height:1}
.phone__screen .to__num{font-size:9.5px; font-weight:400; color:rgba(235,235,245,.5); letter-spacing:.02em; line-height:1}
.to__ft{justify-self:end; margin-right:8px; display:flex}

/* thread + iOS bubbles (green SMS / grey received) */
.phone__screen .thread{flex:1; gap:1px; padding:8px 12px 16px; overflow:hidden; justify-content:flex-end; background:#000}
.phone .thread__day{align-self:center; text-align:center; font-size:10px; font-weight:600; color:rgba(235,235,245,.5); margin:2px 0 9px; letter-spacing:.01em}
.phone .thread__day b{font-weight:700; color:rgba(235,235,245,.62)}
.phone .thread__day span{font-weight:500}
.phone .bub{max-width:74%; padding:7px 12px; border-radius:17px; font-size:13.5px; line-height:1.32; margin-bottom:15px; position:relative; overflow:visible}
.phone .bub--you{align-self:flex-end; background:#34C759; color:#fff; border-radius:17px}
.phone .bub--her{align-self:flex-start; background:#26252A; color:#fff; border-radius:17px}
.phone .bub small{position:absolute; bottom:-13px; margin:0; font-size:10px; font-weight:500; color:rgba(235,235,245,.6); letter-spacing:.01em; white-space:nowrap; opacity:1}
.phone .bub--you small{right:3px}
.phone .bub--her small{left:5px}
.phone .thread__typing{align-self:flex-start; background:#26252A; border-radius:16px; padding:10px 13px; margin-bottom:8px}
.phone .thread__typing i{background:rgba(235,235,245,.6)}

/* iOS compose bar: + button, rounded field "Text Message", circular green send */
.phone__screen .compose{display:flex; align-items:center; gap:8px; margin:0; padding:8px 12px 20px; background:rgba(22,22,24,.5)}
.compose__plus{flex:none; width:28px; height:28px; border-radius:50%; background:rgba(255,255,255,.12); color:#fff; font-size:20px; font-weight:300; line-height:26px; text-align:center}
.phone__screen .compose input{flex:1; min-width:0; padding:7px 14px; font-size:13px; color:#fff; background:transparent; border:1px solid rgba(255,255,255,.3); border-radius:999px}
.phone__screen .compose input::placeholder{color:rgba(235,235,245,.45)}
.phone__screen #demoSend{flex:none; width:30px; height:30px; min-height:0; padding:0; border-radius:50%; background:#34C759; color:#fff; display:flex; align-items:center; justify-content:center; box-shadow:none; border:0}
.phone__screen #demoSend svg{display:block}

/* home indicator */
.ios-home{position:absolute; bottom:7px; left:50%; transform:translateX(-50%); width:112px; height:5px; border-radius:3px; background:rgba(255,255,255,.9); z-index:6}
