:root {
  --ink: #101226;
  --muted: #5f6178;
  --paper: #fbf7ef;
  --paper-2: #f5efff;
  --surface: #fffdf8;
  --line: rgba(16, 18, 38, 0.12);
  --teal: #2f7f78;
  --teal-deep: #1f5e59;
  --purple: #7c3aed;
  --amber: #f59e0b;
  --coral: #ff6b35;
  --dark: #172326;
  --header-h: 68px;
  --rail-h: 34px;
  --safe-top: calc(var(--header-h) + var(--rail-h));
  color-scheme: light;
  font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}

* {
  box-sizing: border-box;
}

html {
  scroll-behavior: smooth;
  scroll-padding-top: 0;
  scrollbar-width: none;
}

html::-webkit-scrollbar,
body::-webkit-scrollbar {
  display: none;
}

body {
  margin: 0;
  overflow-x: hidden;
  background:
    radial-gradient(circle at 78% 12%, rgba(181, 213, 245, 0.48), transparent 34%),
    radial-gradient(circle at 16% 42%, rgba(245, 158, 11, 0.1), transparent 28%),
    linear-gradient(135deg, #fffaf2 0%, #f7f1ff 48%, #eef8ff 100%);
  color: var(--ink);
}

html[data-snap="on"] {
  scroll-snap-type: y mandatory;
}

html[data-snap="off"] {
  scroll-snap-type: none;
}

[data-snap-target],
.add-stage,
.yourstory-stage,
.faq {
  scroll-snap-align: start;
  scroll-snap-stop: always;
}

.explore-intro,
.explore-showcase,
.add-stage,
.yourstory-stage,
.faq {
  scroll-margin-top: var(--landing-lift, calc(var(--safe-top) + 10px));
}

.footer {
  scroll-snap-align: none;
}

a {
  color: inherit;
}

img {
  display: block;
  max-width: 100%;
}

/* The page's side gutter, as a token, because two places need to agree with it
   rather than restate it: the header island and the Explore carousel, which
   bleeds out to the screen edges and has to put the gutter back inside each
   slide. It shrinks on phones - see the max-width: 760px block. */
:root {
  --wrap-gutter: 18px;
}

.wrap {
  width: min(1180px, calc(100% - var(--wrap-gutter) * 2));
  margin: 0 auto;
}

/* ==========================================================================
   GLOBAL HEADER - floating navigation island + stage status bar

   Everything is derived from one runtime value, --nav-p (0 -> 1), written by
   initDynamicHeader() in gsap-site.js from scroll DISTANCE from the top.
   Tune the look by editing the custom properties below; nothing downstream
   hardcodes a scroll-state colour.
   ========================================================================== */

/* ==========================================================================
   ANNOUNCEMENT BAR

   Fixed, above the island, and gone on the first scroll. --announce-h is
   written by the JS from the bar's measured height and zeroed when it lifts,
   so everything that has to make room for it - the island's resting offset
   and the hero's top padding - reads one number and nothing hard-codes it.
   ========================================================================== */

:root {
  --announce-h: 0px;
}

.announce {
  position: fixed;
  inset: 0 0 auto;
  z-index: 49;              /* under the island, which is 50 */
  background: linear-gradient(180deg, #efe7fb, #ece6f8);
  color: #3d3550;
  font-size: 0.86rem;
  transition: transform 380ms cubic-bezier(0.4, 0, 0.2, 1), opacity 280ms ease;
}

/* Plain inline text, NOT a flex row: every element in here would have become
   a flex item with a gap either side of it, which put a space in front of the
   em-dash and another in front of the full stop - "ever . See what's
   included". A sentence has to be laid out as a sentence. */
.announce-inner {
  width: min(1180px, calc(100% - 36px));
  margin: 0 auto;
  padding-block: 9px;
  text-align: center;
}

.announce-dot {
  display: inline-block;
  width: 7px;
  height: 7px;
  margin-right: 7px;
  border-radius: 50%;
  background: var(--coral);
  vertical-align: middle;
}

.announce-link {
  border: 0;
  margin-left: 6px;
  padding: 0;
  background: none;
  color: #6b4fa0;
  font: inherit;
  font-weight: 800;
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}

.announce-link:hover {
  color: #553d85;
}

/* Lifted out, not display:none - the height is still measurable if the reader
   scrolls back to the top and it comes down again. */
html[data-announce="off"] .announce {
  transform: translateY(-100%);
  opacity: 0;
  pointer-events: none;
}

html[data-announce="on"] .site-header {
  --nav-top: calc(15px + var(--announce-h));
}

@media (prefers-reduced-motion: reduce) {
  .announce { transition: none; }
}

.announce-short { display: none; }

@media (max-width: 620px) {
  /* A short sentence, not the long one with a clause trimmed off it: the bar
     MUST be one line on a phone. At two lines it cost 50px off the hero and
     put "See how Nestory works" back under the fold - the exact thing the
     mobile hero work above was for. */
  .announce-long { display: none; }
  .announce-short { display: inline; }

  .announce-inner {
    padding-block: 7px;
    font-size: 0.79rem;
  }

  .announce-link { margin-left: 4px; }
}

/* Short phones do not get the bar at all. Even at one line it costs ~30px,
   and that is exactly the margin between "the wall mock and the call to
   action are both on the first screen" and "the call to action is under the
   fold". The bar is promotional; the CTA is the page's job. It returns the
   moment there is room for both - 390x844 and up. */
@media (max-width: 760px) and (max-height: 820px) {
  .announce {
    display: none;
  }
}

/* 360px: the short sentence is still two pixels too wide for one line, and
   two lines here costs 18px the hero cannot spare. */
@media (max-width: 400px) {
  .announce-inner {
    width: calc(100% - 20px);
    font-size: 0.73rem;
  }

  .announce-dot { margin-right: 5px; }
}

/* The header island moved to assets/chrome.css so every page wears the same
   one - see that file. The narrative rail below stays here: it is the
   homepage's chapter navigation, not site chrome. */

.stage-rail {
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 0;
  gap: clamp(8px, 1.1vw, 15px);
  padding-left: var(--nav-gap);
  border-left: 1px solid rgba(var(--nav-ink-rgb), calc(0.045 + 0.045 * var(--nav-p)));
  overflow-x: auto;
  /* STATED. A box with `overflow-x: auto` and no overflow-y computes
     overflow-y to `auto` as well, so this one row of pills was a vertical
     scroll container - and setStage() calls scrollIntoView({block:"nearest"})
     on a pill in it, which is all it takes to shift the rail inside its own
     box. The rail is one line; up and down belong to the page.
     The :has() rule below still lifts both axes while the menu is open. */
  overflow-y: hidden;
  scrollbar-width: none;
}

.stage-rail::-webkit-scrollbar {
  display: none;
}

.stage-rail :is(button, a) {
  /* `:is(button, a)` rather than a class, because the rail holds both: the
     five stage links, and the Family submenu trigger, which stays a <button> - it opens a popover, it does not go anywhere. Specificity is identical
     to the bare `button` these rules used to name, so nothing in the cascade
     below moves.

     display and text-decoration are the two things a <button> had by default
     and an <a> does not. The coarse-pointer block further down already sets
     the same display for its 44px targets. */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  text-decoration: none;
  flex: 0 0 auto;
  border: 0;
  border-radius: 999px;
  padding: 6px 8px;
  background: transparent;
  color: rgba(var(--nav-ink-rgb), 0.62);
  cursor: pointer;
  font: inherit;
  font-size: 0.72rem;
  font-weight: 860;
  white-space: nowrap;
  transition: color 180ms ease, background-color 180ms ease, transform 180ms ease;
}

.stage-rail :is(button, a):hover {
  color: rgba(var(--nav-ink-rgb), 0.82);
  background: rgba(var(--nav-ink-rgb), 0.045);
  transform: translateY(-1px);
}

.stage-rail :is(button, a).is-active {
  color: rgba(var(--nav-ink-rgb), 0.95);
  background: rgba(var(--nav-accent-rgb), calc(0.07 + 0.06 * var(--nav-p)));
}

/* --- brand -------------------------------------------------------------- */

.site-header .brand {
  color: rgba(var(--nav-ink-rgb), calc(0.88 + 0.12 * var(--nav-p)));
  font-size: 0.97rem;
  gap: 9px;
}

.site-header .brand-mark {
  width: 34px;
  height: 34px;
  overflow: visible;
  border-radius: 0;
}

.site-header .brand-mark img {
  object-fit: contain;
}

/* --- header actions ----------------------------------------------------- */

.site-header .actions {
  gap: 8px;
}

.site-header .button {
  min-height: calc(36px + 3px * var(--nav-p));
  padding-inline: calc(14px + 3px * var(--nav-p));
  font-size: 0.85rem;
  transition: background-color 200ms ease, border-color 200ms ease, color 200ms ease,
    box-shadow 200ms ease, transform 200ms ease;
}

.site-header .button.secondary {
  border-color: rgba(var(--nav-ink-rgb), calc(0.06 + 0.07 * var(--nav-p)));
  background: rgba(var(--nav-glass-rgb), calc(0.16 + 0.3 * var(--nav-p)));
  color: rgba(var(--nav-ink-rgb), 0.8);
}

.site-header .button.secondary:hover {
  background: rgba(var(--nav-glass-rgb), calc(0.4 + 0.4 * var(--nav-p)));
  color: rgba(var(--nav-ink-rgb), 0.96);
  transform: translateY(-1px);
}

/* The CTA keeps the brand purple at every scroll position and through every
   chapter - it must never read as "part of the fill". It only grounds itself
   a little more once the shell settles. */
.site-header .button.primary {
  background: var(--purple);
  color: #fff;
  box-shadow: 0 calc(4px + 5px * var(--nav-p)) calc(12px + 12px * var(--nav-p))
    rgba(124, 58, 237, calc(0.18 + 0.1 * var(--nav-p)));
}

.site-header .button.primary:hover {
  transform: translateY(-1px);
}

.stage-rail :is(button, a):focus-visible,
.site-header .brand:focus-visible,
.site-header .button:focus-visible {
  outline: 2px solid rgba(var(--nav-accent-rgb), 0.72);
  outline-offset: 3px;
}

/* --- dark chapters ------------------------------------------------------ */
/* --nav-glass-rgb / --nav-ink-rgb are tweened in JS so the swap never flashes;
   only the alphas change here. */

.site-header[data-nav-theme="dark"] {
  --nav-glass-alpha: 0.4;
  --nav-glass-rest: 0.22;
}

/* The glass alpha is inside a background-color, so it does not transition on
   its own - the crossing has to be eased or it lands as a snap. */
.nav-shell::before {
  transition: background-color 280ms ease, opacity 280ms ease;
}

.site-header[data-nav-theme="dark"] .stage-rail :is(button, a):hover,
.site-header[data-nav-theme="dark"] .button.secondary {
  background: rgba(var(--nav-glass-rgb), calc(0.22 + 0.24 * var(--nav-p)));
}

@media (prefers-reduced-motion: reduce) {
  .site-header,
  .stage-rail :is(button, a),
  .site-header .button {
    transition: none;
  }

  .stage-rail :is(button, a):hover,
  .site-header .button:hover,
  .site-header .button.secondary:hover {
    transform: none;
  }
}

/* The menu-jump veil. The page's own ground rather than a flat colour, so a
   jump reads as the page dimming and resolving, not a sheet passing over it.
   Under the header (z 50) on purpose: the nav never blinks, so the reader can
   see the rail update to the chapter they picked while the fade runs. */
.stage-veil {
  position: fixed;
  inset: 0;
  z-index: 45;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  background:
    radial-gradient(circle at 78% 12%, rgba(181, 213, 245, 0.48), transparent 34%),
    radial-gradient(circle at 16% 42%, rgba(245, 158, 11, 0.1), transparent 28%),
    linear-gradient(135deg, #fffaf2 0%, #f7f1ff 48%, #eef8ff 100%);
}

.snap-panel,
.pin-stage {
  min-height: 100vh;
  min-height: 100svh;
  display: grid;
  align-items: center;
  padding: var(--safe-top) 0 48px;
  overflow: hidden;
}

.split {
  display: grid;
  grid-template-columns: minmax(0, 0.92fr) minmax(420px, 1.08fr);
  align-items: center;
  gap: clamp(34px, 6vw, 76px);
}

.split.reverse {
  grid-template-columns: minmax(420px, 1.08fr) minmax(0, 0.92fr);
}

h1,
h2,
h3,
p {
  margin: 0;
}

h1 {
  max-width: 1000px;
  font-size: clamp(3.4rem, 6.6vw, 6.25rem);
  line-height: 0.96;
  letter-spacing: 0;
}

h2 {
  max-width: 980px;
  font-size: clamp(2.8rem, 5.4vw, 5.8rem);
  line-height: 0.98;
  letter-spacing: 0;
}

h3 {
  font-size: clamp(1.2rem, 2.4vw, 2rem);
  line-height: 1.12;
}

.lead {
  max-width: 720px;
  margin-top: 20px;
  color: var(--muted);
  font-size: clamp(1rem, 1.7vw, 1.28rem);
  line-height: 1.58;
}

.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 14px;
  color: var(--teal-deep);
  font-size: 0.78rem;
  font-weight: 930;
  letter-spacing: 0;
  text-transform: uppercase;
}

.eyebrow::before {
  content: "";
  width: 9px;
  height: 9px;
  border-radius: 999px;
  background: var(--coral);
}

.hero-actions {
  margin-top: 28px;
}

.trust-line {
  margin-top: 18px;
  color: #345552;
  font-weight: 820;
}

/* The field is a custom property so a seal can be stacked ON it without the
   whole list being restated - see the phone block that closes this file, where
   the hero's neighbour changes and its bottom edge has to be flattened. */
.intro-copy {
  --intro-field:
    radial-gradient(circle at 82% 16%, rgba(181, 213, 245, 0.42), transparent 34%),
    radial-gradient(circle at 14% 30%, rgba(232, 213, 245, 0.5), transparent 34%),
    radial-gradient(circle at 74% 78%, rgba(255, 214, 196, 0.34), transparent 34%),
    linear-gradient(180deg, #fffaf2 0%, #faf6fb 100%);
  background:
    /* Seals the last few per cent to the #faf6fb the gradient above already
       ends on. The peach radial at 74% 78% is what stops it getting there:
       measured, the hero's own bottom edge arrives at rgb(250,241,242) on the
       right-hand side, against the cool rgb(233,239,243) the next section
       opens on - a 17/255 step, and 34 on a 720px-tall window where the mock's
       shadow reaches the edge as well. */
    linear-gradient(180deg, rgba(250, 246, 251, 0) 88%, #faf6fb 98%),
    var(--intro-field);
}

/* ------------------------------------------------------------
   PRODUCT HERO - Section 1 (replaced the FAMILY letter morph)
   ------------------------------------------------------------ */

.product-hero {
  position: relative;
  --hero-h1: clamp(2.9rem, 5vw, 4.9rem);
  /* GROWN WHEN THE PHONE STOPPED STANDING NEXT TO IT.

     The satellite used to be a sibling at `right: 0` of a tableau widened by
     `--phone-outset: 157px`, and the tablet was sized inside what was left - 40vw, capped at 560, capped again by the tableau's height. Now the phone
     slides out from behind the bezel (see `.hero-phone-bay`), so the whole box
     is the tablet's and the WIDTH cap comes up with it: 40vw/560 to 48vw/700.

     ONLY the width cap. `--tableau-h` is untouched, and that is the whole
     discipline of this change: the outset was width the phone was holding, so
     width is what there is to give back. Raising the height budget as well
     looked like the same edit and is not - the tablet's height is what the
     section fits into, and 50svh/540 put 1024x600 twelve pixels past the fold
     and 1920x720 seven past it, both caught by tools/hero-fit-probe.py.

     So it grows exactly where the WIDTH was binding and nowhere else: 560 to
     621 at 1440x900, 560 to 700 at 1920x1080 and 2560x1440. At 1366x768 it
     does not move at all, because there the cap that binds is
     `--tableau-h * 1.5` and always was. The wall's --fit goes 0.417 to 0.545
     at 1920 - a 16px Today row at 8.7 real pixels in the wide shot instead of
     6.7, and 14 under the camera's push-in instead of 10.7. */
  --tableau-h: clamp(340px, 46vh, 500px);
  --tableau-h: clamp(340px, 46svh, 500px);
  --main-w: min(clamp(380px, 48vw, 700px), calc(var(--tableau-h) * 1.5));
}

/* Prevent FOUC: keep hero elements hidden until the entrance/reveal runs. */
.product-hero [data-hero-intro],
.product-hero [data-hero-lead-a],
.product-hero [data-hero-step] {
  opacity: 0;
}

.product-hero-wrap {
  display: grid;
  /* 0.78/1.22 until the tableau had to hold a tablet AND a phone side by side
     rather than a tablet with things tucked behind it. The lead is capped at
     460px and was never using the left column's full width, so the 30-odd
     pixels come from slack rather than from the copy. */
  grid-template-columns: minmax(0, 0.72fr) minmax(0, 1.28fr);
  grid-template-areas:
    "copy copy"
    "details tableau";
  align-items: center;
  align-content: center;
  gap: clamp(14px, 2.8vh, 34px) clamp(24px, 4vw, 60px);
  gap: clamp(14px, 2.8svh, 34px) clamp(24px, 4vw, 60px);
  min-height: calc(100vh - var(--safe-top) - 48px);
  min-height: calc(100svh - var(--safe-top) - 48px);
}

.product-hero-copy {
  grid-area: copy;
  max-width: 1180px;
}

.product-hero-copy h1 {
  max-width: 1180px;
  font-size: var(--hero-h1);
  line-height: 1.03;
  letter-spacing: 0;
}

.product-hero-details {
  grid-area: details;
}

.product-hero-details .lead {
  max-width: 460px;
  margin-top: 0;
}

.hero-pill-list {
  position: relative;
  display: flex;
  flex-wrap: wrap;
  gap: 7px;
  max-height: 104px;
  margin-bottom: 12px;
  overflow: hidden;
  -webkit-mask-image: linear-gradient(180deg, #000 0, #000 46%, rgba(0, 0, 0, 0.7) 68%, transparent 100%);
  mask-image: linear-gradient(180deg, #000 0, #000 46%, rgba(0, 0, 0, 0.7) 68%, transparent 100%);
}

.hero-pill-list span {
  display: inline-flex;
  align-items: center;
  min-height: 28px;
  padding: 5px 10px;
  border: 1px solid rgba(47, 127, 120, 0.16);
  border-radius: 999px;
  background:
    linear-gradient(135deg, rgba(255, 255, 255, 0.72), rgba(255, 255, 255, 0.42)),
    rgba(124, 58, 237, 0.055);
  color: rgba(16, 18, 38, 0.74);
  font-size: clamp(0.72rem, 0.95vw, 0.86rem);
  font-weight: 850;
  line-height: 1;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.74),
    0 8px 18px rgba(23, 35, 38, 0.055);
}

.hero-pill-list span:nth-child(3n + 1) {
  background:
    linear-gradient(135deg, rgba(255, 255, 255, 0.76), rgba(255, 255, 255, 0.44)),
    rgba(47, 127, 120, 0.075);
}

.hero-pill-list span:nth-child(3n + 2) {
  background:
    linear-gradient(135deg, rgba(255, 255, 255, 0.76), rgba(255, 255, 255, 0.44)),
    rgba(124, 58, 237, 0.07);
}

.hero-pill-list span:nth-child(3n) {
  background:
    linear-gradient(135deg, rgba(255, 255, 255, 0.76), rgba(255, 255, 255, 0.44)),
    rgba(255, 107, 53, 0.07);
}

.product-hero-details .hero-actions {
  margin-top: 26px;
}


.product-hero-details .hero-actions .button.primary {
  position: relative;
  overflow: hidden;
}

.product-hero-details .hero-actions .button.primary::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(105deg, transparent 25%, rgba(255, 255, 255, 0.34) 50%, transparent 75%);
  transform: translateX(-150%);
  animation: hero-cta-shimmer 3.4s ease-in-out infinite;
}

@keyframes hero-cta-shimmer {
  0% {
    transform: translateX(-150%);
  }

  52% {
    transform: translateX(150%);
  }

  100% {
    transform: translateX(150%);
  }
}

@media (prefers-reduced-motion: reduce) {
  .product-hero-details .hero-actions .button.primary::after {
    animation: none;
    opacity: 0;
  }
}

/* --- where a clipped shadow is allowed to finish ------------------------
   A background seal cannot fix these two joins, and it took a while to see
   why: a child's box-shadow paints ABOVE its parent's background, so the flat
   stop at the bottom of .intro-copy and .williams-video-panel goes under the mock's
   shadow and the scene's, not over them. What the seam shows is the shadow
   itself, cut off by the section's own overflow:hidden.

   Measured, below the wall mock: 116px of room on a 900px-tall window, 68px
   on a 768 and 37px on a 720, against a shadow that reaches 136px. Below the
   Williams scene: 52-83px against 120px, and 146px once the card deepens.
   Neither has anywhere near the room, and the cut lands while the shadow is
   still 11-35/255 off the field.

   Giving them the room would mean shrinking the mock and the scene, which is
   the wrong thing to spend: they are the picture. So the last few dozen pixels
   of shadow fade into the base instead of being chopped at it. What that costs
   is the outer end of a shadow that is already down to a few units - at
   1440x900 the scene's tail runs from rgb(229,226,230) to rgb(238,234,240)
   across the band, and it now runs to the base instead. What it buys is a join
   that cannot be found at any window size.

   z-index 5: above the mock (3) and the scene (auto), below the scroll cue
   (20) and the chapter's scroll hint (6), both of which live in this band. */
.product-hero::after,
.williams-video-panel::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 5;
  height: 38px;
  pointer-events: none;
  background: linear-gradient(180deg, rgba(250, 246, 251, 0) 0%, #faf6fb 100%);
}

.williams-video-panel::after {
  height: 46px;
}

.hero-scroll-next {
  position: absolute;
  left: 50%;
  bottom: 22px;
  z-index: 20;
  transform: translateX(-50%);
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-height: 42px;
  padding: 0 6px;
  color: var(--teal-deep);
  font-size: 0.98rem;
  font-weight: 820;
  text-decoration: none;
  white-space: nowrap;
}

.hero-scroll-next span {
  display: inline-block;
  transition: transform 240ms ease;
}

.hero-scroll-next:hover span,
.hero-scroll-next:focus-visible span {
  transform: translateY(3px);
}

/* FLUSH LEFT, NOT CENTRED.

   With four satellites around it the tablet was the middle of a composition and
   centring was the only sensible thing to do. There is one satellite now, it is
   always on the right, and the tablet centred left it 62px of room at 1440 to
   put a 185px phone in - which is why the phone spent this section's whole life
   lying across the wall's Today widget.

   Flush left hands that 62px, and the gutter the grid was giving the tablet on
   its right, to the phone. Measured at five widths: it is the difference
   between the phone clearing the Today rows and covering the owner column on
   every one of them. */
/* THE TABLEAU IS AS WIDE AS THE COMPOSITION, NOT AS WIDE AS THE COLUMN.

   The phone is absolutely positioned at `right: 0` of this box, so whatever
   this box's right edge is, that is where the phone goes. It used to be the
   grid column's full width, and the column is sized by the VIEWPORT while the
   tablet is sized by `--main-w`, which is capped by `calc(var(--tableau-h) *
   1.5)` - a height. So on any viewport where the height is the binding
   constraint, the tablet shrank, the column did not, and the difference came
   out as a gap between the two devices.

   Measured, with the old rule: 6px of daylight at 1920x1080, 40px at 1366x768
   and 92px at 1210x834 - an iPad Pro in landscape, which is not an exotic
   size. The composition is meant to read as a phone standing in front of a
   wall tablet, and at 92px it reads as two unrelated objects.

   157px is not a taste: it is the outset the layout already resolves to at
   1440, 1512, 1600, 1920 and 2560, which are the widths this was tuned at. So
   this rule changes nothing at those sizes and drags every narrower one onto
   the same geometry. min(), so a column narrower than the composition still
   wins and nothing overflows. */
.hero-tableau {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: flex-start;
  grid-area: tableau;
  width: min(100%, var(--main-w));
  height: var(--tableau-h);
  /* Centred in its column rather than flush left. Flush left was right while
     the phone stood in the gap on the right; with nothing there it is just an
     off-balance composition with a hole in it. */
  margin-inline: auto;
}

.hero-tableau::before {
  content: "";
  position: absolute;
  inset: -4% 6%;
  z-index: 0;
  border-radius: 999px;
  background:
    radial-gradient(circle at 20% 30%, rgba(124, 58, 237, 0.1), transparent 42%),
    radial-gradient(circle at 82% 26%, rgba(181, 213, 245, 0.34), transparent 44%),
    radial-gradient(circle at 60% 86%, rgba(255, 107, 53, 0.1), transparent 42%);
  filter: blur(30px);
  pointer-events: none;
}

.hero-product {
  position: absolute;
  will-change: transform, opacity;
}

/* ---- Main Family Wall ---- */

.hero-product-main {
  position: relative;
  z-index: 3;
  width: var(--main-w);
  flex: 0 0 auto;
}

.hero-main-frame {
  border-radius: 26px;
  background: var(--dark);
  padding: 12px;
  box-shadow:
    0 46px 90px rgba(23, 35, 38, 0.22),
    0 10px 28px rgba(16, 18, 38, 0.1);
}

/* 1284/883 is the tab's authored size, and both layers in here are that shape:
   the screenshot is a capture of it and the live tab IS it. 16/10 was the old
   mock's, which was free to be any ratio it liked because it was a drawing.

   No gradient behind them any more, and no flex column. The mock was built out
   of rows in here and needed both; what sits here now is one image with another
   image-shaped thing on top of it, and a background neither can ever show
   through is a paint nobody asked for. .ct-stage brings its own. */
.hero-main-screen {
  position: relative;
  aspect-ratio: 1284 / 883;
  overflow: hidden;
  border-radius: 17px;
}

.hero-main-screenshot {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* ---- the cycling layer -------------------------------------------------
   A second screenshot stacked on the first, used to crossfade between the
   four tabs - see the cycle block at the foot of hero-demo.js.

   It is the OVERLAY that moves and the base image that is authoritative: the
   base is never faded, it is re-pointed once the overlay covering it is
   opaque. That keeps `.hero-main-screenshot` at opacity 1 at all times, which
   is a thing tools/hero-probe.js asserts on a phone and which should stay
   true - an opacity on the base is how the live upgrade signals it has taken
   over, and two mechanisms writing the same property would eventually
   disagree.

   THE DURATION IS MIRRORED IN hero-demo.js (FADE_MS). Change both. */
.hero-main-cycle {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity 0.5s ease;
  pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
  /* Belt and braces: the script does not start at all under reduced motion,
     but if it ever did, this makes the crossfade a cut rather than a slide. */
  .hero-main-cycle {
    transition: none;
  }
}

/* ---- the live layer ----------------------------------------------------
   The screenshot is not replaced, it is covered - see the head of
   hero-demo.js. Two layers in the one screen, crossfaded.

   .ct-stage states a width, an aspect-ratio and a max-width, all of which are
   for a stage that sizes a column of its own. Here the column is already
   sized: the box is .hero-main-screen, which carries the tab's ratio and the
   bezel's inner radius. So the stage is pinned to it and its own sizing is
   handed back.

   Specificity 0,2,0, on purpose and for the reason explore-demo.css gives at
   length: wall-demo.css and walltab-demo.css are fetched at runtime and so land
   AFTER this file in the cascade, where a rule of equal weight would win.
   `.hero-product-main .hero-live` beats `.ct-stage` whatever order they arrive
   in; `.hero-live` alone would not. */
.hero-product-main .hero-live {
  position: absolute;
  inset: 0;
  width: auto;
  height: auto;
  max-width: none;
  aspect-ratio: auto;
  border-radius: inherit;
  /* .ct-stage clips twice - overflow for layout, clip-path because a promoted
     child is clipped by the compositor and antialiased against whatever the
     stage paints, which leaves a dotted stair-step along each arc. Same
     reasoning here; only the radius changes, to .hero-main-screen's. */
  clip-path: inset(0 round 17px);
  opacity: 0;
  transition: opacity 420ms ease;
  pointer-events: none;
}

.hero-product-main.is-live .hero-live { opacity: 1; }
.hero-product-main.is-live .hero-main-screenshot { opacity: 0; transition: opacity 420ms ease; }

/* THE ROW THE PHONE SENDS.

   It arrives the way a row arrives on the wall - it is not there, and then it
   is. It used to be the only thing on this tablet that moved, and it moved at
   0.42 scale where its text could not be read, so motion was the whole of what
   carried: SOMETHING landed, in the right widget, at the right position in the
   day, and the phone's "Added" card beside it was the legible half.

   The camera lands it in close now (see initProductHero in gsap-site.js), so
   the row is read as well as seen and the count going 4 to 5 is visible. The
   entrance is unchanged - it is the right one at either scale.

   Authored in tab pixels, because everything inside .ct-cam is laid out at the
   tab's authored size and only then scaled by the camera's transform. */
.hero-product-main .hero-live .wt-ev.is-landing {
  animation: hero-row-land 520ms cubic-bezier(.22, 1, .36, 1) both;
}

@keyframes hero-row-land {
  from { opacity: 0; transform: translateY(-10px); }
  to   { opacity: 1; transform: translateY(0); }
}

@media (prefers-reduced-motion: reduce) {
  /* hero-demo.js does not upgrade the tablet at all with motion reduced, so
     the screenshot is what is on screen and there is nothing here to still.
     This block exists for the case the JS check and this one ever disagree. */
  .hero-product-main .hero-live,
  .hero-product-main .hero-live * { animation: none !important; }
  .hero-product-main.is-live .hero-main-screenshot { opacity: 1; }
  .hero-product-main .hero-live { opacity: 0; }
}


/* ---- A family's Sunday - the live tablet's story --------------------------
   On a desktop-shaped window the live tablet plays "A family's Sunday"
   (heroday-demo.*, mounted by hero-demo.js, markup from
   tools/heroday-build.py): 7:30 am to 8 pm, one job off Mum's plate an hour,
   with her worry and its answer as a caption UNDER the tablet and the hour in
   a pill on its top edge.

   THE CAPTION'S ROOM IS RESERVED HERE, FROM THE FIRST PAINT. The live wall
   arrives a few hundred milliseconds after the screenshot, and the tablet is
   the LCP element - growing a caption under it then would shove it upward in
   front of the reader. So on these windows the tableau is a column (tablet,
   then caption) and the tablet's width is worked out from what is left of the
   height budget once the caption has had its share: `--hd-caps-h` is three
   lines, the most any caption takes at the narrowest tablet this applies to.

   If the live wall never arrives, hero-demo.js takes `hd-day` off and the
   tablet gets its full size back - an empty band under it would say that
   something broke.

   THE MEDIA QUERY IS MIRRORED IN hero-demo.js (`dayFits`). Change both. The
   min-height leaves out windows under 681px tall (a 1366x768 laptop with
   its browser bars, at the bottom): there the tablet is already at its
   340px floor, and with a caption under it the wall would be a 330px screen
   whose close-ups are 7px text. Those keep the screenshots and the link. */
.hero-tableau .hd-caps,
.hero-tableau .hd-clock { display: none; }

@media (prefers-reduced-motion: no-preference) and (min-width: 1025px) and (min-height: 681px),
       (prefers-reduced-motion: no-preference) and (min-width: 761px) and (min-height: 681px) and (orientation: landscape) {
  /* The tablet is the frame (screen + 2 x 12px bezel) and the screen is the
     tab's own 1284:883, so its WIDTH follows from the height left over:
     (height - 24) * 1284/883 + 24. Worked out exactly rather than with the
     base rule's `* 1.5`, which was close enough when the tablet had the whole
     height to itself and is 16px too tall once a caption is under it.

     The budget grows into the section's spare room on taller windows - up to
     60px from 900px tall, nothing at 720 - which the fold has at every desktop
     size tools/hero-fit-probe.js sweeps. */
  .hero-tableau.hd-day {
    --hd-above: 216px;
    --hd-caps-h: 74px;
    --hd-caps-gap: 12px;
    --tableau-h: clamp(340px, calc(100vh - var(--hd-above) - 2.06 * var(--hero-h1)), 700px);
    --tableau-h: clamp(340px, calc(100svh - var(--hd-above) - 2.06 * var(--hero-h1)), 700px);
    --main-w: min(clamp(380px, 48vw, 700px),
                  calc((var(--tableau-h) - var(--hd-caps-h) - var(--hd-caps-gap) - 24px) * 1284 / 883 + 24px));
    flex-direction: column;
    justify-content: center;
    gap: var(--hd-caps-gap);
  }

  /* The caption: the tablet's width, under it, in its reserved band. Hidden
     until the live wall is up, so the screenshot never sits over a caption
     about something it is not showing. 0,3,0 and up throughout, because
     heroday-demo.css arrives at runtime, after this file. */
  .hero-tableau.hd-day .hd-caps {
    display: grid;
    position: relative;
    z-index: 3;
    width: 100%;
    min-height: var(--hd-caps-h);
    margin: 0;
    padding: 0;
    align-content: start;
    visibility: hidden;
    opacity: 0;
    transition: opacity 600ms ease, visibility 600ms;
  }
  .hero-tableau.hd-day .hd-caps .hd-q { font-size: 0.92rem; line-height: 1.4; }
  .hero-tableau.hd-day .hd-caps .hd-a { font-size: 1rem; line-height: 1.35; }
  .hero-tableau.hd-day.is-day-live .hd-caps { visibility: visible; opacity: 1; }

  /* The hour, ON the bezel's top edge rather than above it: the headline's
     last line runs across this column, so a pill clear of the bezel would sit
     in its descenders. Half on the dark frame it reads as part of the device
     and costs no height. */
  .hero-tableau.hd-day .hd-clock {
    display: flex;
    position: absolute;
    top: -13px;
    left: 50%;
    z-index: 6;
    margin: 0;
    transform: translateX(-50%);
    visibility: hidden;
    opacity: 0;
    transition: opacity 600ms ease, visibility 600ms;
  }
  .hero-tableau.hd-day.is-day-live .hd-clock { visibility: visible; opacity: 1; }

  /* NO "See how Nestory works" WHILE THE SUNDAY PLAYS. It is pinned to the
     section's foot, out of flow, and on a laptop-height window the caption
     band was landing on it (1366x650: caption 596..638, link 594..636). The
     header's own nav goes to every section below, so the tablet has the
     height instead. If the live wall fails, hero-demo.js takes `hd-day` off
     and the link comes back with the screenshots. */
  .product-hero:has(.hero-tableau.hd-day) .hero-scroll-next { display: none; }
}

/* SHORT WINDOWS: ONE SLOT. Under 800px tall there is no height to spare for a
   three-line band without taking a fifth off the tablet, so the caption is one
   two-line slot - her worry, and then the answer IN ITS PLACE once the wall
   has done the job - rather than the worry with the answer under it. */
@media (prefers-reduced-motion: no-preference) and (min-width: 761px) and (min-height: 681px) and (max-height: 799px) and (orientation: landscape) {
  .hero-tableau.hd-day { --hd-above: 228px; --hd-caps-h: 42px; --hd-caps-gap: 8px; }
  .hero-tableau.hd-day .hd-caps .ct-sub { grid-template-areas: "line"; }
  .hero-tableau.hd-day .hd-caps .hd-q,
  .hero-tableau.hd-day .hd-caps .hd-a { grid-area: line; align-self: start; }
  .hero-tableau.hd-day .hd-caps .hd-q { font-size: 0.9rem; transition: opacity 500ms ease; }
  .hero-tableau.hd-day .hd-caps .hd-a { font-size: 0.95rem; }
  .hero-tableau.hd-day .hd-caps.is-answered .hd-q { opacity: 0; }
}

@media (prefers-reduced-motion: no-preference) and (min-width: 761px) and (max-width: 1180px) and (min-height: 681px) and (orientation: landscape) {
  /* The narrower composition's own width cap, as the 1180 block gives
     `.product-hero` - with the caption's share taken out of the height. */
  .hero-tableau.hd-day {
    --main-w: min(clamp(360px, 52vw, 560px),
                  calc((var(--tableau-h) - var(--hd-caps-h) - var(--hd-caps-gap) - 24px) * 1284 / 883 + 24px));
  }
}

/* The phones' layer is the SCREEN's box; sat-phones.css rounds it to a .ct-frame
   screen's 15px, and this one is 17. */
.hero-product-main .sat-phones { border-radius: 17px; }

/* The guard explore-demo.css keeps, for the same collision: once the reader
   nears Explore, calendar-demo.css arrives and its `.ct-card` (an event dialog)
   lands on the Chores meters in this wall too. Width AND transform - the
   dialog's rest state is `scale(0.96)`, which chores-demo.css never declares
   and so never overrides; measured, it took every meter from 404 to 388. */
.hero-product-main .hero-live .ct-card { width: auto; transform: none; }

/* ---- Phone / Capture - out from behind the bezel ---- */

/* THE BAY. A straight port of `.sat-phones` in add-demo.css; its note is the
   long version of this one.

   The SCREEN's box, not the frame's: `inset: 12px` is .hero-main-frame's own
   padding and 17px is .hero-main-screen's radius, so a parked phone is hidden
   at the edge of the glass - where something behind a tablet would appear
   from - rather than at the edge of the case. Separate from the frame rather
   than being it, because clipping the frame would clip its drop shadow and
   flatten the picture.

   Over the live tab as well as the screenshot: .hero-product-main is the
   containing block and z 4 clears the .hero-live layer's stack. */
.hero-phone-bay {
  display: none;
  position: absolute;
  inset: 12px;
  z-index: 4;
  overflow: hidden;
  border-radius: 17px;
  pointer-events: none;
  /* So the phone can be sized against the GLASS and not only against the
     column - see the 52cqh cap below. */
  container-type: size;
}

/* ANCHORED TO THE GLASS, NOT OFFSET FROM THE MIDDLE - again add-demo.css's
   reasoning: a rest position measured from the centre cannot be right at more
   than one breakpoint, and this box is a percentage of a glass that runs from
   ~490px wide on a 1024 tablet to 676px at 1920.

   The park is xPercent, written by gsap.js from `PHONE_PARKED` in
   initProductHero, not from here: the slide is on the idle loop's clock
   because the camera it has to agree with is, and two clocks would drift. What
   CSS owns is where the phone rests when it is out, and the rest state is what
   an unstyled/no-JS render gets.

   Percentage width so it tracks the wall it stands on. The floor is what
   "Approve" and "Edit" need side by side - measured at 1024 landscape, where
   at 136 the Edit pill was cut off by the phone's own screen. */
.hero-product-capture {
  right: 10px;
  bottom: 5%;
  z-index: 1;
  /* 52cqh is the bay's own height, and the second half of the min() is what
     stops a short wall cropping the phone standing on it. `.hero-phone-screen`
     is 9/17 inside 8px of bezel, so the whole handset is about 1.79x its own
     width; 52% of the glass leaves it clear of both edges with the 5% bottom
     offset taken out first. Without it a 1280x720 laptop drew a 250px phone in
     a 240px bay and the clip took the top off it. */
  width: min(clamp(140px, 23%, 182px), 52cqh);
  transform: rotate(2.8deg);
}

/* THE ONE SWITCH, AND THE TWO NUMBERS IT TURNS ON.

   The phone used to be dropped in two places - the phone/portrait block and
   the short-landscape one - for the same reason in both: it was a second
   object competing for width the tablet needed. It is not any more. It stands
   ON the tablet and costs the composition nothing, so the only question left
   is whether it has room of its own, which is a width and a height.

   846 is the number the sweep already knows: tools/hero-fit-probe.js opens
   846x414 (a handset turned sideways) as one of the four shapes that broke,
   and below it the bay's 23% is under the 140px floor - a phone drawn wider
   than the wall's own screen can hold, over a wall that at that size is the
   only thing worth showing anyway.

   AND A HEIGHT, which is new. Beside the tablet the phone could hang past the
   bottom of the tableau and nothing clipped it; on the glass it cannot. 700 is
   where the two blocks that already concede this section is height-starved
   begin - `(max-height: 700px) and (orientation: landscape)` and the
   short-landscape one under it - and below it 52cqh takes the phone under the
   136px its own Approve/Edit row needs. A phone too small to read its own
   buttons is not worth the pixels it is standing on.

   display:none on the BAY, not on the phone inside it: the phone carries
   data-hero-step, so the intro timeline still tweens it, and a hidden element
   costs one tween that paints nothing. */
@media (max-width: 846px), (max-height: 700px) {
  .hero-phone-bay { display: none; }
}

.hero-phone {
  position: relative;
  border-radius: 26px;
  background: var(--dark);
  padding: 8px;
  box-shadow: 0 34px 68px rgba(16, 18, 38, 0.28);
}

.hero-phone-notch {
  position: absolute;
  top: 11px;
  left: 50%;
  z-index: 2;
  width: 34%;
  height: 11px;
  border-radius: 999px;
  background: var(--dark);
  transform: translateX(-50%);
}

.hero-phone-screen {
  position: relative;
  aspect-ratio: 9 / 17;
  overflow: hidden;
  border-radius: 19px;
  background:
    radial-gradient(circle at 82% 6%, rgba(181, 213, 245, 0.5), transparent 40%),
    #fdfaf3;
}

.hero-phone-state {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 8px;
  padding: 22px 16px 18px;
  opacity: 0;
  transform: translateY(10px);
  pointer-events: none;
}

.hero-phone-state.is-active {
  opacity: 1;
  transform: none;
}

.hero-phone-app {
  color: var(--teal-deep);
  font-size: 0.62rem;
  font-weight: 900;
  text-transform: uppercase;
  letter-spacing: 0;
}

.hero-phone-state strong {
  font-size: 0.92rem;
  font-weight: 850;
  line-height: 1.15;
}

.hero-phone-state p {
  color: var(--muted);
  font-size: 0.72rem;
  line-height: 1.4;
}

.hero-share {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  align-self: flex-start;
  margin-top: 4px;
  border-radius: 999px;
  background: var(--purple);
  color: #fff;
  padding: 7px 12px;
  font-size: 0.72rem;
  font-weight: 850;
}

.hero-phone-actions {
  display: flex;
  gap: 8px;
  margin-top: 2px;
}

.hero-phone-actions span {
  border-radius: 999px;
  padding: 7px 12px;
  font-size: 0.72rem;
  font-weight: 850;
}

.hero-phone-actions span:first-child {
  background: var(--teal);
  color: #fff;
}

.hero-phone-actions span:last-child {
  border: 1px solid var(--line);
  background: #fff;
  color: var(--ink);
}

.hero-phone-check {
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  border-radius: 999px;
  background: var(--teal);
  color: #fff;
  font-size: 1.1rem;
  font-weight: 900;
}


/* ==========================================================================
   FAMILY - WILLIAMS VIDEO
   ========================================================================== */

.williams-video-panel {
  --scene-w: min(1280px, calc(100vw - 48px), calc((100vh - var(--safe-top) - 56px) * 16 / 9));
  --scene-w: min(1280px, calc(100vw - 48px), calc((100svh - var(--safe-top) - 56px) * 16 / 9));
  position: relative;
  background:
    linear-gradient(180deg, #faf6fb 0%, rgba(250, 246, 251, 0) 4%),
    linear-gradient(0deg, #faf6fb 0%, rgba(250, 246, 251, 0) 4%),
    radial-gradient(circle at 82% 16%, rgba(181, 213, 245, 0.42), transparent 34%),
    radial-gradient(circle at 14% 30%, rgba(232, 213, 245, 0.5), transparent 34%),
    radial-gradient(circle at 74% 78%, rgba(255, 214, 196, 0.34), transparent 34%),
    linear-gradient(180deg, #fffaf2 0%, #faf6fb 100%);
}

.williams-video-stage {
  position: relative;
  display: grid;
  align-items: center;
  justify-items: center;
  width: 100%;
  min-height: calc(100vh - var(--safe-top) - 48px);
  min-height: calc(100svh - var(--safe-top) - 48px);
}

.williams-video-scene {
  position: relative;
  display: grid;
  place-items: center;
  width: var(--scene-w);
  aspect-ratio: 16 / 9;
  overflow: hidden;
  isolation: isolate;
  border-radius: 28px;
  background: #faf6fb;
  box-shadow: 0 34px 86px rgba(23, 35, 38, 0.2);
}

.williams-video-placeholder {
  position: absolute;
  inset: 0;
  z-index: 1;
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: inherit;
  background: #faf6fb;
}

.williams-video-play {
  position: absolute;
  top: 50%;
  left: 50%;
  z-index: 2;
  display: grid;
  place-items: center;
  width: clamp(56px, 6.8vw, 82px);
  aspect-ratio: 1;
  border: 0;
  border-radius: 50%;
  color: rgba(36, 49, 51, 0.82);
  background:
    radial-gradient(circle at 72% 20%, rgba(181, 213, 245, 0.62), transparent 38%),
    radial-gradient(circle at 18% 26%, rgba(232, 213, 245, 0.7), transparent 42%),
    radial-gradient(circle at 74% 78%, rgba(255, 214, 196, 0.58), transparent 42%),
    linear-gradient(180deg, rgba(255, 250, 242, 0.74), rgba(250, 246, 251, 0.64));
  box-shadow:
    inset 0 8px 24px rgba(255, 255, 255, 0.46),
    inset 0 -16px 30px rgba(70, 55, 72, 0.18),
    0 20px 54px rgba(23, 35, 38, 0.24);
  cursor: pointer;
  transform: translate(-50%, -50%);
  backdrop-filter: blur(12px) saturate(1.08);
  transition: transform 160ms ease, opacity 160ms ease, box-shadow 160ms ease, border-color 160ms ease;
}

.williams-video-play::before {
  content: "";
  position: absolute;
  inset: -10px;
  border-radius: inherit;
  background:
    radial-gradient(circle, rgba(250, 246, 251, 0.3) 0 48%, rgba(250, 246, 251, 0) 70%);
}

.williams-video-play::after {
  content: "";
  position: absolute;
  inset: 9px;
  border-radius: inherit;
  box-shadow: inset 0 12px 24px rgba(23, 35, 38, 0.16);
  pointer-events: none;
}

.williams-video-play span {
  position: relative;
  z-index: 1;
  width: 0;
  height: 0;
  margin-left: 7%;
  border-top: clamp(9px, 1.1vw, 14px) solid transparent;
  border-bottom: clamp(9px, 1.1vw, 14px) solid transparent;
  border-left: clamp(14px, 1.7vw, 22px) solid currentColor;
}

.williams-video-play:hover {
  transform: translate(-50%, -50%) scale(1.04);
  box-shadow:
    inset 0 8px 24px rgba(255, 255, 255, 0.5),
    inset 0 -18px 32px rgba(70, 55, 72, 0.2),
    0 24px 62px rgba(23, 35, 38, 0.28);
}

.williams-video-play:focus-visible {
  outline: 3px solid rgba(36, 49, 51, 0.42);
  outline-offset: 5px;
}

.williams-video-scene.is-playing .williams-video-play {
  opacity: 0;
  pointer-events: none;
  transform: translate(-50%, -50%) scale(0.92);
}

.williams-video-empty {
  position: relative;
  z-index: 0;
  display: grid;
  place-items: center;
  width: min(72%, 720px);
  aspect-ratio: 16 / 9;
  border: 1px dashed rgba(255, 255, 255, 0.36);
  border-radius: 18px;
  color: rgba(255, 255, 255, 0.78);
  font-size: clamp(0.86rem, 1.4vw, 1.05rem);
  font-weight: 800;
  text-transform: uppercase;
}

@media (max-height: 560px) and (orientation: landscape) {
  .williams-video-panel {
    --scene-w: min(1280px, calc(100vw - 20px), calc((100vh - 74px) * 16 / 9));
    --scene-w: min(1280px, calc(100vw - 20px), calc((100svh - 74px) * 16 / 9));
  }

  .williams-video-scene {
    border-radius: 18px;
  }
}

@media (max-width: 760px), (orientation: portrait) and (max-width: 1024px) {
  .williams-video-panel,
  .stage-rail .nav-family {
    display: none;
  }
}

/* ==========================================================================
   EXPLORE - continuous product showcase
   ========================================================================== */

.explore-stage {
  position: relative;
  background:
    /* The seal into the testimonials above, which end on a flat #e5f0f9 (their
       own bottom seal). Without it this section opens on whatever its top-right
       radial happens to reach at this viewport - measured, rgb(236,244,250) at
       1440x900 and rgb(242,247,250) on a 412px phone, neither of them #e5f0f9
       and neither of them the same across the width. A flat first 9% gives the
       join one colour to be on both sides of, at every size. */
    linear-gradient(180deg, #e5f0f9 0%, rgba(229, 240, 249, 0) 9%),
    radial-gradient(circle at 82% 4%, rgba(181, 213, 245, 0.32), transparent 30%),
    radial-gradient(circle at 10% 26%, rgba(232, 213, 245, 0.3), transparent 32%),
    linear-gradient(180deg, #f3f8fb 0%, #fbf7ef 42%, #fdfbf7 100%);
}

/* ==========================================================================
   LOVED BY PARENTS AND KIDS - an endless loop

   A row of equal cards drifting right to left without end, paused while a
   pointer rests on it or it has keyboard focus. It was a centre-focus rail
   with arrows and dots until 2026-10-10; a row that is already moving says
   "there is more" without being a control the reader has to work.
   ========================================================================== */

.love-stage {
  position: relative;
  /* A full screen of its own, with the rail centred in what the header
     leaves. It is a snap target, so it lands at its own top. */
  display: grid;
  /* minmax(0, 1fr), not the default auto: an auto column sizes to max-content,
     and this one's max-content is all five cards laid end to end - the section
     grew to 754px inside a 390px viewport and took the page's width with it. */
  grid-template-columns: minmax(0, 1fr);
  align-content: center;
  min-height: 100vh;
  min-height: 100svh;
  /* >= --safe-top: landingTop() then lands it flush instead of lifting it and
     leaving a header-tall band of the previous section above. */
  padding: calc(var(--safe-top) + 3vh) 0 clamp(40px, 6vh, 76px);
  /* A BRIDGE, not an island. This page sits between the Williams video, which
     ends on #faf6fb, and the Explore tour, which opens on #f3f8fb - so those
     are its first and last stops exactly, and the two seams cannot be found.
     It was starting on #fdf7f2 and ending on #edf4fb, near-misses either end,
     which is what made it look like a different site's page dropped in.

     The radials are at the strength the rest of the site uses (Your Story runs
     0.4-0.46); at the 0.26 they started on this was the one grey screen in a
     warm site. They are also kept away from the top and bottom edges for the
     same reason - a tint that reaches a seam IS the seam. Ellipses rather than
     circles: a circle's stops carry past the edges of a wide short box. */
  /* The seals. Both neighbours are gradient-heavy, so their edge colour is
     not their declared base - measured, the Williams video leaves this seam on
     ~#efeaee and the Explore tour opens on ~#e5f0f9, not the #faf6fb and
     #f3f8fb their stylesheets name. These two layers put this section on
     those measured colours for the first and last 12%, so each seam has the
     same pixel either side of it.

     The top seal is SHAPED, not flat, because the chapter above does not end
     on one colour: its own vignette leaves the middle of the seam at
     ~rgb(234,230,234) while the far left and right are at ~rgb(244,240,246),
     a 12-point spread. A flat top stop can only match one of them - measured,
     it matched the centre and left a line down both thirds. An ellipse
     anchored above the top edge reproduces that shape: darker in the middle,
     lighter at the sides, gone within 100px.

     ...is what the top seal USED to do, with an ellipse anchored above the top
     edge. It has been flat since the chapter learned to seal its own bottom
     edge (see .williams-video-panel): a shape can only match the one viewport it was
     measured at, and the same ellipse that matched at one size was a 10-21/255
     line at 1024, 1280, 1440 and 1920. Both sides flat on #faf6fb match at
     every size, and on a phone - where the chapter is not rendered at all and
     the hero is the neighbour - they still match, because the hero seals its
     bottom edge to the same colour. */
  --love-seal-top: linear-gradient(180deg, #faf6fb 0%, rgba(250, 246, 251, 0) 14%);
  background:
    var(--love-seal-top),
    linear-gradient(0deg, #e5f0f9 0%, rgba(229, 240, 249, 0) 12%),
    radial-gradient(ellipse 60% 44% at 84% 30%, rgba(150, 199, 245, 0.46), transparent 66%),
    radial-gradient(ellipse 54% 42% at 8% 40%, rgba(226, 205, 248, 0.48), transparent 66%),
    radial-gradient(ellipse 70% 38% at 46% 74%, rgba(255, 205, 180, 0.4), transparent 70%),
    linear-gradient(180deg, #e9e5ea 0%, #f7f3fa 46%, #e5f0f9 100%);
}

/* One accent per card, cycling the site palette, so five testimonials read as
   five people rather than five copies of one card. */
.love-card:nth-child(5n + 1) { --accent: 255, 107, 53; }
.love-card:nth-child(5n + 2) { --accent: 124, 58, 237; }
.love-card:nth-child(5n + 3) { --accent: 47, 127, 120; }
.love-card:nth-child(5n + 4) { --accent: 245, 158, 11; }
.love-card:nth-child(5n + 5) { --accent: 90, 155, 230; }

.love-head {
  text-align: center;
  margin-bottom: clamp(18px, 2.6vh, 34px);
}

.love-head h2 {
  /* margin-inline, not just text-align: the global h2 caps at max-width 980px
     with no auto margins, so on a wide window the text centred inside a box
     that started at the wrap's left edge - 100px left of the page's middle. */
  max-width: 980px;
  margin-inline: auto;
  font-size: clamp(1.9rem, 3.2vw, 3rem);
  line-height: 1.06;
}

/* Full-bleed by being full width, NOT by margin-inline: calc(50% - 50vw) -
   that measures 100vw, which counts a classic scrollbar, and gave the page a
   horizontal scrollbar of its own.

   Scrollable by default, which is the whole row for a reader without script
   or with reduced motion. Once the loop runs it is hidden instead: a row the
   reader can drag while it also animates fights them.

   The vertical padding is the cards' shadow; overflow-y
   has to be hidden (overflow-x:auto alone computes it to auto), so the
   padding is what keeps the shadow from being cut in a straight line. The
   side fades are what the loop enters and leaves through. */
.love-marquee {
  --love-gap: clamp(16px, 1.8vw, 24px);
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: none;
  outline: none;
  -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 7%, #000 93%, transparent 100%);
  mask-image: linear-gradient(90deg, transparent 0, #000 7%, #000 93%, transparent 100%);
}

.love-marquee::-webkit-scrollbar {
  display: none;
}

/* Keyboard focus pauses the loop, so it needs to be visible. Drawn on the
   track's top edge rather than as an outline: the side fades would eat one. */
.love-marquee:focus-visible {
  box-shadow: inset 0 3px 0 rgba(47, 127, 120, 0.55);
}

/* max-content, so the track IS its contents and -50% of it is exactly one
   copy of the five. */
.love-track {
  display: flex;
  /* stretch: every card is as tall as the longest quote, so the row reads as
     one even band rather than five boxes of different sizes. */
  align-items: stretch;
  gap: var(--love-gap);
  width: max-content;
  padding: 14px clamp(18px, 4vw, 48px) 44px;
}

/* Looping: no leading padding and a trailing one exactly one gap wide, so
   both halves are (card + gap) x 5 and the seam is invisible. */
.love-marquee:has(.is-looping) {
  overflow-x: hidden;
}

.love-track.is-looping {
  padding-inline: 0 var(--love-gap);
  animation: love-loop var(--love-loop, 60s) linear infinite;
}

.love-marquee:hover .love-track,
.love-marquee:focus-within .love-track,
.love-track.is-offscreen {
  animation-play-state: paused;
}

@keyframes love-loop {
  to { transform: translateX(-50%); }
}

.love-card {
  position: relative;
  flex: 0 0 clamp(280px, 25vw, 360px);
  display: grid;
  align-content: start;
  gap: 16px;
  margin: 0;
  padding: clamp(20px, 1.8vw, 26px) clamp(20px, 1.9vw, 28px) clamp(22px, 2vw, 28px);
  border: 1px solid rgba(16, 18, 38, 0.06);
  border-radius: 22px;
  background: #fff;
  text-align: left;
  /* The accent rides in the shadow rather than on a border: a coloured edge
     reads as a state, a coloured shadow reads as light. */
  box-shadow:
    0 16px 36px rgba(var(--accent, 23, 35, 38), 0.1),
    0 2px 8px rgba(16, 18, 38, 0.04);
  transition: transform 260ms ease, box-shadow 260ms ease;
}

.love-marquee:hover .love-card:hover {
  transform: translateY(-3px);
  box-shadow:
    0 22px 44px rgba(var(--accent, 23, 35, 38), 0.16),
    0 2px 8px rgba(16, 18, 38, 0.05);
}

/* The opening mark, in the card's accent, where the screenshot's source
   logo would sit. On the card so it cannot end up inside the quotation. */
.love-card::before {
  content: "\201C";
  position: absolute;
  top: 8px;
  right: 20px;
  color: rgba(var(--accent, 47, 127, 120), 0.28);
  font-size: 3.4rem;
  font-weight: 900;
  line-height: 1;
  pointer-events: none;
}

.love-card figcaption {
  display: flex;
  align-items: center;
  gap: 12px;
  font-size: 0.86rem;
  line-height: 1.35;
}

.love-card figcaption img {
  flex: none;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  object-fit: cover;
  object-position: center 22%;
  box-shadow: 0 0 0 2px #fff, 0 0 0 4px rgba(var(--accent, 47, 127, 120), 0.3);
}

.love-card figcaption b {
  display: block;
  color: var(--ink);
  font-weight: 800;
}

.love-card figcaption span span {
  color: var(--muted);
}

.love-card blockquote {
  margin: 0;
  color: var(--ink);
  font-size: clamp(0.94rem, 1vw, 1.02rem);
  font-weight: 500;
  line-height: 1.55;
  text-wrap: pretty;
}

@media (max-width: 760px) {
  .love-card {
    flex-basis: min(78vw, 320px);
  }
}

/* ==========================================================================
   WHY FAMILIES LOVE NESTORY - the second half of the credibility page

   Not a logo strip and not a second page. The rail above is what a parent
   feels; this is four reasons why, sharing one screen with it because a
   reader weighs the two together. It named publications until 2026-09-22 and
   read as press quotes until 2026-10-10; it is product benefits now, so no
   mastheads, no quotation marks, no "Featured in". The class names still say
   `press` because renaming forty selectors buys nothing but a diff.
   ========================================================================== */

/* The rule and its label are one object: the hairline runs the full width and
   the label sits ON it, in the section's own ground, rather than floating
   above a line that has nothing to do with it. */
.press-band {
  margin-top: clamp(20px, 3vh, 40px);
  padding-top: clamp(14px, 2vh, 24px);
}

/* The rule is drawn as two segments either side of the label, NOT as one line
   with the label knocked out of it on a background swatch: the section's
   ground is a four-layer gradient now, and no flat swatch can sit on that
   without showing as a patch. */
.press-label {
  display: flex;
  align-items: center;
  gap: clamp(12px, 2vw, 22px);
  margin-bottom: clamp(16px, 2.2vh, 26px);
  color: var(--teal-deep);
  font-size: clamp(0.92rem, 1.15vw, 1.15rem);
  font-weight: 900;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  white-space: nowrap;
}

.press-label::before,
.press-label::after {
  content: "";
  flex: 1;
  height: 1px;
  background: linear-gradient(90deg, rgba(16, 18, 38, 0), rgba(16, 18, 38, 0.16));
}

.press-label::after {
  background: linear-gradient(90deg, rgba(16, 18, 38, 0.16), rgba(16, 18, 38, 0));
}

/* Columns, not a carousel. Breadth is the claim this band makes, so all four
   moments are on screen; auto-fit drops them to two and then one rather than
   crushing a fixed four-across into a phone. */
.press-list {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: clamp(18px, 2.4vw, 40px);
  margin: 0;
  padding: 0;
  list-style: none;
  text-align: left;
}

/* Hairline between columns, drawn on the item so it disappears by itself
   when the grid wraps to fewer columns... except it does not know that, which
   is why it is a left border on every item and a negative one is not needed:
   the first column's rule is hidden by the grid's own left edge via :first-child,
   and a wrapped row's first item keeps it. Acceptable: at one column the rule
   reads as an indent rail, which is what the mobile block turns it into. */
.press-item:nth-child(4n + 1) { --accent: 255, 107, 53; }
.press-item:nth-child(4n + 2) { --accent: 124, 58, 237; }
.press-item:nth-child(4n + 3) { --accent: 47, 127, 120; }
.press-item:nth-child(4n + 4) { --accent: 90, 155, 230; }

.press-item {
  display: grid;
  align-content: start;
  gap: 10px;
  padding-left: clamp(14px, 1.6vw, 24px);
  /* The divider carries the column's accent instead of grey - it is the only
     colour in a row of small dark type, and it is what makes four columns
     read as four sources. */
  border-left: 2px solid rgba(var(--accent, 16, 18, 38), 0.34);
}

.press-item:first-child {
  padding-left: clamp(14px, 1.6vw, 24px);
}

/* A small mark above each heading, in the column's accent, so the four read
   as four different reasons before a word of them is read. */
.press-icon {
  display: grid;
  place-items: center;
  width: 36px;
  height: 36px;
  border-radius: 11px;
  color: rgb(var(--accent, 31, 94, 89));
  background: rgba(var(--accent, 31, 94, 89), 0.1);
}

.press-icon svg {
  width: 19px;
  height: 19px;
}

/* The benefit's title, set in the page's own type. */
.press-masthead {
  margin: 0;
  color: var(--ink);
  font-size: clamp(0.95rem, 1.15vw, 1.18rem);
  font-weight: 930;
  letter-spacing: -0.02em;
  line-height: 1.15;
}

.press-quote {
  margin: 0;
  color: var(--muted);
  font-size: clamp(0.86rem, 0.95vw, 0.98rem);
  font-weight: 500;
  line-height: 1.5;
  text-wrap: pretty;
}

/* Staggered in on first view. .is-pending comes from gsap-site.js, never the
   markup, so without script or with reduced motion the columns just show. */
.press-band.is-pending .press-item {
  opacity: 0;
  transform: translateY(18px);
  transition: opacity 620ms ease, transform 620ms cubic-bezier(.2, .8, .3, 1);
}

.press-band.is-pending .press-item:nth-child(2) { transition-delay: 110ms; }
.press-band.is-pending .press-item:nth-child(3) { transition-delay: 220ms; }
.press-band.is-pending .press-item:nth-child(4) { transition-delay: 330ms; }

.press-band.is-in .press-item {
  opacity: 1;
  transform: none;
}

.love-stage.has-press .love-head {
  margin-bottom: clamp(10px, 1.4vh, 18px);
}

/* Short desktop windows: the two layers still share one screen, so the slack
   comes out of both rather than letting the page run 57px past its own fold
   at 1366x768. */
@media (min-width: 761px) and (max-height: 840px) {
  .love-stage.has-press {
    padding-block: calc(var(--safe-top) + 8px) clamp(20px, 3vh, 34px);
  }

  .love-stage.has-press .love-head { margin-bottom: 8px; }

  .love-stage.has-press .love-head h2 {
    font-size: clamp(1.5rem, 2.4vw, 2.2rem);
  }

  
  .press-band {
    margin-top: 14px;
    padding-top: 16px;
  }

  .press-list { gap: clamp(14px, 1.8vw, 28px); }

  .press-quote { font-size: 0.84rem; }
}

@media (max-width: 760px) {

  /* The band drops .wrap's constraint and takes the full width, with the
     gutter moved onto the label and the rail as padding.

     NOT a negative margin out of .wrap: that has to know the gutter's exact
     width, it guessed 18px, and the box it actually resolved against was 8px
     wider - the rail hung 4px off the left of the document and gave the whole
     page a horizontal scrollbar. Padding cannot be wrong in that way. */
  .press-band {
    width: 100%;
  }

  .press-label {
    padding-inline: 18px;
  }

  /* A swipe rail, not a stack: four stacked entries ran the section 131px
     past the fold on a 844px phone, and this page's whole contract is that
     each screen is one screen. Same gesture as the testimonial rail above it. */
  .press-list {
    grid-template-columns: none;
    grid-auto-flow: column;
    grid-auto-columns: 62vw;
    gap: 0;
    overflow-x: auto;
    /* Stated, never implied - see .stage-rail. `overflow-x: auto` alone makes
       overflow-y `auto` too, and a one-row rail that can scroll vertically
       will eventually be scrolled vertically by something. */
    overflow-y: hidden;
    /* mandatory, not proximity: the columns are a swipe apart here, which is
       the case proximity is good at and mandatory is safe for. */
    scroll-snap-type: x mandatory;
    scroll-padding-left: 18px;
    scrollbar-width: none;
    padding-inline: 18px;
  }

  .press-list::-webkit-scrollbar { display: none; }

  .press-item {
    scroll-snap-align: start;
    padding: 0 18px 0 0;
    border-left: 0;
    /* The rule goes on the RIGHT here: with a left border the first column
       carried a stray rule hard against the screen edge, and removing it from
       the first only made the remaining three look misaligned. */
    border-right: 2px solid rgba(var(--accent, 16, 18, 38), 0.34);
    margin-right: 18px;
  }

  .press-item:first-child {
    padding-left: 0;
  }

  .press-item:last-child {
    border-right: 0;
    margin-right: 0;
  }
}

/* --- explore intro + sticky showcase ------------------------------------
   Explore mirrors Add: first a quiet headline page, then the interactive
   product surface on the next scroll beat. */

.explore-intro {
  display: grid;
  align-items: center;
  min-height: clamp(430px, 52vh, 560px);
  min-height: clamp(430px, 52svh, 560px);
  padding: calc(var(--safe-top) + clamp(24px, 5vh, 68px)) 0 clamp(30px, 5vh, 64px);
  padding: calc(var(--safe-top) + clamp(24px, 5svh, 68px)) 0 clamp(30px, 5svh, 64px);
}

.explore-showcase {
  display: grid;
  grid-template-rows: minmax(0, 1fr);
  /* >= --safe-top, so landingTop() reads this as a block that clears the
     header itself and lands it flush instead of lifting it - a lift here
     leaves a header-tall band of the previous section showing above. */
  padding: calc(var(--safe-top) + 2vh) 0 clamp(18px, 3vh, 38px);
  /* A definite height, not a min-height: the 1fr track row can only take
     "what is left" if there is a fixed total to take it from. Under
     min-height the grid sizes to content, 1fr resolves to max-content, and
     the mock hangs 127px below the fold of a box that is pinned there. */
  height: 100vh;
  height: 100svh;
}

.explore-head {
  max-width: 900px;
}

.explore-head h2 {
  max-width: 10.8em;
  margin-top: 6px;
  font-size: clamp(2.6rem, 5.2vw, 5.2rem);
  line-height: 1.08;
  letter-spacing: 0;
}

.explore-word {
  position: relative;
  color: var(--purple);
  letter-spacing: -0.01em;
}

.explore-word::after {
  content: "";
  position: absolute;
  inset: auto 0 0.08em;
  height: 0.1em;
  border-radius: 999px;
  background: linear-gradient(90deg, rgba(124, 58, 237, 0.9), rgba(47, 127, 120, 0.6));
}

.explore-head .lead {
  max-width: 660px;
  margin-top: 22px;
}

.explore-track {
  display: grid;
  /* The product column gets the larger share, because what it holds is a wall
     tablet being read at half size and the copy beside it is four short lines.
     It was 0.82/1.18 with a 72px gutter, which put the screen at 638px - the
     tab is authored at 1284, so that is 0.50 and the type on it is at half
     size. 0.70/1.30 and a tighter gutter carry it to about 715, which is 0.56.

     The ceiling is the row's height, not this ratio: the screen is 16/11, so a
     wider column is also a taller one, and it has to keep fitting under the
     heading. At 1920 - the tightest case, because the gutter is at its
     maximum there - 715 wide is 491 tall against a 504px budget. Widening
     further needs the heading to give something up first. */
  grid-template-columns: minmax(0, 0.70fr) minmax(0, 1.30fr);
  align-items: center;
  gap: clamp(28px, 3vw, 48px);
  /* The showcase's 1fr row is the height budget now; a min-height here would
     push the track past the fold the heading has to share. */
  min-height: 0;
}

.explore-copy-track {
  position: relative;
  display: block;
  min-width: 0;
  /* The chapters are absolute/inset:0, so this column has no content height
     of its own - it has to take the row's instead, or it collapses to zero
     and all five chapters render in a 0px box. */
  align-self: stretch;
  min-height: 0;
}

.explore-chapter {
  position: absolute;
  inset: 0;
  display: grid;
  align-content: center;
  align-items: start;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
}

.explore-chapter.is-active {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
}

.explore-chapter h3 {
  max-width: 460px;
  min-height: 2.12em;
  margin-top: 16px;
  font-size: clamp(1.9rem, 3.1vw, 3.2rem);
  line-height: 1.06;
  letter-spacing: 0;
}

/* The link out of each Explore chapter to its own page.

   These exist for two readers. A visitor who wants the detail this pinned
   showcase deliberately does not carry, and a crawler: before them the
   homepage's only internal links were in the footer, so the only sitelinks
   Google could offer were Privacy Policy and Terms of Service. Anchor text
   is the signal, which is why each one names the feature rather than saying
   "learn more".

   No layout risk: .explore-chapter is position:absolute/inset:0, so the four
   chapters share one box and a line added inside one cannot move the track.
   pointer-events follows .is-active, so only the visible chapter is
   clickable. */
.explore-more {
  margin-top: 22px;
}

.explore-more a {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  color: var(--teal-deep);
  font-size: 0.95rem;
  font-weight: 800;
  text-decoration: none;
  border-bottom: 2px solid rgba(47, 127, 120, 0.28);
  padding-bottom: 2px;
  transition: color 180ms ease, border-color 180ms ease;
}

.explore-more a::after {
  content: "\2192";
  transition: transform 180ms ease;
}

.explore-more a:hover {
  color: var(--teal);
  border-color: rgba(47, 127, 120, 0.6);
}

.explore-more a:hover::after {
  transform: translateX(3px);
}

.explore-chapter .explore-copy {
  max-width: 440px;
  min-height: 4.86em;
  margin-top: 18px;
  color: var(--muted);
  font-size: 1.04rem;
  line-height: 1.62;
}

.explore-product-stage {
  position: relative;
  /* stretch, not center: the stack below needs a DEFINITE height to be a size
     container, and the only definite height on this axis is the track row's.
     Centred, it was content-sized, which is how a 514px tablet ended up
     hanging out of a 443px row - see .explore-visual-stack. */
  align-self: stretch;
  min-height: 0;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: auto minmax(0, 1fr);
  /* Row 1 is the nav, which is display:none here - see below. The gap goes
     with it, or the stack sits 16px lower than the column it is centred
     against. The rail's own spacing is set in the .is-swipe block. */
  gap: 0;
  padding-bottom: 0;
  min-width: 0;
}

/* Hidden where the scroll position is already the indicator, shown where it is
   the only control.

   On desktop the pinned section IS the navigation - a chapter per scroll, with
   the copy beside the screen naming it - so the row of pills underneath said
   nothing the reader did not already know, and it cost the tablet 47px of
   height, which at 16/11 is 68px of width it could have been.

   On the mobile rail it is the carousel's control and the ONLY one
   (gsap-site.js, the click handler on [data-explore-nav]), so it stays. The
   buttons are left in the markup rather than deleted for the same reason: the
   two layouts share one set, and the JS scrolls the active pill into view. */
.explore-nav {
  grid-row: 1;
  display: none;
  flex-wrap: wrap;
  gap: 7px;
}

.explore-nav button {
  position: relative;
  border: 0;
  border-radius: 0;
  background: transparent;
  color: var(--muted);
  padding: 8px 2px 10px;
  font: inherit;
  font-size: 0.78rem;
  font-weight: 850;
  white-space: nowrap;
  cursor: pointer;
  transition: color 180ms ease;
}

.explore-nav button span {
  margin-right: 5px;
  color: rgba(47, 127, 120, 0.58);
  font-size: 0.68rem;
  font-weight: 900;
}

.explore-nav button::after {
  content: "";
  position: absolute;
  inset: auto 0 0;
  height: 2px;
  border-radius: 999px;
  background: rgba(47, 127, 120, 0.72);
  transform: scaleX(0);
  transform-origin: center;
  transition: transform 180ms ease;
}

.explore-nav button:hover {
  color: var(--ink);
}

.explore-nav button.is-active {
  color: var(--ink);
}

.explore-nav button.is-active::after {
  transform: scaleX(1);
}

.explore-visual-stack {
  grid-row: 2;
  grid-column: 1;
  display: grid;
  min-width: 0;
  min-height: 0;
  /* THE TABLET'S HEIGHT BUDGET, and the only place that knows it.

     .explore-screen is 16/11, so the frame's height follows its WIDTH, and
     its width followed the column - which meant a window short enough to
     squeeze the row but not narrow enough to squeeze the column drew a 514px
     tablet inside a 443px row and let the bottom of the bezel run off the
     fold. Measured: 1743x875 clipped by 14px. Nothing was clipping it; it was
     simply taller than the box it was centred in, and the shrinking was being
     done on the HEADING instead (the max-height blocks at :3596), which can
     only buy so much before it runs out.

     A max-height cannot fix that: a ratio box whose width is already resolved
     does not re-derive its width from a clamped height (tested - it just gets
     squashed). So the cap is written on the WIDTH, in terms of the height
     that is available, which is what cqh is for. Being a size container is
     also why the stage above had to stretch: cqh needs a definite height.

     align-content centres the row rather than stretching it, so the frame
     keeps its ratio and sits in the middle of whatever is left. */
  container-type: size;
  align-content: center;
}

/* frame height = (width - 24px of padding) * 883/1284 + 24px, and it has to
   come in under the container. Solved for width, capped at the column.

   883/1284 and not 16/11, and ONE sum and not four. The aspect-ratios
   declared on .explore-screen a few rules up are dead: :6370 drops them all
   with `aspect-ratio: auto`, so every screen's height is its screenshot's,
   and all four screenshots are 1284x883. Writing 16/11.5 for the three
   non-wall tabs here would be repeating a shape they do not actually have. */
.explore-visual-stack .explore-frame {
  width: min(100%, calc((100cqh - 24px) * 1284 / 883 + 24px));
  margin-inline: auto;
}

.explore-visual {
  grid-area: 1 / 1;
  /* Grid items default to min-width:auto and refuse to shrink below their
     min-content width (428px here). Below ~828px the track's column is
     narrower than that, so the mock pushed through the wrap and gave the
     whole page a horizontal scrollbar. The stack already had min-width:0;
     the child needs it too. */
  min-width: 0;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transform: translateY(16px) scale(0.985);
  transition: opacity 0.5s ease, transform 0.5s ease, visibility 0s 0.5s;
}

.explore-visual.is-active {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transform: none;
  transition: opacity 0.5s ease, transform 0.5s ease;
}

/* --- shared product frame + widgets ------------------------------------- */

.explore-frame {
  border-radius: 26px;
  background: var(--dark);
  padding: 12px;
  box-shadow:
    0 46px 90px rgba(23, 35, 38, 0.22),
    0 10px 28px rgba(16, 18, 38, 0.1);
}

.explore-screen {
  position: relative;
  aspect-ratio: 16 / 11;
  overflow: hidden;
  border-radius: 17px;
  background:
    radial-gradient(circle at 12% 8%, rgba(255, 214, 232, 0.6), transparent 34%),
    radial-gradient(circle at 88% 94%, rgba(181, 213, 245, 0.55), transparent 38%),
    #fffdf8;
  display: flex;
  flex-direction: column;
}

.explore-screenshot {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* THE DRAWN MOCKS ARE GONE FROM HERE.

   Explore used to draw its four screens in page pixels - a fake top bar, a fake
   dock, rows of events and chores and meals and a phone - and roughly 600 lines
   of this file dressed them. They were replaced first by real screenshots and
   then, for a reader on a desktop, by the app's own tabs mounted live
   (explore-demo.*), and nothing has referenced any of it since. Forty class
   names' worth: .explore-topbar, -greet, -pill, -label, -avatar, -check, -col,
   -dock, -event*, -chore*, -meal*, -grocery, -recipe, -reward, -coins, -phone*,
   -capture-*, -calendar-*, -meals-*, -chores-*, -quick-add, -added, -approve,
   -prep-panel, -wall-body and -trust-grid/-block.

   What is left in this section is the LAYOUT - the stage, the track, the
   chapters, the pills, the frame, the screen and the screenshot - which is what
   holds the real thing. */

/* The three non-wall tabs are drawn a touch taller than the wall's 16/11,
   which is what their screenshots are. */
.explore-screen--calendar,
.explore-screen--meals,
.explore-screen--chores {
  aspect-ratio: 16 / 11.5;
}

/* --- final CTA ---------------------------------------------------------- */

.explore-final-cta {
  padding: 56px 0 120px;
  text-align: center;
}

.explore-final-cta-inner {
  display: grid;
  justify-items: center;
  gap: 22px;
}

.explore-final-cta h2 {
  max-width: 760px;
  font-size: clamp(2.3rem, 4.6vw, 4.2rem);
  line-height: 1.04;
}

.explore-final-cta .lead {
  max-width: 720px;
}

.explore-final-cta .hero-actions {
  margin-top: 6px;
}

/* --- responsive: tablet ------------------------------------------------- */

@media (max-width: 1180px) {
  .explore-track {
    grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr);
    gap: 28px;
  }

  .explore-chapter {
    min-height: 66vh;
    min-height: 66svh;
  }
}

/* The heading shares one screen with the mock, and the mock has a hard floor:
   it is a real-sized UI, so its content will not go below ~400px tall however
   narrow the column gets. Below ~820px of viewport the full heading block
   does not fit in front of it, so the LEAD is what goes - not the headline.
   Shrinking a two-line headline to buy room for three lines of supporting
   copy gets the trade backwards: the headline is the signpost the section
   needs at a glance, the lead is what a reader on a short window can do
   without. */
@media (min-width: 1025px) and (max-height: 820px) {
  .explore-showcase {
    padding-top: calc(var(--safe-top) + 10px);
  }

  /* Capped by vh as well as vw: two lines is a HEIGHT cost, and by width
     alone a 1600x760 window ran 12px past its own fold while a narrower,
     taller one had 17px to spare. */
  .explore-head h2 {
    font-size: clamp(1.8rem, min(3.6vw, 7.4vh), 3.6rem);
  }
}

/* Tightest case: one line again, and only here. The cut is at 730px, not
   700px: two lines and the mock still overlapped by 5px at 701. */
@media (min-width: 1025px) and (max-height: 730px) {

  /* One line again down here, and only down here: two lines of heading plus
     the mock do not both fit in 640px of viewport, and the mock has a ~400px
     floor it cannot go below. */
  .explore-head h2 {
    max-width: none;
    font-size: clamp(1.4rem, 2vw, 2rem);
  }
}

/* --- responsive: mobile ------------------------------------------------- */

/* Compound, not a bare max-width: this must match gsap-site.js MOBILE_MQ
   exactly, or a tablet in portrait runs one scroll model while desktop-only
   layout assumptions still apply here. */
@media (max-width: 760px), (orientation: portrait) and (max-width: 1024px) {
  /* Unpinned below this width: the showcase is an ordinary block again, so
     the one-screen grid and its viewport height have to come back off. */
  .explore-intro {
    min-height: clamp(430px, 58vh, 560px);
    min-height: clamp(430px, 58svh, 560px);
    padding-top: calc(var(--safe-top) + clamp(34px, 6vh, 64px));
    padding-top: calc(var(--safe-top) + clamp(34px, 6svh, 64px));
    padding-bottom: clamp(28px, 5vh, 56px);
    padding-bottom: clamp(28px, 5svh, 56px);
  }

  .explore-showcase {
    display: block;
    height: auto;
    padding-top: calc(var(--safe-top) + 18px);
  }


  .explore-head {
    padding-bottom: 24px;
  }

  .explore-track {
    grid-template-columns: minmax(0, 1fr);
    align-items: start;
    min-height: 0;
  }

  .explore-product-stage {
    position: static;
    grid-template-rows: auto;
    align-self: auto;
  }

  /* The showcase is an ordinary block here, so there is no row height to
     measure and the desktop cap has nothing to resolve against - cqh on a
     container with no definite height is 0, which would collapse the frame to
     its padding. Both come back off together. */
  .explore-visual-stack {
    grid-row: auto;
    grid-column: auto;
    display: block;
    container-type: normal;
  }

  .explore-visual-stack .explore-frame {
    width: auto;
  }

  .explore-visual {
    grid-area: auto;
    position: relative;
    opacity: 1;
    visibility: visible;
    transform: none;
    transition: none;
    pointer-events: auto;
    margin-top: 26px;
  }

  .explore-visual:not(.is-active) {
    display: none;
  }

  .explore-copy-track {
    position: static;
    min-height: 0;
  }

  .explore-chapter {
    position: static;
    inset: auto;
    min-height: 0;
    padding: 60px 0 8px;
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
    transform: none;
  }

  .explore-chapter h3 {
    font-size: clamp(1.7rem, 7vw, 2.3rem);
  }

  .explore-copy-track {
    display: grid;
    gap: 0;
  }

}


/* ==========================================================================
   FOOTER

   Closes on the ink the Add panel introduced rather than the old flat
   #172326, so the page ends on a colour it has already used rather than a
   fourth dark. One hairline along the top edge marks it as a deliberate
   edge instead of a background change.
   ========================================================================== */

/* --- FAQ ----------------------------------------------------------------
   Sits in the same #faf6fb field as Your Story above it and the footer below,
   with no band of its own: the three read as one closing page rather than
   three stacked sections. */

/* padding-TOP carries the header reserve, not a symmetric padding-block: the
   FAQ is a landing target now, and landingTop() lifts anything whose own top
   padding does not already clear the header - which would leave a header-tall
   band of Your Story across the top of it. */
.faq {
  padding-top: calc(var(--safe-top) + clamp(10px, 2vh, 28px));
  padding-bottom: clamp(52px, 7vw, 96px);
  background: #faf6fb;
}

.faq-wrap {
  max-width: 800px;
}

.faq h2 {
  margin-bottom: clamp(24px, 3vw, 40px);
  font-size: clamp(1.8rem, 3vw, 2.6rem);
  line-height: 1.08;
  text-align: center;
}

.faq-list details {
  border-bottom: 1px solid var(--line);
}

.faq-list summary {
  position: relative;
  padding: 20px 44px 20px 0;
  color: var(--ink);
  font-size: 1.05rem;
  font-weight: 800;
  list-style: none;
  cursor: pointer;
  transition: color 180ms ease;
}

/* Both spellings: ::marker is the standard, ::-webkit-details-marker is what
   older WebKit still draws, and either one left a bullet beside the question. */
.faq-list summary::marker,
.faq-list summary::-webkit-details-marker {
  display: none;
  content: "";
}

.faq-list summary:hover {
  color: var(--teal-deep);
}

.faq-list summary::after {
  content: "";
  position: absolute;
  right: 8px;
  top: 50%;
  width: 10px;
  height: 10px;
  border-right: 2px solid var(--muted);
  border-bottom: 2px solid var(--muted);
  transform: translateY(-70%) rotate(45deg);
  transition: transform 200ms ease;
}

.faq-list details[open] summary::after {
  transform: translateY(-25%) rotate(-135deg);
}

/* The JS keeps `open` true for the whole of a close, so the answer stays
   measurable while it shrinks - which means [open] alone would hold the
   chevron up until the box had finished moving. .is-closing is set the instant
   the tap lands and turns it with the gesture. With no JS the class never
   appears and [open] above is the whole behaviour, as it was. */
.faq-list details[open].is-closing summary::after {
  transform: translateY(-70%) rotate(45deg);
}

.faq-list details > div {
  padding: 0 44px 22px 0;
  color: var(--muted);
  font-size: 0.99rem;
  line-height: 1.62;
}

.faq-list a {
  color: var(--teal-deep);
}

@media (max-width: 760px) {
  .faq-list summary {
    padding-right: 34px;
    font-size: 1rem;
  }

  .faq-list details > div {
    padding-right: 0;
  }
}

.footer {
  --ink-strong: #101226;
  --ink-soft: rgba(16, 18, 38, 0.66);
  --ink-faint: rgba(16, 18, 38, 0.42);
  --hair: rgba(16, 18, 38, 0.1);
  /* Hardcoded, not var(--teal-deep): that token is teal in gsap-site.css and
     purple in site.css, and this block is deliberately palette-independent. */
  --label: #1f5e59;

  position: relative;
  /* Same flat base as the section above, radials kept low and away from the
     seam, so the footer is the bottom of that field rather than a band under
     it. Nothing separates them - no rule, no colour change. */
  background:
    /* ...and seals the top, so the two elements meet on pure base either
       side and the join cannot be found. */
    linear-gradient(180deg, #faf6fb 16%, rgba(250, 246, 251, 0) 52%),
    radial-gradient(circle at 70% 92%, rgba(255, 214, 196, 0.34), transparent 52%),
    radial-gradient(circle at 14% 80%, rgba(232, 213, 245, 0.4), transparent 52%),
    #faf6fb;
  color: var(--ink-strong);
  /* Symmetric now that the bottom bar is gone - a 30px tail under the last
     link read as the footer having been cut off. */
  padding: clamp(52px, 6vw, 78px) 0 clamp(46px, 5.5vw, 70px);
}

/* A centred cluster, not a spread.

   Six links and a one-line tagline do not fill 1180px, and the two layouts
   that try both fail in their own way: fractional tracks left a dead tail and
   pulled the visible mass 135px left of centre, then pinning the links to the
   right edge centred the mass but opened a ~600px hole in the middle.

   Sizing all three blocks to their content and centring them with one shared
   gap gives even spacing and a centred mass at the same time. */
.footer-top {
  display: flex;
  justify-content: center;
  align-items: flex-start;
  gap: clamp(40px, 7vw, 120px);
}

.footer-brand {
  flex: 0 1 auto;
}

.footer-brand .brand {
  color: var(--ink-strong);
  font-size: 1.12rem;
}

/* No tile any more: on the mesh the mark reads exactly as it does in the
   header, and a plate behind it would be mesh drawn on mesh. */
.footer-brand .brand-mark {
  width: 40px;
  height: 40px;
}

.footer-brand .brand-mark img {
  object-fit: contain;
}

.footer-brand p {
  max-width: 30ch;
  margin-top: 16px;
  color: var(--ink-soft);
  font-size: 0.92rem;
  line-height: 1.6;
}

.footer-links {
  display: flex;
  gap: clamp(40px, 7vw, 120px);
}

.footer-col {
  min-width: 0;
}

/* The site's label colour, the same teal every .eyebrow uses - which also
   takes these from 2.72:1 to AA on the light ground. */
.footer-col h3 {
  margin-bottom: 4px;
  color: var(--label);
  font-size: 0.72rem;
  font-weight: 930;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.footer a:not(.brand) {
  display: block;
  width: fit-content;
  margin-top: 11px;
  color: var(--ink-soft);
  font-size: 0.92rem;
  font-weight: 620;
  text-decoration: none;
  transition: color 180ms ease;
}

.footer a:not(.brand):hover {
  color: var(--ink-strong);
}

.footer a:not(.brand):focus-visible,
.footer .brand:focus-visible {
  outline: 2px solid var(--label);
  outline-offset: 3px;
  border-radius: 5px;
}

/* 760, not 900: at 900 the blocks still sit side by side with room to spare,
   and stacking them there left the whole footer hugging the left of a wrap
   that was itself centred - 238px off. */
@media (max-width: 760px) {
  .footer-top {
    flex-direction: column;
    align-items: stretch;
    gap: 34px;
  }

  /* Stacked under the brand. The columns share the wrap's width equally
     rather than bunching at its left edge, which otherwise left ~300px of
     dead space on the right at tablet widths. */
  .footer-links {
    justify-content: flex-start;
    gap: 20px;
  }

  .footer-col {
    flex: 1 1 0;
  }

  .footer-brand p {
    max-width: 46ch;
  }
}

/* Two columns rather than four stacked: the old footer ran 649px tall on a
   phone, which is most of a screen of links nobody asked for. */
@media (max-width: 560px) {
  .footer-links {
    flex-wrap: wrap;
    gap: 26px 40px;
  }
}

/* THE ISLAND'S SECOND ROW - 699px, not 1180px.

   The rail used to drop to its own row here, which meant a two-line header on
   every laptop window under 1180px. It does not have to: the whole row has a
   measured natural width of 671px, so it fits in one line down to ~707px of
   viewport, and to ~675px with the tighter gaps the 700-1320px block gives it.
   chrome.css carries the measurement and the hamburger breakpoint these three
   files share; below 699 the rail is not a second row at all, it is the panel.

   What is left in this block is the island's own top row at phone width. */
@media (max-width: 699px) {
  :root {
    --header-h: 62px;
    --rail-h: 40px;
  }

  .site-header {
    --nav-top: 12px;
    --nav-h: auto;
    --nav-pad-y: 4px;
    --nav-gap: calc(12px + 4px * var(--nav-p));
  }

  .nav-shell {
    flex-wrap: wrap;
    justify-content: space-between;
    width: 100%;
    row-gap: 0;
  }

  .nav-shell > .brand {
    order: 1;
  }

  .nav-shell > .actions {
    order: 2;
  }

  .nav-shell > .stage-rail {
    order: 3;
    flex: 1 1 100%;
    justify-content: flex-start;
    gap: clamp(14px, 3.4vw, 34px);
    padding-left: 0;
    border-left: 0;
    /* the rail scrolls here - dissolve at the shell edge rather than slicing */
    mask-image: linear-gradient(to right, #000 calc(100% - 26px), transparent);
    -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 26px), transparent);
  }

  .stage-rail :is(button, a) {
    padding: 3px 8px;
  }

  .nav {
    display: none;
  }

  .nav {
    position: fixed;
    inset: var(--header-h) 0 auto 0;
    grid-template-columns: 1fr;
    border-bottom: 1px solid var(--line);
    background: rgba(255, 253, 248, 0.96);
    padding: 14px;
  }

  body.nav-open .nav {
    display: grid;
  }

  .nav a {
    border-radius: 8px;
    padding: 13px 18px;
  }
}

/* Page layout below 1180px - nothing to do with the header. */
@media (max-width: 1180px) {
  .split,
  .split.reverse,
  .proof-layout {
    grid-template-columns: 1fr;
  }

  /* WITH THE HEIGHT CAP, which the override never carried.

     It was `clamp(360px, 44vw, 500px)` - a width, flat, replacing a base rule
     whose whole second half is `min(…, calc(var(--tableau-h) * 1.5))`. At 44vw
     that was survivable by luck; at 52vw it is not, and 1024x600 drew a 532px
     tablet twelve pixels past the bottom of the screen. The cap belongs here
     too: this block narrows the composition, it does not exempt it. */
  .product-hero {
    --main-w: min(clamp(360px, 52vw, 560px), calc(var(--tableau-h) * 1.5));
  }

  .module-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .proof-copy {
    min-height: 310px;
  }

  .proof-kicker {
    display: none;
  }

  .proof-step {
    top: 50%;
  }

  .proof-device {
    height: min(560px, 52vh);
    height: min(560px, 52svh);
    min-height: 420px;
  }

}

/* The island, tightened, everywhere between the hamburger and a wide desktop.
   It used to start at 1181 because below that the rail wrapped; now that the
   row holds down to 700 this is what holds it - 12px gaps and a 0.68rem rail
   are what make 671px of content fit a 700px screen. */
@media (min-width: 700px) and (max-width: 1320px) {
  .site-header {
    --nav-gap: calc(12px + 4px * var(--nav-p));
    --nav-pad-x: calc(11px + 5px * var(--nav-p));
  }

  .stage-rail {
    gap: 10px;
  }

  .stage-rail :is(button, a) {
    padding-inline: 7px;
    font-size: 0.68rem;
  }

  .site-header .button {
    padding-inline: calc(12px + 3px * var(--nav-p));
    font-size: 0.82rem;
  }

  .header-inner {
    gap: 12px;
  }

  .nav {
    gap: 2px;
  }

  .nav a {
    padding-inline: 8px;
    font-size: 0.84rem;
  }

  .button {
    min-height: 40px;
    padding-inline: 16px;
  }
}

@media (max-width: 760px) {
  :root {
    --header-h: 56px;
    --rail-h: 40px;
  }

  :root {
    --wrap-gutter: 14px;
  }

  .wrap,
  .header-inner {
    width: min(100% - var(--wrap-gutter) * 2, 1180px);
  }

  .site-header {
    --nav-top: 10px;
    --nav-settle: 1px;
    --nav-pad-x: calc(9px + 4px * var(--nav-p));
    --nav-pad-y: 5px;
    --nav-radius: calc(15px + 3px * var(--nav-p));
  }

  .site-header .brand {
    font-size: 0.92rem;
    gap: 8px;
  }

  .site-header .brand-mark {
    width: 30px;
    height: 30px;
  }

  .stage-rail :is(button, a) {
    padding: 4px 7px;
  }

  .brand-mark {
    width: 34px;
    height: 34px;
  }

  .actions {
    gap: 7px;
  }

  .site-header .actions {
    gap: 6px;
  }

  .actions .button {
    min-height: 36px;
    padding-inline: 12px;
    font-size: 0.86rem;
  }

  .site-header .button {
    min-height: 34px;
    padding-inline: calc(11px + 2px * var(--nav-p));
    font-size: 0.8rem;
  }

  h1 {
    font-size: clamp(2.8rem, 13vw, 4rem);
  }

  h2 {
    font-size: clamp(2.35rem, 10vw, 3.5rem);
  }

  .snap-panel,
  .pin-stage {
    padding-top: calc(var(--safe-top) + 16px);
  }

  .stage-rail :is(button, a) {
    font-size: 0.66rem;
  }

  .family-wall-demo {
    height: min(620px, calc(100vh - var(--safe-top) - 126px));
    height: min(620px, calc(100svh - var(--safe-top) - 126px));
    min-height: 430px;
    border-width: 10px;
    padding: 12px;
  }

  .wall-grid,
  .scene-grid.four {
    grid-template-columns: 1fr;
  }

  .wall-card {
    min-height: 92px;
    padding: 14px;
  }

  .wall-dock {
    overflow-x: auto;
    /* Stated, never implied - see .stage-rail. `overflow-x: auto` alone makes
       overflow-y `auto` too, and a one-row rail that can scroll vertically
       will eventually be scrolled vertically by something. */
    overflow-y: hidden;
    justify-content: flex-start;
  }

  .rhythm-stage,
  .prep-flow,
  .meal-board,
  .routine-ladder,
  .privacy-console {
    min-height: 300px;
  }

  .module-grid {
    grid-template-columns: 1fr;
  }

  .module-card {
    min-height: 150px;
  }

  .module-card span {
    margin-bottom: 24px;
  }

  .proof-layout {
    gap: 18px;
  }

  .proof-copy {
    min-height: 300px;
  }

  .proof-step {
    top: 34px;
    max-width: 100%;
  }

  .proof-device {
    height: 390px;
    min-height: 390px;
    border-width: 10px;
  }

  .proof-scene {
    inset: 12px;
    gap: 10px;
  }

  .scene-grid span {
    min-height: 62px;
  }

  .calendar-lines {
    min-height: 140px;
  }
}

@media (max-width: 400px) {
  /* Keep brand + Sign in + CTA on one row down to the narrowest phones, so the
     island stays two rows and the stack still fits inside --safe-top. */
  .site-header {
    --nav-pad-x: calc(8px + 3px * var(--nav-p));
    --nav-gap: calc(9px + 3px * var(--nav-p));
  }

  .site-header .brand {
    font-size: 0.86rem;
    gap: 7px;
  }

  .site-header .brand-mark {
    width: 28px;
    height: 28px;
  }

  .site-header .actions {
    gap: 5px;
  }

  .site-header .button {
    min-height: 32px;
    padding-inline: calc(9px + 2px * var(--nav-p));
    font-size: 0.75rem;
  }
}

@media (max-width: 344px) {
  /* Below this the three controls genuinely cannot share a row; let the island
     take a third row and reserve the space for it rather than overlap. */
  :root {
    --rail-h: 68px;
  }

  .site-header {
    --nav-top: 8px;
  }
}

@media (max-width: 480px) and (orientation: portrait) {
  .product-hero {
    --hero-h1: clamp(2.35rem, 11.5vw, 3.1rem);
  }

}

@media (max-height: 540px) and (orientation: landscape) {
  :root {
    --header-h: 52px;
    --rail-h: 40px;
  }

  .site-header {
    --nav-top: 8px;
    --nav-pad-y: 4px;
  }

  .site-header .brand-mark {
    width: 28px;
    height: 28px;
  }

  .site-header .button {
    min-height: 32px;
  }

  .brand-mark {
    width: 32px;
    height: 32px;
  }

  .button {
    min-height: 38px;
    padding-inline: 16px;
  }

  .stage-rail {
    gap: clamp(16px, 4vw, 40px);
  }

  .snap-panel,
  .pin-stage {
    padding-top: calc(var(--safe-top) + 8px);
    padding-bottom: 20px;
  }

  .product-hero {
    --hero-h1: clamp(1.9rem, 5vw, 2.8rem);
    --tableau-h: calc(100vh - var(--safe-top) - 32px);
    --tableau-h: calc(100svh - var(--safe-top) - 32px);
    --main-w: min(clamp(280px, 44vw, 440px), calc(var(--tableau-h) * 1.5));
  }

  .product-hero-wrap {
    grid-template-columns: minmax(0, 0.92fr) minmax(0, 1.08fr);
    grid-template-areas:
      "copy tableau"
      "details tableau";
    align-items: center;
    gap: 14px 22px;
    min-height: calc(100vh - var(--safe-top) - 28px);
    min-height: calc(100svh - var(--safe-top) - 28px);
  }

  .product-hero-copy h1 {
    max-width: 100%;
  }

  .product-hero-details .lead {
    max-width: 460px;
    margin-top: 0;
  }

  .hero-pill-list {
    gap: 5px;
    max-height: 92px;
    margin-bottom: 10px;
  }

  .hero-pill-list span {
    min-height: 25px;
    padding: 4px 8px;
    font-size: 0.72rem;
  }

  .product-hero-details .hero-actions {
    margin-top: 14px;
  }

  .product-hero-details .trust-line {
    display: none;
  }

  .hero-tableau {
    display: flex;
    height: var(--tableau-h);
  }

  .hero-product-main {
    position: relative;
    width: var(--main-w);
    transform: none;
  }

  h1 {
    font-size: clamp(2.3rem, 6vw, 3.3rem);
  }

  h2 {
    font-size: clamp(1.85rem, 4.6vw, 2.65rem);
  }

  .lead {
    font-size: 0.94rem;
    line-height: 1.42;
  }

  .split,
  .split.reverse {
    grid-template-columns: minmax(0, 0.9fr) minmax(260px, 1.1fr);
    gap: 18px;
  }

  .family-wall-demo {
    height: calc(100vh - var(--safe-top) - 96px);
    height: calc(100svh - var(--safe-top) - 96px);
    min-height: 260px;
  }

  .wall-topbar,
  .wall-card,
  .wall-dock,
  .scene-header,
  .prep-card,
  .flow-card,
  .privacy-console > div {
    padding: 10px 12px;
  }

  .wall-card {
    min-height: 70px;
  }

  .wall-card h3 {
    margin-top: 4px;
    font-size: 0.95rem;
  }

  .wall-card p,
  .wall-dock,
  .progress-line,
  .ingredient-row,
  .mini-bars {
    display: none;
  }

  .rhythm-stage,
  .prep-flow,
  .meal-board,
  .routine-ladder,
  .privacy-console {
    min-height: 230px;
    padding: 14px;
  }

  .module-grid {
    grid-template-columns: repeat(5, minmax(0, 1fr));
    gap: 8px;
    margin-top: 14px;
  }

  .module-card {
    min-height: 150px;
    padding: 12px;
  }

  .module-card span {
    width: 22px;
    height: 22px;
    margin-bottom: 18px;
  }

  .module-card h3 {
    font-size: 0.92rem;
  }

  .module-card p,
  .story-list {
    font-size: 0.78rem;
    line-height: 1.32;
  }

  .proof-layout {
    grid-template-columns: minmax(230px, 0.42fr) minmax(0, 0.58fr);
    gap: 18px;
    min-height: calc(100vh - 24px);
    min-height: calc(100svh - 24px);
  }

  .proof-kicker {
    display: none;
  }

  .proof-copy {
    min-height: calc(100vh - var(--safe-top) - 46px);
    min-height: calc(100svh - var(--safe-top) - 46px);
  }

  .proof-device {
    height: calc(100vh - var(--safe-top) - 36px);
    height: calc(100svh - var(--safe-top) - 36px);
    min-height: 260px;
  }

  .proof-step {
    top: 38px;
    max-width: 260px;
  }

  .proof-step p {
    font-size: 0.9rem;
    line-height: 1.42;
  }

  .scene-grid span {
    min-height: 58px;
    padding: 12px;
    font-size: 0.9rem;
  }

  .calendar-lines,
  .routine-bars {
    min-height: 110px;
  }
}

/* ==========================================================================
   SECTION 6 - ADD

   A product showcase, not a chapter. Normal document flow, roughly 1.8
   viewports tall, with one local carousel of THREE states.

   THREE, not four. The fourth was Household Memory, drawn as a constellation
   of labelled nodes joined by hairlines - and a diagram of what a system
   retains about someone's children argues against itself however the copy is
   worded. The feature is still sold: it is a line in the price card and a
   whole beat on /add/. Here the story is before, during and after, which is
   how a person tells a story about a Tuesday, and it stops the payoff being
   buried three clicks in - "it's on the wall" is now the frame she ends on.

   LIGHT, not ink. This used to be the only dark panel on the page, sitting
   between a sunlit living room and a $7.99 card, with graph paper behind it.
   Ink plus lavender-on-teal plus a node graph is the visual grammar of an AI
   infrastructure company, which for this reader is a reason to hesitate.

   The whole visual stage is ONE set of objects. Their position and opacity in
   each state is declared here, on `.add-canvas.is-state-N .thing`, and the
   only thing JS does is swap that class. Two consequences worth keeping:

     1. Objects carry across states instead of being replaced - the email you
        forward in state 0 is the letter that gets read in state 1 and the row
        that lands on the board in state 2.
     2. Rapid clicking is safe for free. A CSS transition retargets from
        wherever it currently is; there is no timeline to kill or stack.

   Positions are percentages of the stage box, so the whole composition scales
   with the panel and nothing needs a resize handler.
   ========================================================================== */

.add-stage {
  position: relative;
  /* The tonal bridge out of Explore's warm cream, through a cooler lavender
     under the panel, and back to warm for Your Story. The panel used to be the
     reason for the cool middle - a near-black card needed a field to sit on.
     It is a white card now, so the lavender is doing a quieter job: separating
     this section from the cream above and below so the card reads as a card. */
  background: linear-gradient(
    180deg,
    #fdfbf7 0%,
    #f6f1fd 16%,
    #efe9fb 46%,
    #f4eff9 74%,
    /* the shared base the closing field is built on - see .yourstory-stage */
    #faf6fb 100%
  );
  /* --safe-top clears the floating header + stage rail, so a rail jump to
     this section lands with the eyebrow visible rather than under the nav.

     The bottom is not spacing, it is the panel's shadow allowance, and it is a
     PIXEL floor rather than a viewport fraction because a shadow's reach is
     measured in pixels. A white card's shadow is far softer than the ink one's
     was, so this no longer has a hard edge to protect - but the floor costs
     nothing and the section still clips. */
  padding: calc(var(--safe-top) + clamp(18px, 5vh, 64px)) 0 max(120px, 10vh);
  padding: calc(var(--safe-top) + clamp(18px, 5svh, 64px)) 0 max(120px, 10svh);
  overflow: hidden;
}

.add-intro {
  max-width: 900px;
}

.add-intro h2 {
  font-size: clamp(2.6rem, 5.2vw, 5.2rem);
  line-height: 1.08;
}

.add-intro .lead {
  max-width: 660px;
  margin-top: 22px;
}

.add-word {
  position: relative;
  color: var(--purple);
  letter-spacing: -0.01em;
}

.add-word::after {
  content: "";
  position: absolute;
  inset: auto 0 0.08em;
  height: 0.1em;
  border-radius: 999px;
  background: linear-gradient(90deg, rgba(124, 58, 237, 0.9), rgba(47, 127, 120, 0.6));
}

/* --- the panel ----------------------------------------------------------- */

.add-showcase-wrap {
  width: min(1240px, calc(100% - 36px));
  margin-top: clamp(34px, 5vh, 62px);
  margin-top: clamp(34px, 5svh, 62px);
}

.add-showcase {
  /* Four accents, one per detail the letter gives up. They are the page's own
     tokens, darkened where a highlighter tint under black text needed it, and
     they are the ONLY thing joining a mark in the letter to its row on the
     card beside it. Colour can do that job; a hairline between two labelled
     boxes cannot do it without also looking like a schematic. */
  --who: #c2410c;
  --what: #6d28d9;
  --when: #1f5e59;
  --where: #a16207;
  --soft: #5f6178;
  --faint: #83859b;
  --hair: rgba(16, 18, 38, 0.1);
  --card: #fff;

  position: relative;
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: clamp(22px, 2.4vw, 34px);
  /* Warm white, with the page's lavender and teal allowed in only as a breath
     at two corners - the same card idiom as .price-card and .love-card, which
     is the point: this should look like it belongs to this website. */
  background:
    radial-gradient(ellipse 58% 62% at 88% 2%, rgba(124, 58, 237, 0.05), transparent 62%),
    radial-gradient(ellipse 52% 58% at 4% 98%, rgba(47, 127, 120, 0.05), transparent 64%),
    var(--surface);
  box-shadow:
    0 34px 74px rgba(23, 35, 38, 0.11),
    0 6px 18px rgba(16, 18, 38, 0.05);
  color: var(--ink);
  padding: clamp(22px, 2.8vw, 38px);
  display: grid;
  gap: clamp(18px, 2.2vw, 26px);
}

.add-showcase > * {
  min-width: 0;
}

.add-showcase__tabs {
  min-width: 0;
  overflow: hidden;
}

/* --- tabs ---------------------------------------------------------------- */

.add-tabs {
  display: flex;
  gap: 4px;
  overflow-x: auto;
  /* Stated, never implied - see .stage-rail. `overflow-x: auto` alone makes
     overflow-y `auto` too, and a one-row rail that can scroll vertically will
     eventually be scrolled vertically by something. */
  overflow-y: hidden;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}

.add-tabs::-webkit-scrollbar {
  display: none;
}

.add-tabs button {
  display: inline-flex;
  align-items: center;
  flex: 0 0 auto;
  position: relative;
  border: 0;
  border-radius: 12px;
  background: transparent;
  color: var(--soft);
  padding: 11px 15px;
  font: inherit;
  font-size: 0.92rem;
  font-weight: 800;
  letter-spacing: 0;
  white-space: nowrap;
  cursor: pointer;
  transition: color 200ms ease, background-color 200ms ease;
}

.add-tabs button:hover {
  color: var(--ink);
  background: rgba(16, 18, 38, 0.04);
}

.add-tabs button[aria-selected="true"] {
  color: var(--ink);
  background: rgba(124, 58, 237, 0.08);
}

.add-tabs button::after {
  content: "";
  position: absolute;
  inset: auto 15px 5px;
  height: 2px;
  border-radius: 999px;
  background: linear-gradient(90deg, var(--purple), var(--teal));
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 260ms cubic-bezier(0.33, 1, 0.68, 1);
}

.add-tabs button[aria-selected="true"]::after {
  transform: scaleX(1);
}

.add-tabs button:focus-visible,
.add-copy:focus-visible {
  outline: 2px solid var(--purple);
  outline-offset: 3px;
}

.add-copy:focus-visible {
  border-radius: 10px;
}

/* --- body: copy + stage --------------------------------------------------- */

.add-showcase__body {
  display: grid;
  grid-template-columns: minmax(0, 0.36fr) minmax(0, 0.64fr);
  gap: clamp(20px, 3vw, 44px);
  align-items: center;
}

.add-showcase__copy {
  position: relative;
  min-height: 168px;
  display: grid;
  align-content: center;
}

.add-copy {
  grid-area: 1 / 1;
  max-width: 34ch;
  opacity: 0;
  transform: translateY(12px);
  transition: opacity 300ms ease, transform 300ms cubic-bezier(0.33, 1, 0.68, 1);
  pointer-events: none;
}

.add-copy.is-active {
  opacity: 1;
  transform: none;
  pointer-events: auto;
}

.add-copy h3 {
  font-size: clamp(1.5rem, 2.3vw, 2.2rem);
  line-height: 1.06;
  color: var(--ink);
}

.add-copy p {
  margin-top: 14px;
  color: var(--soft);
  font-size: clamp(0.95rem, 1.15vw, 1.06rem);
  line-height: 1.6;
}

.add-showcase__visual {
  position: relative;
}

/* ==========================================================================
   THE STAGE

   One box, everything inside absolutely positioned in % of it. Each object
   declares its resting look once, then each `.is-state-N` block says where it
   is (or is not) in that state.

   There is no grid texture on it any more. 44px CAD squares are the surface of
   a schematic, and nothing in a family's week happens on graph paper.
   ========================================================================== */

.add-canvas {
  --card-line: rgba(16, 18, 38, 0.09);
  --ease: cubic-bezier(0.33, 1, 0.68, 1);
  --t: 520ms;

  position: relative;
  /* Fixed ratio, never capped by viewport height. The composition inside is
     positioned in percentages of BOTH axes, so shortening the box does not
     flatten it - it collides the cards. When the panel is taller than the
     viewport can show, addStagePoints() gives it a second scroll stop
     instead, which costs a gesture and costs the layout nothing. */
  aspect-ratio: 16 / 11;
  min-height: 300px;
  border-radius: 18px;
  overflow: hidden;
  background:
    radial-gradient(ellipse 74% 64% at 62% 38%, rgba(124, 58, 237, 0.055), transparent 72%),
    radial-gradient(ellipse 58% 56% at 8% 94%, rgba(47, 127, 120, 0.05), transparent 72%),
    #faf6f0;
  box-shadow: 0 0 0 1px rgba(16, 18, 38, 0.055) inset;
}

.add-canvas > * {
  position: absolute;
  z-index: 2;
  transition:
    opacity var(--t) ease var(--d, 0ms),
    transform var(--t) var(--ease) var(--d, 0ms),
    filter var(--t) ease var(--d, 0ms);
}

/* --- source cards -------------------------------------------------------- */

.add-source {
  width: 30%;
  min-width: 132px;
  border: 1px solid var(--card-line);
  border-radius: 13px;
  background: var(--card);
  padding: 11px 13px;
  box-shadow: 0 10px 26px rgba(23, 35, 38, 0.09);
}

.add-source__kind {
  display: block;
  margin-bottom: 5px;
  color: var(--what);
  font-size: 0.58rem;
  font-weight: 930;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.add-source strong {
  display: block;
  font-size: 0.82rem;
  font-weight: 850;
  line-height: 1.2;
}

.add-source p {
  margin-top: 5px;
  color: var(--soft);
  font-size: 0.72rem;
  line-height: 1.4;
}

.add-source--shot .add-source__kind { color: var(--when); }
.add-source--msg  .add-source__kind { color: var(--who); }
.add-source--note .add-source__kind { color: var(--where); }

.add-shot-bubble {
  border-radius: 10px 10px 10px 3px;
  background: rgba(47, 127, 120, 0.1);
  padding: 8px 10px;
  font-size: 0.74rem;
  font-weight: 750;
  line-height: 1.35;
}

/* resting positions (state 0) */
.add-source--email { left: 3%;  top: 6%; }
.add-source--shot  { left: 6%;  top: 40%; }
.add-source--msg   { left: 2%;  top: 70%; }
.add-source--note  { left: 33%; top: 78%; width: 26%; }

/* --- the phone ----------------------------------------------------------- */

.add-device {
  z-index: 1;
  right: 5%;
  top: 50%;
  width: 40%;
  min-width: 190px;
  transform: translateY(-50%);
  border: 1px solid var(--card-line);
  border-radius: 20px;
  background: var(--card);
  padding: 8px;
  box-shadow:
    0 22px 48px rgba(23, 35, 38, 0.14),
    0 2px 6px rgba(16, 18, 38, 0.05);
}

.add-device__screen {
  border-radius: 14px;
  background: #fcfaf6;
  padding: 12px;
  display: grid;
  gap: 10px;
}

.add-device__head {
  display: flex;
  align-items: flex-start;
  gap: 9px;
}

.add-device__mark {
  display: grid;
  place-items: center;
  width: 24px;
  height: 24px;
  flex: 0 0 auto;
  border-radius: 8px;
  background: linear-gradient(145deg, #8b5cf6, var(--purple));
  color: #fff;
  font-size: 1rem;
  font-weight: 930;
  line-height: 1;
}

.add-device__title {
  font-size: 0.82rem;
  font-weight: 880;
  line-height: 1.2;
}

.add-device__title i {
  display: block;
  margin-top: 3px;
  color: var(--faint);
  font-style: normal;
  font-size: 0.66rem;
  font-weight: 650;
  line-height: 1.3;
}

.add-device__slot {
  position: relative;
  display: grid;
  min-height: 84px;
  border: 1px dashed rgba(16, 18, 38, 0.16);
  border-radius: 11px;
  padding: 10px;
  transition: border-color 400ms ease, background-color 400ms ease;
}

.add-device__placeholder,
.add-device__landed {
  grid-area: 1 / 1;
  transition: opacity 320ms ease;
}

.add-device__placeholder {
  align-self: center;
  color: var(--faint);
  font-size: 0.74rem;
}

.add-device__landed {
  opacity: 0;
}

.add-device__landed strong {
  display: block;
  font-size: 0.78rem;
  font-weight: 850;
  line-height: 1.2;
}

.add-device__landed p {
  margin-top: 4px;
  color: var(--soft);
  font-size: 0.68rem;
  line-height: 1.4;
}

.add-device__status {
  position: relative;
  display: grid;
  min-height: 30px;
}

.add-status {
  grid-area: 1 / 1;
  justify-self: start;
  align-self: center;
  border-radius: 999px;
  background: rgba(124, 58, 237, 0.1);
  padding: 7px 11px;
  color: var(--what);
  font-size: 0.68rem;
  font-weight: 830;
  line-height: 1.25;
  opacity: 0;
  transition: opacity 300ms ease;
}

.add-status--0 {
  background: linear-gradient(135deg, var(--purple), #6d28d9);
  color: #fff;
}

/* --- state 1: the letter, gone over with a highlighter -------------------- */

.add-letter {
  border: 1px solid var(--card-line);
  border-radius: 14px;
  background: var(--card);
  padding: 15px 17px 14px;
  opacity: 0;
  box-shadow: 0 16px 38px rgba(23, 35, 38, 0.1);
}

.add-letter__from {
  display: block;
  margin-bottom: 7px;
  color: var(--faint);
  font-size: 0.62rem;
  font-weight: 870;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.add-letter strong {
  display: block;
  font-size: 0.9rem;
  font-weight: 880;
  line-height: 1.3;
}

.add-letter p {
  margin-top: 9px;
  color: #2c2e44;
  font-size: 0.83rem;
  line-height: 1.8;
}

.add-letter__sign {
  display: block;
  margin-top: 9px;
  color: var(--faint);
  font-size: 0.72rem;
  font-style: italic;
}

/* The sweep. A background-image that grows from 0 to full width reads as a
   pen going across, which is the gesture being claimed - not a box appearing
   around a phrase. Each mark carries its own --d so they land in reading
   order rather than all at once. */
.add-letter mark {
  /* --hl, not currentColor: the gradient and the text are different colours
     here, and driving the ink off the highlighter tinted the words the same
     translucent orange as the paper under them. */
  background-color: transparent;
  background-image: linear-gradient(var(--hl), var(--hl));
  background-repeat: no-repeat;
  background-position: 0 88%;
  background-size: 0% 82%;
  border-radius: 3px;
  padding: 0 1px;
  color: #1a1c30;
  font-weight: 830;
  box-decoration-break: clone;
  -webkit-box-decoration-break: clone;
  transition: background-size 480ms var(--ease) var(--d, 0ms);
}

.add-letter mark.is-who   { --hl: rgba(234, 88, 12, 0.3); }
.add-letter mark.is-what  { --hl: rgba(139, 92, 246, 0.3); }
.add-letter mark.is-when  { --hl: rgba(45, 168, 154, 0.34); }
.add-letter mark.is-where { --hl: rgba(245, 158, 11, 0.34); }

.add-canvas.is-state-1 .add-letter mark { background-size: 100% 82%; }

.add-read {
  border: 1px solid var(--card-line);
  border-radius: 14px;
  background: var(--card);
  padding: 14px 16px;
  opacity: 0;
  box-shadow: 0 16px 38px rgba(23, 35, 38, 0.1);
}

.add-read__head {
  display: block;
  margin-bottom: 11px;
  color: var(--faint);
  font-size: 0.62rem;
  font-weight: 930;
  letter-spacing: 0.09em;
  text-transform: uppercase;
}

.add-read__row {
  position: relative;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 11px 0;
  border-top: 1px solid rgba(16, 18, 38, 0.06);
  border-radius: 10px;
  transform-origin: center;
  transition: background-color 260ms ease, box-shadow 260ms ease;
}

.add-read__row:first-of-type {
  border-top: 0;
  padding-top: 0;
}

.add-read__row b {
  flex: 0 0 auto;
  width: 46px;
  font-size: 0.6rem;
  font-weight: 930;
  letter-spacing: 0.07em;
  text-transform: uppercase;
}

.add-read__row > span {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  min-width: 0;
  font-size: 0.82rem;
  font-weight: 830;
  line-height: 1.25;
}

.add-read__row.is-who   b { color: var(--who); }
.add-read__row.is-what  b { color: var(--what); }
.add-read__row.is-when  b { color: var(--when); }
.add-read__row.is-where b { color: var(--where); }

.add-read__row.is-reading {
  background: rgba(47, 127, 120, 0.055);
  box-shadow: 0 10px 24px rgba(23, 35, 38, 0.08);
}

.add-read__row.is-who.is-reading { background: rgba(234, 88, 12, 0.08); }
.add-read__row.is-what.is-reading { background: rgba(139, 92, 246, 0.08); }
.add-read__row.is-when.is-reading { background: rgba(45, 168, 154, 0.09); }
.add-read__row.is-where.is-reading { background: rgba(245, 158, 11, 0.09); }

.add-avatar {
  display: grid;
  place-items: center;
  width: 19px;
  height: 19px;
  flex: 0 0 auto;
  border-radius: 999px;
  background: linear-gradient(145deg, #ffb694, #f0876a);
  color: #4a1f10;
  font-size: 0.56rem;
  font-weight: 930;
}

/* --- state 2: the wall ---------------------------------------------------- */

.add-board {
  border: 1px solid var(--card-line);
  border-radius: 16px;
  background: var(--card);
  padding: 13px 15px 8px;
  opacity: 0;
  box-shadow: 0 18px 42px rgba(23, 35, 38, 0.11);
}

.add-board__head {
  display: block;
  margin-bottom: 9px;
  color: var(--faint);
  font-size: 0.62rem;
  font-weight: 930;
  letter-spacing: 0.09em;
  text-transform: uppercase;
}

.add-board__row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 11px 0;
  border-top: 1px solid rgba(16, 18, 38, 0.06);
}

.add-board__row b {
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  flex: 0 0 auto;
  border-radius: 10px;
  background: rgba(124, 58, 237, 0.09);
  color: var(--what);
  font-size: 0.64rem;
  font-weight: 930;
  text-transform: uppercase;
}

.add-board__row > span:not(.add-avatar) {
  min-width: 0;
  flex: 1 1 auto;
}

.add-board__row strong {
  display: block;
  font-size: 0.78rem;
  font-weight: 860;
  line-height: 1.2;
}

.add-board__row i {
  display: block;
  margin-top: 3px;
  color: var(--soft);
  font-style: normal;
  font-size: 0.68rem;
}

.add-confirm {
  display: flex;
  align-items: center;
  gap: 11px;
  border: 1px solid rgba(47, 127, 120, 0.28);
  border-radius: 14px;
  background: rgba(47, 127, 120, 0.07);
  padding: 11px 13px;
  opacity: 0;
  box-shadow: 0 14px 32px rgba(23, 35, 38, 0.08);
}

.add-confirm > svg {
  width: 19px;
  height: 19px;
  flex: 0 0 auto;
  color: var(--teal);
}

.add-confirm > span {
  min-width: 0;
  flex: 1 1 auto;
}

.add-confirm strong {
  display: block;
  color: var(--teal-deep);
  font-size: 0.79rem;
  font-weight: 880;
  line-height: 1.25;
}

.add-confirm i {
  display: block;
  margin-top: 3px;
  color: var(--soft);
  font-style: normal;
  font-size: 0.68rem;
  line-height: 1.35;
}

.add-confirm__cta {
  flex: 0 0 auto;
  border-radius: 999px;
  background: var(--teal);
  padding: 8px 14px;
  color: #fff;
  font-size: 0.72rem;
  font-weight: 900;
}

/* the one-shot send gesture: a small stand-in for the email card that travels
   into the Add sheet once, on first view, then gets out of the way */
.add-ghost {
  left: 3%;
  top: 10%;
  width: 30%;
  min-width: 132px;
  border: 1px solid rgba(124, 58, 237, 0.34);
  border-radius: 13px;
  background: #fff;
  padding: 11px 13px;
  opacity: 0;
  pointer-events: none;
  box-shadow: 0 20px 40px rgba(23, 35, 38, 0.18);
  transition: none;
}

.add-ghost strong {
  display: block;
  font-size: 0.8rem;
  font-weight: 850;
}

/* ==========================================================================
   STATE TABLE
   ========================================================================== */

/* --- state 0 - send it over ----------------------------------------------- */

.add-canvas.is-state-0 .add-status--0 { opacity: 1; }

/* The one-shot gesture ends by fading out over the slot, which used to leave
   the sheet still saying "Tell Nestory anything…" - the demo mimed a send and
   then showed nothing arriving. `is-shared` is set on its completion, so the
   email is in the sheet afterwards and beat 1 finishes its own sentence. */
.add-canvas.is-shared.is-state-0 .add-device__placeholder { opacity: 0; }
.add-canvas.is-shared.is-state-0 .add-device__landed { opacity: 1; }
.add-canvas.is-shared.is-state-0 .add-device__slot {
  border-color: rgba(124, 58, 237, 0.3);
  border-style: solid;
  background: rgba(124, 58, 237, 0.04);
}

/* --- state 1 - it reads the details --------------------------------------- */

/* the things you are not following recede rather than vanish */
.add-canvas.is-state-1 .add-source,
.add-canvas.is-state-2 .add-source {
  opacity: 0;
  transform: translateX(-14%) scale(0.94);
}

/* the phone hands over: it is the same email either way, so the sheet gets out
   of the way rather than competing with the letter it turned into */
.add-canvas.is-state-1 .add-device,
.add-canvas.is-state-2 .add-device {
  opacity: 0;
  transform: translateY(-50%) translateX(10%) scale(0.92);
}

.add-canvas.is-state-1 .add-letter,
.add-canvas.is-state-1 .add-read { opacity: 1; }

/* Vertically centred by measurement, not by eye: the letter runs ~40% of the
   stage's height and the card ~36%, so they hang from 30% and 32%. */
.add-letter { left: 3%;  top: 25%; width: 48%; }
.add-read   { left: 55%; top: 29%; width: 42%; }

.add-canvas:not(.is-state-1) .add-letter { transform: translateY(6%) scale(0.97); }
.add-canvas:not(.is-state-1) .add-read   { transform: translate(6%, 6%) scale(0.97); }

/* --- state 2 - it's on the wall -------------------------------------------- */

.add-canvas.is-state-2 .add-board,
.add-canvas.is-state-2 .add-confirm { opacity: 1; }

/* The board is the payoff, so it gets the room: the pair spans ~18%–82% of
   the stage, which leaves roughly equal air above, between and below.

   ONE width for both, because they share a left edge - two stacked cards
   flush left and ragged right read as a mistake, which is what they were:
   each was sized to its own contents and neither was measured against the
   other. 74% is the narrower of the two the confirm bar can hold without its
   sentence wrapping under the tick. */
.add-board   { left: 5%; top: 18%; width: 74%; }
.add-confirm { left: 5%; top: 70%; width: 74%; }

.add-canvas:not(.is-state-2) .add-board,
.add-canvas:not(.is-state-2) .add-confirm {
  transform: translateY(9%) scale(0.96);
}

/* --- Your Story ------------------------------------------------------------ */

/* ==========================================================================
   THE CLOSING FIELD - Your Story + the footer, one ground

   They are siblings (a section and a page landmark), so they cannot share a
   background box. They share a FLAT BASE instead, and each carries its own
   soft radials placed well away from the seam - Your Story's in its upper
   half, the footer's in its lower. Where the two meet there is nothing but
   the base colour on both sides, so the join is invisible without any
   background-attachment trickery that mobile would refuse to honour.
   ========================================================================== */

.yourstory-stage {
  position: relative;
  background:
    /* Seals the TOP into the Add section, which ends on flat #faf6fb. This
       section only ever sealed its bottom, and the blue radial below reaches
       the upper edge at rgb(232,237,249) on a desktop and rgb(234,238,248) on
       a phone - a 17/255 step across the right-hand half of the join, in both
       cases. Nine per cent is enough: the radial is centred at 18%. */
    linear-gradient(180deg, #faf6fb 0%, rgba(250, 246, 251, 0) 9%),
    /* Seals the bottom back to the base. A radial's default sizing is
       farthest-corner, so on a wide short box its stops still carry past the
       lower edge - which left a measurable 4/255 step exactly at the join.
       This layer is height-independent; the radials can be placed freely. */
    linear-gradient(180deg, rgba(250, 246, 251, 0) 48%, #faf6fb 84%),
    radial-gradient(circle at 84% 18%, rgba(181, 213, 245, 0.4), transparent 46%),
    radial-gradient(circle at 10% 30%, rgba(232, 213, 245, 0.46), transparent 48%),
    #faf6fb;
}

/* The section is the last thing before a ~270px footer, so the page bottom
   clamps before its top can reach the viewport: at rest its eyebrow sat
   behind the nav island at 1440x900 and off the top entirely at 1280x720 and
   on phones. --safe-top is the same allowance every other section uses; the
   extra is so "Your Story" lands clear of the island rather than against it. */
/* The header reserve is on the SECTION, not on the block inside it.

   landingTop() asks the element it is landing whether it already clears the
   header; with the padding one level down, .yourstory-stage looked like a box
   with no top padding, so the landing lifted it by a header's height and left
   a header-tall band of the section above showing across the top. Identical to
   the bug the Explore stage had - the fix has to live on whatever is actually
   being landed. */
.yourstory-stage {
  padding-top: calc(var(--safe-top) + clamp(12px, 3vh, 40px));
  padding-top: calc(var(--safe-top) + clamp(12px, 3svh, 40px));
  scroll-margin-top: 0;
}

.yourstory-stage .explore-final-cta {
  padding-top: 0;
  padding-bottom: clamp(40px, 5vh, 64px);
  padding-bottom: clamp(40px, 5svh, 64px);
}


/* --- "Works with what you already use" ----------------------------------
   A strip, not a section: it answers one objection (do I have to move
   everything?) at the moment the price lands, and it answers it in a line.
   Given a page of its own it would read as a feature list, which it is not. */

.works-with {
  grid-column: 1 / -1;
  margin-top: clamp(16px, 2.6vh, 34px);
  padding-top: clamp(14px, 1.8vh, 22px);
  border-top: 1px solid rgba(16, 18, 38, 0.08);
  text-align: center;
}

.works-with-label {
  color: var(--muted);
  font-size: 0.68rem;
  font-weight: 900;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

.works-with-list {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 10px clamp(20px, 3vw, 46px);
  margin: clamp(12px, 1.8vh, 18px) 0 0;
  padding: 0;
  list-style: none;
  color: var(--ink);
  font-size: clamp(0.9rem, 1.05vw, 1.05rem);
  font-weight: 700;
}

/* The sprite is defined once and never drawn. display:none would stop the
   <use> references resolving in some engines, so it is sized to nothing
   instead. */
.works-with-defs {
  position: absolute;
  width: 0;
  height: 0;
  overflow: hidden;
}

.works-with-list li {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  opacity: 0.68;
  transition: opacity 180ms ease, color 180ms ease;
}

.works-with-list li:hover {
  opacity: 1;
  color: var(--teal-deep);
}

/* One optical size for both kinds of mark. The vendor logos are already
   trimmed and squared in assets/works/, so a single box lines them up; the
   drawn glyph gets the same box so Google Tasks does not sit smaller than its
   neighbours. */
.works-with-list img,
.works-with-list .wi-drawn {
  width: 20px;
  height: 20px;
  flex: 0 0 auto;
}

.works-with-list img {
  object-fit: contain;
}

.works-with-list .wi-drawn {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.7;
  stroke-linecap: round;
  stroke-linejoin: round;
  opacity: 0.72;
}

@media (max-width: 760px) {
  .works-with-list {
    gap: 10px 16px;
    font-size: 0.86rem;
  }

  .works-with-list img,
  .works-with-list .wi-drawn {
    width: 17px;
    height: 17px;
  }
}

/* --- Your Story: the closing card ---------------------------------------
   Two columns rather than the centred stack the rest of .explore-final-cta
   uses: the closing ask is the one place on the page where the price, what
   is in it and the way out all have to be readable at once, and a centred
   column of six ticks reads as a list of demands. The card is the same one
   the pricing page shows, so a reader who has seen one recognises the other. */

.yourstory-stage .explore-final-cta-inner {
  grid-template-columns: minmax(0, 1fr) minmax(0, 420px);
  align-items: center;
  justify-items: stretch;
  gap: clamp(28px, 5vw, 76px);
  text-align: left;
}

.yourstory-copy {
  display: grid;
  justify-items: start;
  gap: 16px;
}

.yourstory-stage .explore-final-cta h2 {
  max-width: 14ch;
  font-size: clamp(2.2rem, 4vw, 3.8rem);
}

.yourstory-stage .explore-final-cta .lead {
  max-width: 48ch;
}

.pricing-note {
  max-width: 44ch;
  color: var(--muted);
  font-size: 0.92rem;
  line-height: 1.6;
}

.price-card {
  display: grid;
  gap: 14px;
  justify-items: stretch;
  padding: clamp(24px, 2.4vw, 34px);
  border: 1px solid var(--line);
  border-radius: 28px;
  background: #fff;
  box-shadow:
    0 34px 74px rgba(23, 35, 38, 0.11),
    0 6px 18px rgba(16, 18, 38, 0.05);
}

.price-name {
  color: var(--muted);
  font-size: 0.74rem;
  font-weight: 900;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.price-figure {
  display: flex;
  align-items: baseline;
  color: var(--muted);
  font-size: 1.05rem;
  font-weight: 800;
  line-height: 1.25;
  letter-spacing: 0;
}

.price-then {
  margin-right: 0.35em;
}

.price-cur {
  font-size: 1em;
  font-weight: 900;
}

.price-per {
  color: inherit;
  font-size: 0.95em;
  font-weight: 800;
}

.price-trial {
  color: var(--ink);
  font-size: clamp(2.15rem, 3.1vw, 3.3rem);
  font-weight: 930;
  line-height: 0.96;
  letter-spacing: -0.01em;
}

.ticks {
  display: grid;
  gap: 11px;
  margin: 6px 0 4px;
  padding: 0;
  list-style: none;
}

.ticks li {
  position: relative;
  padding-left: 28px;
  color: var(--ink);
  font-size: 0.95rem;
  line-height: 1.45;
}

.ticks li::before {
  content: "";
  position: absolute;
  top: 0.18em;
  left: 0;
  width: 17px;
  height: 17px;
  border-radius: 999px;
  background:
    /* The tick itself, drawn rather than typed: a text glyph would inherit the
       list's font and sit differently in every fallback face. */
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M2.6 6.3l2.2 2.2 4.6-4.8' fill='none' stroke='%231f5e59' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / 11px 11px no-repeat,
    rgba(47, 127, 120, 0.14);
}

.price-card .store-buttons {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: center;
  gap: 10px;
  justify-self: center;
  margin-top: 6px;
}

.price-card .store-button img {
  height: 42px;
}

.price-card .store-button--disabled {
  align-items: center;
  flex-direction: column;
  gap: 3px;
  cursor: default;
}

.price-card .store-button--disabled img {
  filter: grayscale(1);
  opacity: 0.46;
}

.price-card .store-button-note {
  color: var(--muted);
  font-size: 0.56rem;
  font-variant-caps: all-small-caps;
  font-weight: 800;
  letter-spacing: 0.08em;
  line-height: 1;
  text-transform: lowercase;
}

/* One column under the width where the card and the copy stop both having a
   comfortable measure. */
@media (max-width: 1000px) {
  .yourstory-stage .explore-final-cta-inner {
    grid-template-columns: minmax(0, 1fr);
    justify-items: center;
    text-align: center;
  }

  .yourstory-copy {
    justify-items: center;
  }

  .yourstory-stage .explore-final-cta h2 {
    max-width: 18ch;
  }

  .price-card {
    width: min(100%, 420px);
    text-align: left;
  }

  .price-figure {
    justify-content: flex-start;
  }
}

/* ==========================================================================
   ADD - responsive
   ========================================================================== */

@media (max-width: 1100px) {
  .add-showcase__body {
    grid-template-columns: 1fr;
    gap: 18px;
  }

  /* Mobile order (and narrow tablets): label rail, visual, then copy. */
  .add-showcase__copy   { order: 2; min-height: 0; }

  /* CAPPED, and this is the whole fix for the tablet. One column hands the
     stage the panel's full 929px, but everything inside it is sized in px - so a wider stage does not make a bigger composition, it makes the same
     composition marooned in more emptiness. Capping the stage near its
     desktop width keeps the two in proportion at every size. */
  .add-showcase__visual {
    order: 1;
    width: 100%;
    max-width: 720px;
    margin-inline: auto;
  }

  .add-copy { max-width: 46ch; }

  .add-canvas { min-height: 0; }
}

@media (max-width: 760px) {
  /* The bottom padding is the dark panel's shadow allowance, not spacing.
     Whatever of the shadow does not fit inside it is cut off by the section's
     own overflow:hidden, and cut against a dark panel means cut while it is
     still strong - measured, a 23-31/255 step straight across the join into
     Your Story. Keep it comfortably longer than the shadow's reach below. */
  .add-stage {
    padding: calc(var(--safe-top) + clamp(12px, 3vh, 32px)) 0 clamp(68px, 8vh, 96px);
    padding: calc(var(--safe-top) + clamp(12px, 3svh, 32px)) 0 clamp(68px, 8svh, 96px);
  }

  /* ...and the shadow cut to that room - the right size for a panel that is
     388px wide here rather than 900. */
  .add-showcase {
    box-shadow:
      0 14px 30px rgba(23, 35, 38, 0.1),
      0 2px 6px rgba(16, 18, 38, 0.04);
  }

  .add-showcase-wrap {
    width: calc(100% - 22px);
  }

  .add-showcase {
    border-radius: 20px;
    padding: 14px 12px 15px;
    gap: 14px;
  }

  /* Full labels stay - the rail scrolls rather than truncating them. */
  .add-tabs {
    margin: 0 -14px;
    padding: 0 14px;
  }

  .add-tabs button {
    padding: 9px 12px;
    font-size: 0.84rem;
  }

  .add-tabs button::after {
    inset: auto 12px 3px;
  }

  /* The stage turns portrait. A phone is half as wide as a laptop and the
     composition does not survive being squeezed sideways: everything stacks
     instead, sources above the sheet rather than beside it. */
  .add-canvas {
    aspect-ratio: 5 / 6;
    border-radius: 14px;
  }

  .add-canvas::before { background-size: 34px 34px; }

  .add-source {
    width: 56%;
    min-width: 0;
    padding: 9px 10px;
  }

  .add-source p { display: none; }

  /* one line, so three sources still fit above the sheet */
  .add-shot-bubble {
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
    font-size: 0.7rem;
  }

  .add-source--email { left: 4%; top: 3%; }
  .add-source--shot  { left: 4%; top: 17%; }
  .add-source--msg   { left: 4%; top: 34%; }
  .add-source--note  { display: none; }

  .add-device {
    right: auto;
    left: 18%;
    top: 72%;
    width: 64%;
    min-width: 0;
  }

  .add-device__title i { display: none; }
  .add-device__slot { min-height: 58px; }
  .add-device__landed p { display: none; }
  .add-status { font-size: 0.64rem; padding: 6px 9px; }

  /* sources sit above the sheet now, so they recede downward into it */
  .add-canvas.is-state-1 .add-source,
  .add-canvas.is-state-2 .add-source {
    transform: translate(32%, 570%) scale(0.7);
  }

  .add-ghost {
    left: 4%;
    top: 3%;
    width: 56%;
    min-width: 0;
  }

  /* The letter and the card it fills in stack, because side by side at 342px
     each would be ~160px wide and the letter has to stay readable - it is the
     whole point of the frame that you can see the words being marked. */
  .add-letter {
    left: 4%;
    top: 4%;
    width: 92%;
    padding: 12px 13px 11px;
  }

  .add-letter strong { font-size: 0.8rem; }
  .add-letter p { margin-top: 7px; font-size: 0.74rem; line-height: 1.7; }
  .add-letter__sign { display: none; }

  .add-read {
    left: 4%;
    top: 58%;
    width: 92%;
    padding: 11px 13px;
  }

  .add-read__head { margin-bottom: 8px; }
  .add-read__row { padding: 5px 0; }

  .add-board {
    left: 4%;
    top: 12%;
    width: 92%;
    padding: 11px 13px 6px;
  }

  .add-board__row { padding: 7px 0; }
  .add-board__row b { width: 28px; height: 28px; }

  /* A grid, not a wrapping flex row: wrapping put the tick on a line of its
     own above the sentence it belongs to. Tick and text share row one, the
     button takes row two under the text. */
  .add-confirm {
    left: 4%;
    top: 56%;
    width: 92%;
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    align-items: start;
    gap: 9px 10px;
    padding: 11px 12px;
  }

  .add-confirm__cta {
    grid-column: 2;
    justify-self: start;
  }

  .add-confirm strong { font-size: 0.74rem; }
  .add-confirm i { font-size: 0.64rem; }
  .add-confirm__cta { font-size: 0.68rem; padding: 7px 12px; }

  .add-copy p { margin-top: 10px; }
}

@media (max-width: 400px) {
  .add-source { width: 62%; }
  .add-device { left: 14%; width: 72%; }
  .add-canvas.is-state-1 .add-source,
  .add-canvas.is-state-2 .add-source {
    transform: translate(28%, 560%) scale(0.7);
  }
  .add-ghost { width: 62%; }
}

/* ==========================================================================
   ADD - reduced motion

   The carousel stays fully functional; only the movement goes. Objects still
   need their per-state transforms (that is their layout), so the transform is
   applied instantly and only opacity crosses over.
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  .add-canvas > *,
  .add-copy,
  .add-device__slot,
  .add-device__placeholder,
  .add-device__landed,
  .add-status,
  .add-tabs button,
  .add-tabs button::after {
    transition: opacity 180ms linear !important;
  }

  /* The highlighter sweep is movement, so it goes; the marks are still the
     thing that ties a phrase to its row, so they arrive at full width. */
  .add-letter mark {
    background-size: 100% 82% !important;
    transition: none !important;
  }

  .add-copy {
    transform: none;
  }

  .add-tabs button::after {
    transform: scaleX(1);
    opacity: 0;
  }

  .add-tabs button[aria-selected="true"]::after {
    opacity: 1;
  }

  .add-ghost {
    display: none;
  }
}


/* ==========================================================================
   REGION B - THE MOCK FRAMES SIZE TO THEIR CONTENTS

   Both product frames set a fixed aspect-ratio over contents that do not
   shrink, with overflow:hidden. That is a clip, not a layout, and it was
   cutting content off at every viewport - not only small ones:

     hero mock      344px -145px   393px -115px   1024x768 -64px   1280 -15px
     explore wall   344px -126px   393px  -75px   768px    -31px
     explore meals  344px -267px   393px -232px   768px    -15px

   Letting the contents set the height keeps each frame the shape it was drawn
   to be - the interiors are already laid out to roughly 16:11 - while removing
   the clip. Deliberately not inside a media query: the ratio was wrong at
   every width, desktop included.
   ========================================================================== */

/* .hero-main-screen was here until its drawn mock became the app's own tab.
   What this rule undoes is a fixed ratio clipping contents that would not
   shrink, and the hero has no contents any more: a screenshot and a live tab,
   both absolutely positioned, both 1284x883. Nothing in there can be clipped,
   so nothing needs the ratio dropped - and dropping it would leave the box with
   no height at all to give them. */
.explore-screen {
  aspect-ratio: auto;
  height: auto;
}

/* A real screenshot IS the interior, so it has to be in flow. The rule above
   drops .explore-screen's aspect-ratio because the drawn mock set its own
   height from its stacked rows; an absolutely-positioned image has no height
   to give and collapsed the screen to its 24px of padding. The shot is already
   16:11, so width:100% + height:auto reproduces the intended shape exactly and
   nothing is cropped. Overrides the inset:0 block that declares it.

   The HERO does not need this and is not in it - see the note on
   .hero-main-screen. */
.explore-screenshot {
  position: static;
  display: block;
  width: 100%;
  height: auto;
}

/* ==========================================================================
   SECTION 1 - PRODUCT HERO (mobile / portrait)

   Same compound query as the Explore stage, so the whole page changes layout
   mode at one place.
   ========================================================================== */

@media (max-width: 760px), (orientation: portrait) and (max-width: 1024px) {
  /* The headline is sized to leave room for the wall mock, not to fill the
     screen on its own: at 11vw it took five lines and pushed the tablet - the
     one thing on this screen that shows what Nestory IS - off the fold, so the
     first thing a phone saw was a page it had to scroll before anything
     happened. */
  .product-hero {
    --hero-h1: clamp(1.8rem, 7.5vw, 2.6rem);
    --tableau-h: auto;
    --main-w: 100%;
  }

  .hero-scroll-next {
    position: static;
    transform: none;
    margin: 0 auto;
    padding: 0 4px;
  }

  /* FLEX, NOT GRID: one column, three blocks, in order.

     The badge lives INSIDE .product-hero-details and flows inline after the
     lead, so there is nothing here that needs ordering across two levels - which is what the earlier `display: contents` version was for, and it
     brought two bugs with it. Ordinary boxes now.

     align-items: stretch, explicitly. The base rule for this wrap is a GRID
     with `align-items: center`, which is the right thing for two columns and
     the wrong thing the moment it becomes a column of one: flex reads it as
     the cross axis, shrink-wraps every child and centres it. The headline
     came out 613px wide and centred while the lead underneath stayed 798px
     and full-width - a page that looked, correctly, like it could not decide
     whether it was centred or left-aligned. */
  .product-hero-wrap {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 10px;
    min-height: 0;
  }

  .product-hero-copy { order: 1; }
  .product-hero-details { order: 2; }
  .hero-tableau { order: 3; }

  /* The section's own bottom padding, which was the last thing pushing this
     past the fold and the hardest to see: the TABLET was ending 26-38px clear
     of the bottom while the SECTION still overran by 8-20px, because the
     padding under it was 40-50px of nothing. The mock's shadow needs a few
     pixels and nothing else does. */
  .snap-panel.product-hero {
    padding-bottom: 10px;
  }

  /* The scroll cue goes on a phone, the same way it already goes in
     landscape. It is 46px - almost exactly the amount by which the section
     was overrunning the screen - and it is the cheapest 46px here: it tells a
     reader to do the thing a phone reader was always going to do anyway,
     while the tablet it was pushing off the bottom is the whole argument. */
  .hero-scroll-next {
    display: none;
  }

  /* THE BADGE SITS WITH THE LEAD.

     It had a row of its own, centred, which cost about 60px of vertical space
     on the one axis this layout has none of - and bought nothing, because a
     store badge is not a heading and does not need its own line. Keeping it
     with the lead hands those 60px to the tablet.

     The trust line goes with it. It sat between the two and would have pushed
     the badge onto a line of its own again; it is also, by the admission of
     the rule that already hides it on short screens, the one piece of copy
     here that repeats rather than adds. */
  /* A ROW, NOT AN INLINE RUN.

     `display: inline` on the lead with an inline-flex badge after it does put
     the two in the same line box, and it works right up until the text fills
     the width - at which point the badge has nowhere to go on the last line,
     wraps, and lands alone against the left margin looking like a mistake.
     That is not an edge case: at 834px the lead is one line ending three
     characters short of the right edge.

     As a flex row the lead gives up the room instead of the badge going
     looking for it, and `align-items: flex-end` puts the badge level with the
     LAST line of the text rather than the first, which is the line it belongs
     to. flex-wrap keeps the phone honest: below about 520px there is no room
     for both, and the badge takes its own line directly under the sentence
     rather than being squeezed. */
  .product-hero-details {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    column-gap: 14px;
    row-gap: 6px;
  }

  .product-hero-details .lead {
    flex: 1 1 320px;
    margin-bottom: 0;
  }

  /* It sat between the lead and the badge and would have pushed the badge off
     the row. It is also the one line of copy here that repeats rather than
     adds. */
  .product-hero-details .trust-line {
    display: none;
  }

  .product-hero-details .hero-actions {
    flex: 0 0 auto;
    margin: 0;
  }

  /* Every value below is paying for the same thing: the wall mock AND "See
     how Nestory works" on the first screen. The mock is content-driven, so
     the room has to come from the space around and inside it. */
  .product-hero {
    /* The floating header island ends at ~62px. 56 left the eyebrow 6px under
       it, close enough to read as a collision; 72 spent a line of slack the
       mock needed. 68 cleared the island and the rest came from the gaps.

       84 now, because the eyebrow is gone on a phone (see the rule above) and
       it was doing two jobs: naming the page, and standing between the header
       island and the headline. Only the first was worth cutting - with it
       gone, "A home for everything" started one line under the hamburger and
       read as part of the chrome. The 16px goes into the overhead constant
       the tablet is sized from, further down, so it is taken from the bezel
       and not from the fold.

       Plus the announcement bar while it is up - it is fixed, so it covers
       this padding rather than pushing it. */
    padding-top: calc(84px + var(--announce-h));
    padding-bottom: 8px;
  }

  .hero-main-topbar {
    padding: 10px 12px;
  }

  .hero-main-body {
    gap: 10px;
    padding: 10px 12px;
  }

  .hero-scroll-next {
    margin-top: 4px;
  }

  .product-hero-copy {
    max-width: 100%;
  }

  .product-hero-details .lead {
    max-width: 100%;
    margin-top: 0;
  }

  /* --- the store badge on a phone ---------------------------------------
     It sat between the lead and the tablet, at 38px, and it was shrunk to
     that size for one reason: anything bigger in that slot read as the
     subject of the page and pushed the mock - the thing that shows what
     Nestory IS - down a screen already fighting for the fold.

     It is under the tablet now (see the order sequence at the top of this
     block), which settles the argument rather than trading against it. In
     that position it cannot compete with the mock for the fold, because the
     mock has already had it; the reader meets the picture, then the way in.
     So the 38px was paying for a problem that no longer exists, and the badge
     goes to 44px. It went to 54 first and that was too far: alone on its line
     under a 340px-wide tablet, 54 stopped reading as a badge and started
     reading as a banner. 44 is the size at which it is obviously the thing to
     tap without being the thing you look at.

     The cost is honest and worth stating: the badge is now below the fold on
     most phones. That is the trade - a tap target nobody sees until they have
     seen the product, instead of one they see before they know what it is.

     It still reveals with the mock - see the closing-beat position in
     initProductHero. */
  .product-hero-details .hero-actions {
    justify-content: center;
    margin-top: 2px;   /* the flex gap above is already 10px */
    margin-bottom: 2px;
  }

  /* 28px, which is roughly the cap height of the lead's own line. Bigger and
     it stops sharing the line and starts interrupting it - the badge was 34
     and sat a clear head above "…in one shared place." */
  .product-hero-details .hero-actions .store-button img {
    height: 28px;
  }

  /* The wall mock alone - or, above 846px, the wall mock with the phone
     standing ON it, which costs this column nothing.

     The grocery card and the chores card are cut here and stay cut: they were
     a second and third screenful of supporting detail in front of "See how
     Nestory works", so the one call to action on the page sat two scrolls
     below the fold. Both get a proper turn in the Explore tour. The capture
     phone used to be cut with them, for the same reason - it was a THIRD
     object in a one-column layout. It is not one any more; whether it is drawn
     is now the bay's own query. */
  .hero-tableau {
    display: grid;
    grid-template-areas: "main";
    grid-template-columns: 1fr;
    gap: 14px;
    /* Back to the column's full width, undoing the composition-width rule at
       the top of this file.

       That rule sizes this box from `--main-w`, and down here `--main-w` is
       itself a PERCENTAGE - of this box. Left in place the two define each
       other, and a browser handed that cycle does not error, it picks
       something: 567px for a tablet that had just been told to be 410. The
       rule up top exists to stop the composition being wider than the tablet
       it is made of, and here the tablet IS the column, so there is nothing
       for it to do anyway. */
    width: 100%;
  }

  /* `.hero-product { position: static }` was here, to undo the absolute
     positioning of the four satellites this tableau used to orbit. There is
     one satellite left and it is no longer a sibling - it lives in
     .hero-phone-bay inside the tablet, which is positioned FROM
     .hero-product-main, so making that box static would have thrown the bay
     out to the section. .hero-product-main's own `position: relative` already
     wins over `.hero-product` on specificity and order; nothing else needs
     saying here. */

  .hero-product-main {
    grid-area: main;
    /* THE HEIGHT BUDGET. 100% of the column is 362px on a 390px phone, and at
       838/1214 that is a 525px-tall bezel - which, under the headline, the
       lead and the badge, runs off the bottom of the screen.

       Capped by the viewport instead, so the whole device is visible with its
       bottom rail: that rail is the thing showing there are four tabs, and
       cropping it is cropping the point. 52svh is what is left once the copy
       above has taken its share; the width follows from the ratio. */
    /* WHAT IS LEFT, not a fraction of the screen.

       This was 46svh, then 52, then 58, and each number was right for the
       handset it was measured on and wrong for the next one - because the
       thing above the tablet is not a percentage of the screen, it is a
       roughly fixed 400px of announcement bar, header, headline and lead. A
       fraction therefore under-fills tall phones and overflows short ones,
       which is exactly the pattern the sweep kept reporting: 14px over at
       428x926 and 60px over at 360x640, from one rule.

       Subtracting that overhead instead makes the tablet as big as the screen
       allows, at every height, and takes the tuning out of it. min() with 100%
       so a wide short phone is still bounded by its column. */
    width: min(100%, calc((100vh - 336px) * 838 / 1214));
    width: min(100%, calc((100svh - 336px) * 838 / 1214));
    margin-inline: auto;
    /* 15px of air between the badge row and the top of the bezel. The gap the
       wrap already provides reads as tight once the tablet is the widest thing
       on the screen, and the fold sweep has more than 15px of slack under the
       device on every portrait size it covers. */
    margin-top: 15px;
    transform: none;
  }

  /* The mock's shadow, cut to the room a phone has under it.

     0 46px 90px reaches ~136px below the frame. On a phone the frame is ~90px
     off the bottom of the section, and the section clips - so a third of that
     shadow was being cut off at the seam, at a strength that still read as a
     step against the flat colour the next section opens on. It is also simply
     over-scaled here: that blur was drawn for a 560px mock on a desktop, not a
     380px one on a phone. 22/44 reaches ~66px and lands inside. */
  .hero-main-frame {
    box-shadow:
      0 20px 40px rgba(23, 35, 38, 0.18),
      0 6px 16px rgba(16, 18, 38, 0.09);
  }

  /* The wall mock's two columns refuse to shrink below their min-content, and
     a flex/grid child's automatic minimum size BEATS the width on its
     ancestors: the 432px the topbar and body wanted travelled all the way up
     and made .product-hero-wrap 404px wide inside a 390px screen. The section
     clips, so it did not show as a page-level scrollbar - it showed as the
     headline, the lead and the right-hand side of the tablet all running off
     the edge. min-width:0 is what lets the mock be as narrow as the phone. */
  .product-hero-copy,
  .product-hero-details,
  .hero-tableau,
  .hero-product-main,
  .hero-main-topbar,
  .hero-main-body,
  .hero-main-col {
    min-width: 0;
  }

}

/* Short phones (360x780 and down) are still 59px over after the compaction
   above. The trust line is what goes: it repeats the hero's claim rather than
   adding a new object to look at - cheaper to lose than the mock or the one
   call to action. */
@media (max-width: 760px) and (max-height: 900px) {
  .product-hero-details .trust-line {
    display: none;
  }
}

/* Shorter still (412x732 - an Android with a tall browser chrome). Everything
   here is a size, not a deletion: past the trust line there is nothing left on
   this screen that repeats itself, so the remaining 51px comes out of the
   type and the gaps rather than out of what the screen says. */
@media (max-width: 760px) and (max-height: 760px) {
  .product-hero {
    --hero-h1: clamp(1.65rem, 6.8vw, 2.2rem);
    padding-top: 64px;
  }

  .product-hero-wrap {
    gap: 8px;
  }

  .product-hero-details .lead {
    font-size: 0.96rem;
    line-height: 1.42;
  }

  /* Shorter than the block above, but not back to 38: the badge is below the
     tablet here too, so the pixels it costs are pixels past the fold on a
     screen whose fold was already spent. */
  .product-hero-details .hero-actions .store-button img {
    height: 26px;
  }

  .product-hero {
    padding-bottom: 0;
  }

  .hero-scroll-next {
    margin-top: 0;
  }

  .hero-main-topbar,
  .hero-main-body {
    padding: 7px 9px;
  }

  .product-hero {
    padding-top: calc(76px + var(--announce-h));
  }

  /* padding-bottom went to 0 above, so all the room the mock's shadow has left
     is the scroll cue's own height - about 42px. Anything longer is cut at the
     section's edge and shows as a step into the next one. */
  .hero-main-frame {
    box-shadow:
      0 12px 26px rgba(23, 35, 38, 0.18),
      0 4px 12px rgba(16, 18, 38, 0.09);
  }
}

/* The lead goes on genuinely short screens only.

   This was 700px, and 700 quietly covered most real phones: an iPhone SE is
   667, and an Android with its URL bar showing reports a layout height well
   under 700 even on a 915px handset. So the pill list that says what Nestory
   actually holds - events, routines, appointments, chores, meals, groceries,
   reminders, alarms and messages - was missing on the devices most likely to
   see this page first, while the desktop kept it.

   620 because the arithmetic changed. The rule was bought when the store
   badge sat between the lead and the mock and everything above the fold was
   competing; the badge is below the tablet now and no longer in that fight,
   and it came down from 54 to 44 besides. What is left to protect is the
   mock, and at 620 and under it is still the mock that wins. Past that there
   is nothing to take that is not the mock or the CTA. */
@media (max-width: 760px) and (max-height: 620px) {
  .product-hero-details .lead {
    display: none;
  }
}

/* ==========================================================================
   REGION B - LEGIBILITY, TOUCH TARGETS, UNPINNED FALLBACK

   Applies to the hero, Williams video, Explore, Add, the header rail and the
   footer.
   ========================================================================== */

/* --- 12px floor on anything carrying real words -------------------------
   The product mocks are the pitch; at 9-11px they were unreadable on the
   device most visitors hold.

   The hero's and Explore's entries are gone from this list because the mocks
   they governed are gone: both sections show the app's own tabs now, at the
   app's own type sizes, and a floor stated in page pixels cannot reach inside
   a stage that is scaled by a transform anyway. What is left is Add, which is
   still drawn in page pixels and still needs it. */
@media (max-width: 760px), (orientation: portrait) and (max-width: 1024px) {
  .add-source__kind,
  .add-shot-bubble,
  .add-device__placeholder,
  .add-status {
    font-size: 0.75rem;
  }

  .stage-rail :is(button, a) {
    font-size: 0.75rem;
  }
}

/* --- 44px touch targets -------------------------------------------------
   Coarse pointers only, so the visual size of each control is unchanged on a
   mouse. The rail pill, the Add tabs and the Add arrow all measured 20-38px. */
@media (pointer: coarse) {
  .stage-rail :is(button, a) {
    min-height: 44px;
    padding-block: 0;
    display: inline-flex;
    align-items: center;
  }

  .add-tabs button {
    min-height: 44px;
  }

  .footer a:not(.brand) {
    min-height: 44px;
    display: flex;
    align-items: center;
    margin-top: 0;
  }
}

/* --- Explore without a pin ----------------------------------------------
   The desktop layout stacks all five chapters in one absolutely positioned
   box and reveals them by scrub. With reduced motion the pin never runs, so
   on a wide landscape window all five landed on top of one another and four
   of the five visuals stayed opacity:0 for the session. When the JS cannot
   pin, it sets .is-unpinned and the chapters flow normally instead. */
.explore-stage.is-unpinned .explore-showcase {
  display: block;
  height: auto;
}


.explore-stage.is-unpinned .explore-head {
  padding-bottom: 28px;
}

.explore-stage.is-unpinned .explore-track {
  grid-template-columns: minmax(0, 1fr);
  align-items: start;
  min-height: 0;
}

.explore-stage.is-unpinned .explore-copy-track {
  position: static;
  align-self: auto;
  min-height: 0;
}

.explore-stage.is-unpinned .explore-chapter {
  position: static;
  inset: auto;
  min-height: 0;
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transform: none;
}

.explore-stage.is-unpinned .explore-product-stage {
  position: static;
  align-self: auto;
  grid-template-rows: auto;
}

/* Same reason as the mobile block: no pin means no one-screen box, so no
   height budget for the frame's width cap to be solved against. */
.explore-stage.is-unpinned .explore-visual-stack {
  container-type: normal;
  align-content: normal;
}

.explore-stage.is-unpinned .explore-visual-stack .explore-frame {
  width: auto;
}

.explore-stage.is-unpinned .explore-visual {
  position: relative;
  grid-area: auto;
  opacity: 1;
  visibility: visible;
  transform: none;
  transition: none;
  pointer-events: auto;
}

/* --- Each feature keeps its own picture ---------------------------------
   On mobile the JS moves every .explore-visual inside the .explore-chapter it
   illustrates, so the five features read as five blocks instead of five
   paragraphs followed by one orphaned mock and a tab strip. */
.explore-chapter > .explore-visual {
  display: block !important;
  grid-area: auto;
  position: relative;
  opacity: 1;
  visibility: visible;
  transform: none;
  transition: none;
  pointer-events: auto;
  margin-top: 22px;
}

.explore-stage.is-paired .explore-nav,
.explore-stage.is-paired .explore-visual-stack {
  display: none;
}

.explore-stage.is-unpinned:not(.is-swipe) .explore-showcase {
  display: grid;
  grid-template-rows: minmax(0, 1fr);
  height: 100vh;
  height: 100svh;
  padding: calc(var(--safe-top) + 2vh) 0 clamp(18px, 3vh, 38px);
  scroll-margin-top: 0;
}

.explore-stage.is-unpinned:not(.is-swipe) .explore-track {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  align-items: center;
  min-height: 0;
}

.explore-stage.is-unpinned:not(.is-swipe) .explore-product-stage {
  display: none;
}

.explore-stage.is-unpinned:not(.is-swipe) .explore-copy-track {
  position: relative;
  display: block;
  align-self: stretch;
  min-height: 0;
}

.explore-stage.is-unpinned:not(.is-swipe) .explore-chapter {
  position: absolute;
  inset: 0;
  display: grid;
  grid-template-columns: minmax(280px, 0.68fr) minmax(460px, 1.32fr);
  align-items: center;
  column-gap: clamp(34px, 5vw, 78px);
  min-height: 0;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transform: translateX(calc(var(--explore-slide-x, 0px) * -1));
  transition: opacity 260ms ease, transform 260ms cubic-bezier(0.22, 1, 0.36, 1), visibility 260ms ease;
}

.explore-stage.is-unpinned:not(.is-swipe) .explore-chapter.is-active {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transform: none;
}

.explore-stage.is-unpinned:not(.is-swipe) .explore-chapter > :not(.explore-visual) {
  grid-column: 1;
}

.explore-stage.is-unpinned:not(.is-swipe) .explore-chapter > .explore-visual {
  grid-column: 2;
  grid-row: 1 / span 5;
  align-self: center;
  margin-top: 0;
}

.explore-stage.is-unpinned:not(.is-swipe) .explore-frame {
  width: min(100%, 820px);
  margin-left: auto;
}

.explore-arrows {
  display: none;
}

.explore-stage.is-unpinned:not(.is-swipe) .explore-arrows {
  position: fixed;
  inset: 50% 0 auto;
  z-index: 8;
  display: flex;
  justify-content: space-between;
  width: min(1820px, calc(100% - 42px));
  margin: 0 auto;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transform: translateY(-50%);
  transition: opacity 180ms ease, visibility 180ms ease;
}

.explore-stage.is-unpinned:not(.is-swipe).is-arrow-active .explore-arrows {
  opacity: 1;
  visibility: visible;
}

.explore-arrow {
  position: relative;
  display: grid;
  place-items: center;
  width: 54px;
  height: 54px;
  border: 1px solid rgba(16, 18, 38, 0.12);
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.86);
  box-shadow: 0 16px 34px rgba(23, 35, 38, 0.14);
  cursor: pointer;
  pointer-events: auto;
  transition: opacity 180ms ease, transform 180ms ease, border-color 180ms ease;
}

.explore-arrow::before {
  content: "";
  width: 13px;
  height: 13px;
  border-top: 2px solid var(--teal-deep);
  border-right: 2px solid var(--teal-deep);
}

.explore-arrow--prev::before {
  transform: translateX(3px) rotate(-135deg);
}

.explore-arrow--next::before {
  transform: translateX(-3px) rotate(45deg);
}

.explore-arrow:hover {
  border-color: rgba(47, 127, 120, 0.34);
  transform: scale(1.05);
}

.explore-arrow:disabled {
  opacity: 0.28;
  cursor: default;
  transform: none;
}

/* --- Mobile: the five features as a swipe carousel ----------------------
   .is-swipe is written by the JS, and only when the rail is actually wired - so if the script never runs the chapters stay as a readable stack rather
   than becoming a 500%-wide row with no way to move it.

   The control is the 01-05 rail, not dots. Dots say "there are five things";
   the pills say WHICH five, and they are the same control the desktop tour
   uses, so the two read as one component at different sizes. */
.explore-stage.is-swipe .explore-copy-track {
  display: flex;
  gap: 0;
  /* FULL BLEED, so the clip edge is the screen edge.

     This box has to be a scroll container - it is what the swipe scrolls - and
     a scroll container clips on both axes. Inside the wrap, that clip landed
     exactly on the mock's own left and right edges, so the lower corners of
     its shadow were cut off against the page and left a short vertical line
     down each side of every slide.

     Pushed out to the viewport edges, the same clip has nothing to cut that
     the screen was not going to cut anyway. The slides put the gutter back
     themselves, so the words still line up with the heading above them. */
  width: 100vw;
  margin-inline: calc(-1 * (50vw - 50%));
  overflow-x: auto;
  /* STATED, and not just implied by overflow-x.

     A box with `overflow-x: auto` and no overflow-y computes overflow-y to
     `auto` as well - that is the spec, not a browser quirk - so this track
     was a vertical scroll container too. Its height is written by the JS from
     the slide on screen, and the moment that measurement came up even a few
     pixels short (a font settling, a mock reflowing) the slide scrolled
     UNDER the 01-05 rail on a flick, leaving the feature cut off at the top
     and a band of nothing under it while the page itself had not moved.

     The section scrolls as one thing now. Sideways is the only gesture this
     box answers; up and down belong to the page. */
  overflow-y: hidden;
  scroll-snap-type: x mandatory;
  overscroll-behavior-x: contain;
  scrollbar-width: none;
}

.explore-stage.is-swipe .explore-copy-track::-webkit-scrollbar {
  display: none;
}

/* The height is written by the JS from the slide on screen; the transition is
   what stops that reading as a jump. */
.explore-stage.is-swipe .explore-copy-track {
  transition: height 320ms cubic-bezier(0.4, 0, 0.2, 1);
}

.explore-stage.is-swipe .explore-chapter {
  align-self: flex-start;
  flex: 0 0 100%;
  /* Grid/flex children default to min-width:auto and refuse to shrink below
     their content - the same trap that made the hero 432px wide inside a
     390px screen. Without this each slide is as wide as its mock. */
  min-width: 0;
  scroll-snap-align: center;
  align-content: start;
}

/* The rail comes back as the carousel's control, ABOVE the slides.

   Below them it was under the fold: a slide is a heading, a paragraph and a
   full UI mock, so the control sat off the bottom of the screen and the
   carousel looked like a plain stack with no way to move it. Above, the five
   features are named before the reader starts, which is what a tab strip is
   for. The markup order is slides-then-rail (the desktop layout needs that),
   so this is `order`, not a DOM move. */
.explore-stage.is-swipe .explore-product-stage {
  order: -1;
}

/* The track's gap is sized for a two-column desktop layout, where it is the
   space between the copy and the mock. Stacked, it became 130px of nothing
   between the rail and the feature it selects - which reads as the rail
   belonging to the heading rather than to the slides. */
.explore-stage.is-swipe .explore-track {
  gap: 0;
}

/* The 60px top padding separated one stacked chapter from the last one. There
   is no last one now - they are side by side - so it is just a hole under the
   rail.

   The bottom padding is the mock's shadow allowance, and it is not optional:
   the track clips on both axes (it has to - it is the swipe's scroll
   container), and the JS sizes it to exactly this slide. With 8px under the
   mock, its shadow was cut off 9px down while still at rgb(209,209,206)
   against a rgb(253,250,246) field - a 44/255 line straight across the page,
   which is what it looked like. The shadow itself is cut to size just below,
   so this number can stay small enough not to read as a hole. */
.explore-stage.is-swipe .explore-chapter {
  padding-top: 0;
  padding-bottom: 60px;
  /* The gutter the track just gave up. Same token the wrap uses, so the slide's
     heading sits on the same line as the section's. */
  padding-inline: var(--wrap-gutter);
  /* Each slide keeps its shadow to itself. The slides are a screen wide and
     their mocks are not, so the one waiting off the right edge was casting a
     38px shadow back onto the one being read - and having it cut off at the
     bottom of the track, because the track is only ever as tall as the slide
     on screen. Clipping at the slide's own padding box puts that cut on the
     screen edge, where there is nothing to see. */
  overflow: clip;
}

/* Cut to the room above, and in any case the right size for a 384px-wide mock
   rather than the 700px one this was drawn for. */
.explore-stage.is-swipe .explore-frame {
  box-shadow:
    0 18px 38px rgba(23, 35, 38, 0.18),
    0 5px 14px rgba(16, 18, 38, 0.09);
}

.explore-stage.is-paired.is-swipe .explore-nav {
  display: flex;
  flex-wrap: nowrap;
  justify-content: flex-start;
  gap: clamp(18px, 7vw, 34px);
  margin: 0 0 18px;
  padding: 0 0 8px;
  border-bottom: 1px solid rgba(47, 127, 120, 0.16);
  overflow-x: auto;
  /* Stated, never implied - see .stage-rail. `overflow-x: auto` alone makes
     overflow-y `auto` too, and a one-row rail that can scroll vertically will
     eventually be scrolled vertically by something. */
  overflow-y: hidden;
  scrollbar-width: none;
}

.explore-stage.is-paired.is-swipe .explore-nav::-webkit-scrollbar {
  display: none;
}

.explore-stage.is-swipe .explore-nav button {
  scroll-snap-align: start;
  min-height: 36px;
}

/* Sized so the heading, one whole feature and the rail are one screen. Every
   slide is as tall as the tallest, so the type has to come down for all five
   or the carousel is a screen and a half no matter which one is showing. */
.explore-stage.is-swipe .explore-head h2 {
  font-size: clamp(2.6rem, 5.2vw, 5.2rem);
  line-height: 1.08;
}

.explore-stage.is-swipe .explore-head {
  padding-bottom: 18px;
}

.explore-stage.is-swipe .explore-head .lead {
  max-width: 660px;
  margin-top: 20px;
  font-size: clamp(1rem, 4.2vw, 1.22rem);
  line-height: 1.42;
}

.explore-stage.is-swipe .explore-chapter h3 {
  margin-top: 8px;
  font-size: clamp(1.35rem, 5.6vw, 1.75rem);
}

.explore-stage.is-swipe .explore-copy {
  margin-top: 10px;
  font-size: 0.94rem;
  line-height: 1.5;
}

.explore-stage.is-swipe .explore-chapter > .explore-visual {
  margin-top: 14px;
}

/* --- The rail only fades when there is something past the edge ----------
   The mask was unconditional, so the last pill sat under a permanent fade with no
   arrow, no peek and no scrollbar - it read as a design edge, not as more. */
.nav-shell > .stage-rail:not(.is-scrollable) {
  mask-image: none;
  -webkit-mask-image: none;
}

/* ==========================================================================
   REGION B - HAMBURGER HEADER ON PHONES

   The island carried brand + Play badge + a five-item rail. Brand and five
   items cannot share one row at phone width (the rail needs ~295px and has
   ~251px beside the brand at 393px), so the rail was a masked scroller with
   items cut off, and giving it its own row put the header at two rows / ~95px.

   On phones the rail collapses into a toggle and drops as a panel instead:
   the header becomes a single ~64px row. The rail is a position indicator as
   much as a menu, so the panel marks the stage you are in rather than just
   listing the five.

   The store CTA is not lost with the badge hidden - the hero and the closing
   section both carry one. Tablets keep the rail and the badge inline.
   ========================================================================== */

/* The header's mobile menu moved to assets/chrome.css, which every page
   loads - the feature pages had no hamburger while this lived here. */


/* ==========================================================================
   SECTION 5 - EXPLORE: RESERVE THE HEADER, AND A COMPACT PRIVACY CLOSE
   ========================================================================== */

/* The showcase pins at top:top, so on a short screen the content rode up
   underneath the fixed header: all five nav pills were hidden at 871x436,
   844x390 AND 1024x768 (iPad landscape) - only a tall desktop escaped it.

   The reserve lives on .explore-showcase now. The track is the pinned box's
   one row, and a viewport min-height there put the mock 300px below the fold. */
.explore-track {
  box-sizing: border-box;
}

/* --header-h is the reserve every pinned stage reads through --safe-top. The
   short-landscape block still declares the old two-row height (52px) while the
   hamburger header actually ends at 60px, so the Explore pills landed under it
   even after .explore-track began reserving the space. */
@media (max-height: 560px) and (orientation: landscape) {
  :root {
    --header-h: 70px;
  }
}

/* ==========================================================================
   SECTION 5 - PRIVACY CLOSE: SIZED TO LAND ON ONE SCREEN

   Measured at the landing position, not just as a block height: the close
   arrives with its top at the header, so what matters is whether the finale
   line still clears the bottom of the viewport. It did not - 445px into a
   436px landscape screen, 899px into an 852px portrait one, which cut the
   closing line in half.
   ========================================================================== */

/* The three trust boxes this section used to close on are gone with the rest of
   the drawn Explore markup, and the three blocks that sized them for a portrait
   phone and a short landscape window went with them. What is left of the
   privacy close is the header reservation above. */

/* ==========================================================================
   REGION B - NATIVE SCROLL SNAP ON TOUCH

   Touch scrolling is the browser's job. The page used to claim the gesture at
   48px of travel and tween to a target, so a swipe tracked, jumped, went dead
   and jumped again: 240px of finger moved the page 1605px. gsap-site.js now
   hands Region B back to the browser on touch, and these are the stops it
   settles on - 1:1 tracking, real inertia, snapping on the compositor.

   Viewport-gated, not pointer-gated: a phone and a resized desktop window at
   the same width must behave identically. Desktop sizes are untouched. `proximity`
   rather than `mandatory`: it settles a gesture that came to rest near a stop
   without ever trapping one that is travelling. Only Region B blocks carry
   scroll-snap-align, so only the intended page stops participate.
   ========================================================================== */

@media (max-width: 760px), (orientation: portrait) and (max-width: 1024px), (max-height: 560px) and (orientation: landscape) {
  /* proximity, never mandatory: mandatory fights ScrollTrigger's pins - it
     collapsed the document from ~22,000px to ~7,000px, because the pinned
     stages' scroll distance cannot survive a container that must always rest
     on a snap point. proximity handles the clean cases on the compositor and
     settleToNearestPage() finishes the ones it leaves short.

     Gated on [data-snap="on"], which gsap-site.js clears once the footer is
     in view. */
  /* DISABLED DELIBERATELY - re-enable by restoring the declaration below.

     The snap stops here are a page apart (665-940px), but a comfortable
     thumb swipe is 300-400px. Under proximity any swipe shorter than half a
     page is UNDONE: measured, 240px of scrolling returned 0px of movement.
     On a phone that reads as the page refusing to move, which is worse than
     no snapping at all. Snapping only works when a stop is within one
     gesture's reach; these are not.

     Mobile now scrolls natively, exactly like any other page. */
  html[data-snap="on"] {
    scroll-snap-type: y mandatory;
  }

  /* Ordinary flow blocks: safe to snap at any mobile viewport.
     --landing-lift is written by gsap-site.js from the header's measured
     height, so a CSS snap and a JS landingTop() land on the same pixel. */
  .explore-intro,
  .explore-showcase,
  .add-stage,
  .yourstory-stage {
    scroll-snap-align: start;
    scroll-margin-top: var(--landing-lift, calc(var(--safe-top) + 10px));
  }

  /* The footer must stay reachable - never snap at the bottom of the page. */
  .footer {
    scroll-snap-align: none;
  }
}

/* .explore-chapter only where Explore is UNPINNED. In the pinned layout the
   chapters are position:absolute inside the pinned stage, so they do not move
   with the scroll - snap points on them resolve to garbage offsets and the
   browser threw the page back to y=221. Same query as the pairing logic. */
@media (max-width: 760px), (orientation: portrait) and (max-width: 1024px) {
  .explore-chapter {
    scroll-snap-align: start;
    scroll-margin-top: var(--landing-lift, calc(var(--safe-top) + 10px));
  }
}

/* ==========================================================================
   SEAMS - WHERE THE SECTIONS MEET

   Collected here because the fixes live next to the sections they belong to
   and are easy to undo by accident. A seam is invisible when the pixel above
   it and the pixel below it are the same, at every x. Two things stop that
   happening on this page, and they need different answers.

   1. NO SECTION ENDS ON THE COLOUR ITS STYLESHEET NAMES. Every one is a stack
      of radials over a base, and a radial placed at `82% 16%` covers a
      completely different band of a 412x900 box than of a 1920x1080 one - so
      the edge colour changes with the window, and a shape matched at one size
      is a line at another. That is what the testimonials' old ellipse-shaped
      top seal was: a reproduction of the chapter's vignette, measured once,
      and a 10-21/255 line at 1024, 1280, 1440 and 1920.

      The answer is to flatten both sides rather than match shapes: the section
      above fades its last few per cent to one flat colour, the section below
      opens on that same colour, and the join has no shape left to get wrong.
      Applied on .intro-copy (bottom), .williams-video-panel (both), .love-stage (top,
      via --love-seal-top), .explore-stage (top) and .yourstory-stage (top).

   2. A PANEL SHADOW CUT OFF BY A SECTION'S OWN overflow:hidden IS ALSO A SEAM,
      and the cheaper one to miss - a background seal cannot fix it, because a
      child's box-shadow paints ABOVE its parent's background. Either the
      section gives the shadow the room it needs (.add-stage's bottom padding
      is a pixel floor for exactly this), or the shadow is cut to the room
      (.hero-main-frame and .explore-frame on phones), or its last few dozen
      pixels fade into the base instead of being chopped at it
      (.product-hero::after and .williams-video-panel::after).

   Every join on the page measures a step of 3/255 or less at 360x640,
   390x844, 412x732, 412x915, 1024x768, 1280x720, 1440x900 and 1920x1080.
   Check it the same way if you change any of this: park the boundary in a
   REAL viewport and read the pixels either side of it. A full-page screenshot
   cannot do it - it re-lays-out anything sized in svh, and the numbers it
   gives are for a window that does not exist.
   ========================================================================== */

/* ==========================================================================
   A TABLET HELD UPRIGHT - the one viewport the portrait shots break

   The four portrait screenshots (838x1214) are what a phone gets instead of
   the landscape wall, and the reason they are worth it is that they fill a
   narrow screen: at `--main-w: 100%` a 375px handset draws the app at 0.41
   instead of 0.27.

   Run the same rule on an iPad held upright and it is a disaster. 100% of an
   820px column is 788px wide, and 788 at 838/1214 is 1142px TALL - a bezel
   taller than most of the screen it is sitting on, which pushes the call to
   action off the bottom and, with the copy above it, overflows the section
   outright. The phone's win and the tablet's failure are the same rule: the
   image is as wide as the column, and on a tablet the column is wide.

   So above phone width the cap is the HEIGHT, not the width. 58svh leaves
   room for the headline, the lead and the badge on a 1180px-tall iPad with
   the browser chrome showing, and the width follows from the ratio rather
   than from the column. Below 761px nothing here applies and a phone keeps
   the full-width treatment it was all for.

   Not folded into the block above: that one is `(max-width: 760px),
   (orientation: portrait) and (max-width: 1024px)` - one query for two
   devices that want opposite things, which is exactly how this got shipped.
   ========================================================================== */
@media (orientation: portrait) and (min-width: 761px) and (max-width: 1024px) {
  /* On the ELEMENT, not on `--main-w`. The phone block a few hundred lines up
     sets `.hero-product-main { width: 100% }` outright, so at these widths the
     variable is dead weight - it is read by a declaration that has already
     been overridden. Setting `--main-w` here looked correct, computed
     correctly, and changed nothing: the tablet stayed at 100% of its column
     and the cap was invisible. Same specificity, later in the file, so this
     wins on order. */
  /* A bigger overhead than a phone, and the difference is exactly what this
     block spends that a phone does not: the badge keeps a centred line of its
     own here (34px plus its 14px margin) and there is 14px of air above the
     tablet. 336 + 48 + 14 is 398, and 376 measured - the rest comes back as
     the shorter headline a wider column gives.

     It shared the phone's 336 for one revision and the tablet ran 11-30px off
     the bottom of every iPad, which is the same bug this section started
     with. */
  .hero-product-main {
    width: min(100%, calc((100vh - 376px) * 838 / 1214));
    width: min(100%, calc((100svh - 376px) * 838 / 1214));
    margin-inline: auto;
  }
}


/* ==========================================================================
   A PHONE TURNED SIDEWAYS - 846x414 and its neighbours

   The landscape block above gives this viewport a two-column hero and sizes
   the tablet from `--tableau-h: calc(100svh - safe-top - 32px)`. On a handset
   that arithmetic is wrong twice over: 32px does not cover the announcement
   bar, which is only hidden below 760px WIDE and a sideways phone is 846, nor
   the header island under it. Between them they take about 105px of a 414px
   screen, so the tablet was sized against a budget a fifth larger than the
   room it actually had and the bottom of the bezel ran off the fold - with
   the store badge, which sits under the copy, never arriving at all.

   Everything here is the same fix stated in four places: give the height
   budget the two bars it forgot, and spend what is left more cheaply.
   ========================================================================== */
@media (max-height: 540px) and (orientation: landscape) and (max-width: 950px) {
  .product-hero {
    /* `--safe-top` is calc(70px + 40px) - 110px, reserved for the header
       island and the announcement bar on a screen with room for both. On a
       sideways handset it is 110px of a 375px screen spent before anything is
       drawn, and with the 46px of bottom padding under it that is 42% of the
       viewport gone to margin. It is the reason the tablet was 189px wide
       here while the section still ran 23px past the fold.

       The announcement bar is already subtracted separately below, so what
       this actually needs to clear is the header island: 56px. */
    --safe-top: 56px;

    /* The bars, named rather than folded into one number, so that a change to
       either is a change here and not a re-measure. 52px is the header island
       plus the gap under it. */
    --tableau-h: calc(100vh - var(--safe-top) - var(--announce-h, 0px) - 52px - 20px);
    --tableau-h: calc(100svh - var(--safe-top) - var(--announce-h, 0px) - 52px - 20px);
    --hero-h1: clamp(1.45rem, 4.2vw, 1.9rem);
  }

  .snap-panel.product-hero {
    padding-top: calc(var(--safe-top) + 8px);
    padding-bottom: 16px;
  }

  .product-hero-wrap {
    /* Was `100svh - safe-top - 28px`, which on its own forced the section
       taller than the screen the moment the announcement bar existed. */
    min-height: calc(100vh - var(--safe-top) - var(--announce-h, 0px) - 52px);
    min-height: calc(100svh - var(--safe-top) - var(--announce-h, 0px) - 52px);
    gap: 8px 20px;
  }

  .product-hero-details .lead {
    font-size: 0.92rem;
    line-height: 1.38;
  }

  .product-hero-details .hero-actions .store-button img {
    height: 26px;
  }

  /* The scroll cue is the one thing here that can go. On a sideways phone the
     next section is one flick away and the cue is a second line of chrome
     competing with the badge for the last 40px. */
  .hero-scroll-next {
    display: none;
  }

  /* ── UNDOING THE PHONE BLOCK ──────────────────────────────────────────
     A handset turned sideways is 740x360: under 760 wide, so every rule in
     the phone block applies to it, and those rules are written for a tall
     narrow screen. Stacked in one column with a full-width tablet, 740x360
     drew a 712x1021 bezel inside a 360px viewport - the tablet alone was
     nearly three screens tall.

     The landscape block that owns this viewport sits earlier in the file, so
     the phone block was simply winning on order. This puts its four
     decisions back. */
  .product-hero-wrap {
    display: grid;
    grid-template-columns: minmax(0, 0.92fr) minmax(0, 1.08fr);
    grid-template-areas:
      "copy tableau"
      "details tableau";
    align-items: center;
  }

  /* A BOX AGAIN - and the comment here said so while the code said
     `display: contents`, which is how this shipped broken.

     With the wrapper dissolved, .lead and .hero-actions became grid items in
     their own right, and both were assigned `grid-area: details`. Two items
     in one area is not two rows, it is two layers: the store badge rendered
     ON TOP of the hero's lead pills.

     As a box it holds its own children in normal flow, which is all that was
     ever wanted here, and the badge flows inline after the lead exactly as it
     does upright. */
  .product-hero-details {
    display: block;
  }

  .product-hero-copy { grid-area: copy; }
  .product-hero-details { grid-area: details; }
  .hero-tableau { grid-area: tableau; }

  .product-hero-details {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    column-gap: 12px;
    /* When the badge drops to its own line it needs to read as a button under
       a sentence, not as a third line of the sentence. */
    row-gap: 10px;
  }

  .product-hero-details .lead {
    flex: 1 1 260px;
    margin-bottom: 0;
  }

  .product-hero-details .hero-actions {
    flex: 0 0 auto;
    margin: 0;
  }

  .hero-tableau {
    display: flex;
    width: 100%;
    height: var(--tableau-h);
  }

  /* Width from the height that is left, at the LANDSCAPE screenshot's ratio - this viewport keeps the 1284x883 wall, not the portrait set.

     `--main-w` is `min(clamp(280px, 44vw, 440px), ...)`, and on a sideways
     handset 44vw is the wrong instinct: the constraint here is never width,
     it is the 300-odd pixels of height between the header and the bottom of
     the screen. Sizing from vw left the tablet at 287x205 in a 414px viewport
     with a third of the screen empty under it. */
  .hero-product-main {
    width: min(100%, calc(var(--tableau-h) * 1284 / 883));
    margin-inline: 0;
  }
}

/* ==========================================================================
   SHORT AND WIDE - 1024x600, and any laptop with every toolbar open

   The desktop hero sizes its tablet from `--tableau-h: clamp(340px, 46svh,
   500px)`, and on a 600px screen 46svh is 276 - under the clamp's floor, so
   the floor wins and the tablet is drawn 340px tall in a space that has
   about 300. The section then runs 47px past the bottom of the screen.

   A floor is the right shape for that rule (below some size the mock stops
   being worth showing at all), it was just set for screens that are at least
   740 tall. Here it comes down to what actually fits. Not folded into the
   landscape-phone block above: that one is `max-height: 540px`, and 600 is
   both taller than a phone and shorter than a laptop.
   ========================================================================== */
@media (min-width: 761px) and (max-height: 700px) and (min-height: 541px) and (orientation: landscape) {
  .product-hero {
    --tableau-h: clamp(220px, 44vh, 500px);
    --tableau-h: clamp(220px, 44svh, 500px);
  }

  .snap-panel.product-hero {
    padding-top: calc(var(--safe-top) + 10px);
    padding-bottom: 12px;
  }

  .product-hero-wrap {
    gap: 10px clamp(24px, 4vw, 60px);
    min-height: 0;
  }
}

/* ==========================================================================
   EXPLORE, ON A HANDSET TURNED SIDEWAYS

   The showcase stacks: heading, then lead, then the feature copy, then the
   tablet. That is right on a phone held upright, where the screen is all
   height and no width. Turned sideways it is the exact opposite - 846x414 is
   all width and no height - and stacking spent the entire screen on three
   lines of type before the tablet got a look in. The tablet ended up 170px
   wide in the right margin, smaller than the word "Everyone" beside it.

   So: two columns. The type takes the left half and gets smaller, because at
   this height a 2.6rem heading is three lines of a four-line screen; the
   tablet takes the right half, where the room is. `display: contents` on the
   track hands its two children up to this grid so the copy and the visual can
   sit in different columns - they are wrapped together for the vertical
   layout, which is not the one being used here.
   ========================================================================== */
@media (max-height: 540px) and (orientation: landscape) {
  .explore-showcase {
    display: grid;
    grid-template-columns: minmax(0, 0.92fr) minmax(0, 1.08fr);
    grid-template-areas:
      "copy visual";
    align-items: center;
    align-content: center;
    column-gap: clamp(18px, 2.4vw, 32px);
    row-gap: 6px;
  }

  .explore-track { display: contents; }
  .explore-copy-track { grid-area: copy; }
  .explore-product-stage { grid-area: visual; }

  .explore-head h2 {
    font-size: clamp(1.3rem, 3.1vw, 1.75rem);
    line-height: 1.1;
  }

  .explore-head .lead {
    font-size: 0.82rem;
    line-height: 1.4;
    max-width: 100%;
  }

  .explore-chapter h3 {
    font-size: clamp(1.05rem, 2.5vw, 1.4rem);
    line-height: 1.12;
  }

  .explore-chapter .explore-copy {
    font-size: 0.8rem;
    line-height: 1.45;
  }

  .explore-chapter .eyebrow {
    font-size: 0.62rem;
  }
}
/* ==========================================================================
   THE BADGE, SPLIT BY WHETHER IT FITS ON THE LINE

   The block above puts the lead and the badge in one flex row. That is the
   right answer for a tablet held upright, where the sentence ends halfway
   across and the badge has somewhere to go, and the wrong one for a phone,
   where the sentence uses every pixel and the badge is pushed onto a line of
   its own - left-aligned, alone, looking dropped rather than placed.

   So the two cases stop sharing a rule. A phone puts it under the tablet and
   gives it the rest of the screen; a tablet keeps it on the last line of the
   sentence, where it reads as the end of the thought.
   ========================================================================== */

/* --- phones: under the bezel, centred, as big as the screen will allow ----
   It used to sit inline on the last line of the lead at 24px, which bought
   about 45px of fold for the tablet and cost the page its only call to
   action above the scroll - a badge set in a sentence is punctuation, not a
   button. The tablet gives 10% of itself back instead (see below) and the
   room that frees goes to the badge, at the bottom, where a reader who has
   just looked at the product finds the way in without hunting for it. */
@media (max-width: 760px) {
  /* display:contents, and this is the one place on the page it earns it.

     The badge has to end up AFTER the tablet, and `order` only sorts
     SIBLINGS: inside .product-hero-details it sits a level below the column
     that holds .hero-tableau, so no amount of ordering within that box can
     move it past the tablet. Dissolving the wrapper drops the lead and the
     badge into the wrap's own flex column, where all four blocks can be
     sorted against each other.

     Safe here specifically: on a phone this box carries no background, no
     padding and no animation of its own - the entrance is driven off the
     children's [data-hero-step] attributes, not off the wrapper - so there
     is nothing in it to lose. Every child's order is restated below, because
     with the box gone the defaults are all 0 and the lead would otherwise
     overtake the headline. */
  .product-hero-details {
    display: contents;
  }

  .product-hero-copy { order: 1; }
  .hero-tableau { order: 3; }

  /* "Nestory for families" goes on a phone. On a wide screen it is the label
     that tells a reader what they have arrived at before the headline does;
     on a phone the headline is already the first thing under the brand in the
     header island, two inches above it, so the eyebrow is the same
     introduction twice - and it costs a line plus its gap on the one axis
     this section has none of. What it frees goes to the tablet. */
  .product-hero-copy .eyebrow {
    display: none;
  }

  /* flex: 0 0 auto undoes `flex: 1 1 320px` from the portrait block. That
     basis was written for the lead as a member of a horizontal row, where 320
     is a WIDTH; with the wrapper dissolved the lead is a member of the wrap's
     vertical column, the same number is read as a height, and the sentence
     came out 320px tall with the tablet shoved off the screen under it. */
  .product-hero-details .lead {
    order: 2;
    display: block;
    flex: 0 0 auto;
    margin-bottom: 0;
  }

  /* THE ROOM UNDER THE BEZEL IS THE BADGE'S, BUT THE BADGE IS A BADGE.

     Sizing the artwork to fill that room was tried and it was too much: at
     136:41 a badge given the whole gap is 100px-plus tall and stops reading
     as something to tap - it reads as a banner, the same failure the 54px
     attempt hit in the portrait block, at twice the size.

     So the BOX still takes what is left - `flex: 1`, which is what keeps this
     the last thing in the section and stops anything else claiming the room - and the badge inside it is a stated height: 35px, the 24 it was set at
     inline, times 1.2 twice over. On its own line under the tablet it can
     afford that; it does not need the whole gap.

     align-items: flex-start, NOT center. Centred, the badge floated in the
     middle of whatever the screen had spare - on a tall phone that is 40-odd
     pixels of nothing between the bezel and the badge, and the pair stopped
     reading as one object. Pinned to the top of its box it sits a wrap-gap
     under the tablet at every height, and the slack falls below it where it
     costs nothing.

     vertical-align is reset because the rule this replaced nudged the badge
     -2px to sit on the lead's baseline; it is not on a line of text any more. */
  .product-hero-details .hero-actions {
    order: 4;
    display: flex;
    align-items: flex-start;
    justify-content: center;
    flex: 1 1 auto;
    min-height: 44px;
    margin: -2px 0 0;
    vertical-align: baseline;
  }

  .product-hero-details .hero-actions .store-button {
    display: inline-flex;
    max-width: 86vw;
  }

  .product-hero-details .hero-actions .store-button img {
    height: 35px;
    width: auto;
    max-width: 100%;
  }

  /* THE TABLET TAKES WHAT IS LEFT - AND NEVER MORE THAN THAT.

     Every number in this rule was a multiplier until the fit probe was run
     across the phone list, and a multiplier is the one shape this formula
     cannot have. `(svh - overhead) x f` grows the tablet by f pixels for every
     pixel of screen, so with f above 1 there is always a height at which the
     tablet overtakes the viewport - and since the copy above it does NOT
     shrink, the overrun compounds: at f = 1.089 the probe measured every
     portrait phone over the fold, from 2px at 360x640 to 91px at 412x846,
     with the store badge clipped off the bottom on nine of the eleven.

     So f is 1 and the tuning lives in the constant, which is the only honest
     place for it: 368px is what this section costs ABOVE and BELOW the bezel
     at this width - padding, headline, lead, the badge's own row and the
     shadow's air - measured, not guessed. Subtract it and the tablet is
     exactly the remainder at every height, which is the most it can be while
     the badge is still on the screen.

     Both constants are set so the badge clears the bottom edge by 14-20px on
     the tightest screen each of them covers - 428x926 for this one, 320x568
     for the short-phone one below. That margin is not decoration: a real
     handset renders the headline and the lead at slightly different heights
     than a headless Chromium does, and the whole sum sits on top of them.

     min(100%, ...) is a second cap and it now almost never binds: on a wide
     phone the column used to win and hand a 412px-wide handset a 545px-tall
     bezel regardless of whether the screen was 846 or 915 tall, which is what
     put 412x846 56px over on its own. */
  .hero-product-main {
    width: min(100%, calc((100vh - 368px) * 838 / 1214));
    width: min(100%, calc((100svh - 368px) * 838 / 1214));
    margin-top: 4px;
  }

  /* The section's own bottom padding, which was the last thing holding this
     past the fold and the hardest to see: the TABLET was ending 26-38px clear
     of the bottom while the SECTION still overran by 8-20px, because the
     padding under it was 40-50px of nothing. The mock's shadow needs a few
     pixels; nothing else does. */
  .snap-panel.product-hero {
    padding-bottom: 10px;
  }
}

/* --- the same sum, on a phone that has already been compacted -------------
   The block up at `(max-width: 760px) and (max-height: 760px)` takes a line
   off the headline, shrinks the lead, drops the section's bottom padding to
   zero and tightens the wrap's gap - so on a short phone this section costs
   about forty pixels less above and below the bezel than the rule above
   assumes, and 368 would hand that saving back as empty space instead of to
   the tablet. Measured at 320x568, 360x640 and 375x667: 324.

   AFTER the phone block, not inside the compact one where it belongs, purely
   because of order: the two selectors have the same specificity and the
   compact block is 1,200 lines further up the file, so anything it said here
   would lose. */
@media (max-width: 760px) and (max-height: 760px) {
  .hero-product-main {
    width: min(100%, calc((100vh - 324px) * 838 / 1214));
    width: min(100%, calc((100svh - 324px) * 838 / 1214));
  }
}

/* --- tablets upright: on the last line of the sentence ------------------- */
@media (orientation: portrait) and (min-width: 761px) and (max-width: 1024px) {
  /* Its own line, centred - the same answer a phone gets, arrived at the long
     way round.

     Inline after the full stop was tried and is right only when the sentence
     ends short of the margin. It does not: at 834px the lead runs to within a
     few characters of the right edge, so the badge wrapped anyway and landed
     against the left margin, which is the worst of the three placements. Once
     it is going to be on its own line, centred under the copy is where it
     belongs, and it can be the size it wants. */
  .product-hero-details {
    display: block;
  }

  .product-hero-details .lead {
    margin-bottom: 0;
  }

  .product-hero-details .hero-actions {
    display: flex;
    justify-content: center;
    margin: 14px 0 0;
  }

  /* 34px - a fifth up on the 28 it was sharing a line at, because it is no
     longer sharing one, and this is a tablet-sized screen. */
  .product-hero-details .hero-actions .store-button img {
    height: 34px;
  }

  /* Air between the sentence and the device. They were 10px apart - the
     wrap's own gap - which read as one block rather than as copy and then a
     picture. */
  .hero-tableau {
    margin-top: 14px;
  }
}

/* ==========================================================================
   ONE STORE BADGE ON THE FIRST SCREEN, NEVER TWO.

   Above the hamburger breakpoint the header carries the badge, and the header
   is on the same screen as the hero - so the hero's copy of it was the same
   button twice, a couple of hundred pixels apart, one of them occupying a row
   of its own. Below 699 the header hides its badge to make room for the
   panel, and the hero's is the one that is left.

   Keyed to the same 699px as chrome.css, so at every width exactly one badge
   is on screen. LAST IN THE FILE deliberately: four blocks above place this
   badge for a phone, a short landscape and a tablet upright, and each of them
   states a `display`. They are the hero's composition and are none of this
   rule's business - it only has to outrank them, which position does.
   ========================================================================== */
@media (min-width: 700px) {
  .product-hero-details .hero-actions {
    display: none;
  }
}
