/* ============================================================
   T2Board — marketing site
   Direction: editorial / printed-page. Ivory ground, Smoke ink,
   Teal as the single accent. Hairline rules, sharp corners,
   generous whitespace, terse copy — one idea per block.

   Brand source: talk2view-brand skill.
   - Corner geometry is SQUARE for web/product UI (brand book p19–24).
   - Primary CTA is Smoke-on-Ivory, not white-on-Teal: the book
     specifies a white label on Teal for buttons, but that pairing
     is ~2.1:1 and fails contrast at button size. Smoke on Teal /
     Ivory on Smoke are both brand-sanctioned and pass.
   ============================================================ */

@font-face {
  font-family: 'Space Grotesk';
  font-style: normal;
  font-weight: 300 700;
  font-display: swap;
  src: url(assets/fonts/SpaceGrotesk.woff2) format('woff2');
}

:root {
  /* ---- Talk2View core palette ---- */
  --teal:      #26C8B8;
  --highlight: #73FFFF;
  --harbor:    #608B8D;
  --mist:      #CAEBEB;
  --ivory:     #F2F0EC;
  --stone:     #9AA4A8;
  --slate:     #67777C;
  --ash:       #354951;
  --smoke:     #021C25;

  /* ---- roles ---- */
  --bg:        var(--ivory);
  --bg-raised: #FBFAF8;
  --bg-sunken: #EAE8E3;
  --ink:       var(--smoke);
  --ink-mute:  var(--slate);
  --ink-faint: var(--stone);
  --rule:      rgba(2, 28, 37, 0.13);
  --rule-soft: rgba(2, 28, 37, 0.07);

  /* ---- type ---- */
  --font: 'Space Grotesk', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;

  --t-display: clamp(2.75rem, 6.6vw, 5.75rem);
  --t-h2:      clamp(1.9rem, 3.8vw, 3.25rem);
  --t-h3:      clamp(1.2rem, 1.6vw, 1.45rem);
  --t-lead:    clamp(1.05rem, 1.3vw, 1.2rem);
  --t-body:    1.0625rem;
  --t-small:   0.875rem;
  --t-micro:   0.75rem;

  /* ---- rhythm ---- */
  /* Height of the sticky nav. Sections subtract it and snapping offsets
     by it, so the two must stay in step — change it here only. */
  --nav-h:     4.5rem;
  --gutter:    clamp(1.25rem, 5vw, 4.5rem);
  --section-y: clamp(3.5rem, 6.5vw, 5.5rem);
  --maxw:      1240px;
}

/* ============================================================
   Reset & base
   ============================================================ */

*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* Exactly the nav's height, so a snapped section's top edge lands
     flush beneath it. Any larger and every section is pushed down by
     the difference, throwing its centred content off and shunting its
     last stripe below the fold. */
  scroll-padding-top: var(--nav-h);
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font);
  font-size: var(--t-body);
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  /* `clip`, not `hidden`: overflow-x:hidden turns body into a scroll
     container, which silently breaks ScrollTrigger's pinning. `clip`
     suppresses the same sideways overflow without that side effect. */
  overflow-x: clip;
}

h1, h2, h3 {
  margin: 0;
  font-weight: 500;
  letter-spacing: -0.028em;
  line-height: 1.03;
  text-wrap: balance;
}

/* figure, blockquote and ul carry chunky UA margins (17px 40px) that
   punch holes in the hairline grids — zero them explicitly. */
p, figure, blockquote, ul { margin: 0; }
p, blockquote { text-wrap: pretty; }
img { max-width: 100%; height: auto; display: block; }
a { color: inherit; }

:focus-visible { outline: 2px solid var(--teal); outline-offset: 3px; }

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

/* Available to screen readers, removed from the visual layout. */
.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ============================================================
   Brand motifs
   ============================================================ */

/* Trailing underscore: a short bar, roughly one lowercase char
   wide, at the headline's stem weight, sitting on the baseline.

   A real inline element, not an ::after. SplitText rewraps a
   headline's text into per-line divs, and a pseudo-element on the
   heading itself would render after all of them — orphaning the
   underscore onto a line of its own. As inline content it stays
   with the last word, split or not. */
.u-mark {
  display: inline-block;
  width: 0.42em;
  height: 0.075em;
  margin-left: 0.1em;
  vertical-align: baseline;
  background: var(--teal);
}
.u-mark--smoke { background: var(--smoke); }

/* ============================================================
   Buttons & links
   ============================================================ */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.6em;
  padding: 0.95em 1.6em;
  border: 1px solid transparent;
  font-family: inherit;
  font-size: var(--t-small);
  font-weight: 500;
  text-decoration: none;
  cursor: pointer;
  transition: background 0.2s ease, transform 0.2s ease;
}
.btn:active { transform: translateY(1px); }

.btn--primary { background: var(--smoke); color: var(--ivory); }
.btn--primary:hover { background: #0a3340; }

.btn--on-teal { background: var(--smoke); color: var(--ivory); }
.btn--on-teal:hover { background: #001219; }

.btn__stack { text-align: left; }
.btn__sub {
  display: block;
  font-size: 0.625rem;
  font-weight: 400;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  opacity: 0.72;
  line-height: 1.25;
}
.btn__main { display: block; font-size: 0.95rem; line-height: 1.2; }

/* Text link with a trailing arrow — used instead of a second button. */
.link-arrow {
  display: inline-flex;
  align-items: center;
  gap: 0.5em;
  font-size: var(--t-small);
  text-decoration: none;
  color: var(--ink);
  border-bottom: 1px solid var(--rule);
  padding-bottom: 0.25em;
  transition: border-color 0.2s ease;
}
.link-arrow::after { content: '→'; transition: transform 0.2s ease; }
.link-arrow:hover { border-bottom-color: var(--teal); }
.link-arrow:hover::after { transform: translateX(3px); }

/* ============================================================
   Nav
   ============================================================ */

.nav {
  position: sticky;
  top: 0;
  z-index: 100;
  background: rgba(242, 240, 236, 0.86);
  backdrop-filter: saturate(180%) blur(14px);
  -webkit-backdrop-filter: saturate(180%) blur(14px);
  border-bottom: 1px solid transparent;
  transition: border-color 0.25s ease;
}
.nav[data-stuck='true'] { border-bottom-color: var(--rule); }

.nav__inner { display: flex; align-items: center; gap: 2rem; height: 4.5rem; }
/* Product lockup: T2Board app icon + name, with the company credited
   beneath it. The icon keeps its own rounded-square shape — that is the
   mark's geometry, not a UI corner, so the brand's square-corner rule
   for interface elements doesn't apply to it. */
.nav__logo {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex-shrink: 0;
  text-decoration: none;
}
.nav__logo img { width: 30px; height: 30px; }

.nav__lockup { display: flex; flex-direction: column; line-height: 1.05; }
.nav__lockup-name {
  font-size: 1rem;
  font-weight: 500;
  letter-spacing: -0.015em;
  color: var(--ink);
}
.nav__lockup-by {
  margin-top: 3px;
  font-size: 0.625rem;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

.nav__links {
  display: flex;
  gap: 2rem;
  margin-left: auto;
  list-style: none;
  padding: 0;
}
.nav__links a {
  position: relative;
  font-size: var(--t-small);
  text-decoration: none;
  color: var(--ink-mute);
  transition: color 0.18s ease;
}
.nav__links a::after {
  content: '';
  position: absolute;
  left: 0; bottom: -0.35em;
  width: 100%; height: 1px;
  background: var(--teal);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.25s ease;
}
.nav__links a:hover { color: var(--ink); }
.nav__links a:hover::after { transform: scaleX(1); }

.nav__cta { flex-shrink: 0; padding: 0.7em 1.25em; }

.nav__toggle {
  display: none;
  margin-left: auto;
  padding: 0.5rem;
  background: none;
  border: 0;
  cursor: pointer;
  color: var(--ink);
}

@media (max-width: 820px) {
  .nav__links, .nav__cta { display: none; }
  /* 44px square — the icon is only 22px, but the hit area shouldn't be. */
  .nav__toggle { display: grid; place-items: center; width: 44px; height: 44px; padding: 0; }

  .nav[data-open='true'] .nav__links {
    display: flex;
    position: absolute;
    inset: 4.5rem 0 auto;
    flex-direction: column;
    gap: 0;
    margin: 0;
    padding: 0.5rem var(--gutter) 1.5rem;
    background: var(--bg);
    border-bottom: 1px solid var(--rule);
  }
  .nav[data-open='true'] .nav__links li { border-top: 1px solid var(--rule-soft); }
  .nav[data-open='true'] .nav__links a {
    display: block;
    padding: 1rem 0;
    font-size: 1.05rem;
    color: var(--ink);
  }
  .nav[data-open='true'] .nav__cta {
    display: inline-flex;
    position: absolute;
    inset: auto 0 0;
    margin: 0 var(--gutter) 1.5rem;
  }
}

/* ============================================================
   Section rail
   ============================================================ */

.rail {
  position: fixed;
  right: clamp(1rem, 2vw, 2rem);
  top: 50%;
  transform: translateY(-50%);
  z-index: 90;
  /* Below 1100px the gutter is too tight — the hover labels would run
     over the content. The nav links cover navigation there. */
  display: none;
}
@media (min-width: 1100px) { .rail { display: block; } }

.rail ul {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 0.45rem;
  list-style: none;
  padding: 0;
}
/* padding-block takes each marker past WCAG 2.5.8's 24px pointer target.
   These are graphical controls, not inline text, so the text-link
   exemption does not cover them. The list gap is reduced to match, so
   the visual rhythm is unchanged. */
.rail a {
  display: flex;
  align-items: center;
  gap: 0.8rem;
  padding-block: 5px;
  text-decoration: none;
}

.rail__label {
  font-size: var(--t-micro);
  letter-spacing: 0.11em;
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--ink-mute);
  opacity: 0;
  transform: translateX(6px);
  transition: opacity 0.25s ease, transform 0.25s ease, color 0.25s ease;
}

/* The brand dash: sharp rectangle, never a dot or a pill. */
.rail__dash {
  display: block;
  width: 18px;
  height: 2px;
  background: var(--stone);
  transition: width 0.35s cubic-bezier(0.22, 1, 0.36, 1),
              background 0.25s ease;
}

.rail a:hover .rail__dash,
.rail a:focus-visible .rail__dash { width: 26px; background: var(--ink); }
.rail a:hover .rail__label,
.rail a:focus-visible .rail__label { opacity: 1; transform: none; color: var(--ink); }

.rail a[aria-current='true'] .rail__dash { width: 34px; background: var(--teal); }
.rail a[aria-current='true'] .rail__label { opacity: 1; transform: none; color: var(--ink); }

/* Over the teal CTA panel, Stone and Teal both disappear into the
   background — swap to Smoke, which the brand pairs with Teal at 8.4:1. */
.rail[data-invert='true'] .rail__dash { background: rgba(2, 28, 37, 0.4); }
.rail[data-invert='true'] a:hover .rail__dash,
.rail[data-invert='true'] a[aria-current='true'] .rail__dash { background: var(--smoke); }
.rail[data-invert='true'] .rail__label { color: var(--smoke); }

/* ============================================================
   Next-section button
   ============================================================ */

.next {
  position: fixed;
  left: 50%;
  bottom: clamp(1.25rem, 2.5vw, 2rem);
  transform: translateX(-50%);
  z-index: 90;
  display: inline-flex;
  align-items: center;
  gap: 0.65em;
  padding: 0.8em 1.2em;
  background: var(--smoke);
  color: var(--ivory);
  font-size: var(--t-micro);
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  text-decoration: none;
  transition: opacity 0.35s ease, visibility 0.35s ease,
              transform 0.25s ease, background 0.2s ease;
}
.next:hover { background: #0a3340; }
.next svg { transition: transform 0.25s ease; }
.next:hover svg { transform: translateY(2px); }

/* Faded rather than display:none so it can transition, and
   visibility keeps it out of the tab order while hidden. */
.next[data-hidden='true'] {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
}

/* On the teal panel the Smoke button still passes, but the last
   section hides the button anyway. */
@media (max-width: 600px) {
  .next { display: none; }
}

/* ============================================================
   Hero
   ============================================================ */

/* Light padding only. The section is full-height and centres its own
   content, so generous padding here just pushes past one screen. */
.hero { padding-block: 2rem; }

.hero__grid {
  display: grid;
  grid-template-columns: 0.85fr 1.15fr;
  gap: clamp(2rem, 5vw, 4rem);
  align-items: center;
}

.hero__title { font-size: var(--t-display); }

.hero__lead {
  max-width: 30ch;
  margin-top: 1.5rem;
  font-size: var(--t-lead);
  color: var(--ink-mute);
}

.hero__actions { margin-top: 2.25rem; }

/* ============================================================
   Hero device — hand mockup with a live host-app overlay

   The shell PNG has a blank (opaque black) screen above the keyboard.
   .device__screen is pinned to exactly that rectangle, in percentages
   measured off the source image so it tracks any render width:
     x 138 -> 674 of 1290   =  10.698% left,  41.550% wide
     y  98 -> 570 of 1448   =   6.768% top,   32.597% tall

   Inset a few px inside the glass rather than flush to it. The phone is
   slightly turned, so its right edge runs from x=679 at the top of the
   screen to x=693 lower down — sizing to the WIDEST point overhangs the
   bezel where the phone is most angled. Fit the narrowest. The top also
   clears the status bar and Dynamic Island, which the screen scan reads
   as black but which must stay uncovered.
   (the source PNG was cropped to 1290x1448 — dead transparency above
   the phone and surplus forearm below — so the device renders larger in
   less vertical space. Re-measure these if the crop ever changes.)
   It paints on top of the shell, since that area is opaque, not
   transparent.
   ============================================================ */

/* Sits to the right of its column rather than centred, so it reads as
   anchored to the page edge instead of floating mid-gutter.
   NOTE: never use a CSS transform to nudge this — GSAP owns .device's
   transform for the scroll parallax and would overwrite it. Use
   justify-self / margins. */
.device { position: relative; justify-self: end; width: min(100%, 500px); }
.device__shell { display: block; width: 100%; }

.device__screen {
  position: absolute;
  left: 10.698%;
  top: 6.768%;
  width: 41.550%;
  height: 32.597%;
  overflow: hidden;
  background: #000;
  container-type: inline-size;
}

/* Flowing icon stream inside the mockup's screen. Rows drift in
   alternating directions so the block reads as movement rather than one
   sliding sheet. Tile content is duplicated in the markup, so a -50%
   translate loops with no visible seam. */
.stream {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 6cqw;
  overflow: hidden;
  /* Fade the edges so tiles enter and leave rather than popping at the
     screen boundary. */
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 14%, #000 86%, transparent);
          mask-image: linear-gradient(90deg, transparent, #000 14%, #000 86%, transparent);
}

.stream__row {
  display: flex;
  gap: 6cqw;
  width: max-content;
  will-change: transform;
}

/* Name chips. Sized to their text rather than square, so the row reads
   as a list of places you can type rather than a home screen.

   Accent colours are OURS, not each product's. Reproducing a brand's
   colour alongside its name starts rebuilding the mark; a neutral
   treatment keeps this a plain statement of compatibility. */
.tile {
  display: flex;
  align-items: center;
  gap: 0.5em;
  flex-shrink: 0;
  padding: 0.6em 1em;
  border-radius: 999px;
  background: color-mix(in srgb, var(--c) 18%, #000);
  border: 1px solid color-mix(in srgb, var(--c) 40%, transparent);
  color: color-mix(in srgb, var(--c) 45%, #fff);
  font-size: 5.4cqw;
  font-weight: 500;
  letter-spacing: -0.01em;
  line-height: 1;
  white-space: nowrap;
}

/* Present only once an official asset is dropped into the tile. */
.tile img,
.tile svg {
  width: 1.15em;
  height: 1.15em;
  flex-shrink: 0;
  border-radius: 22%;
}

/* Wired tiles — the official icon IS the tile. The pill chrome comes
   off so the mark sits directly on the screen, sized up from the old
   pill height so marks read at a glance, keeping the same squircle
   radius. */
.tile--icon {
  padding: 0;
  background: none;
  border: none;
}
.tile--icon img,
.tile--icon svg {
  width: 16cqw;
  height: 16cqw;
}

@media (max-width: 900px) {
  .hero__grid { grid-template-columns: 1fr; }
  .hero__lead { max-width: 42ch; }
  /* Headline leads on mobile — the device follows, so the value
     proposition sits above the fold rather than the mockup. Centred
     again here: with one column there is no right edge to hug.

     left, not transform (GSAP owns the transform): the phone sits
     left-of-centre inside the hand-hold photo, so a small shift right
     centres the handset itself rather than the picture's bounding box. */
  .device { justify-self: center; width: min(100%, 320px); margin-top: 0.75rem; left: 7%; }
}

/* Short phones (SE-class, ~667px tall) can't fit headline, lead, badge
   AND a full-height device. Scale the device rather than let the
   section spill past one screen. */
@media (max-width: 900px) and (max-height: 700px) {
  .device { width: min(100%, 215px); }
}

/* ============================================================
   Showcase — the model roster behind the assistant
   ============================================================ */

/* No padding of its own — the next section's top padding supplies the
   gap, otherwise the two stack and the page opens a canyon. */
.showcase { padding-block: 0; }

.showcase__panel {
  padding: clamp(3rem, 6vw, 5.5rem) var(--gutter);
  background: var(--bg-sunken);
  text-align: center;
  overflow: hidden;
}

.showcase__title { font-size: var(--t-h2); }

/* Model chips — names state compatibility, same contract as the hero
   tiles: an official asset dropped into assets/img/models/ upgrades a
   chip to icon + name. */
.models {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1.75rem;
  margin-top: clamp(2.5rem, 4vw, 3.5rem);
}

.models__label {
  font-size: var(--t-micro);
  font-weight: 500;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--ink-mute);
}

.models__row {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.6rem;
  margin-top: 0.8rem;
  padding: 0;
  list-style: none;
}

.model {
  display: inline-flex;
  align-items: center;
  gap: 0.5em;
  padding: 0.55em 1em;
  border: 1px solid var(--rule);
  border-radius: 999px;
  background: var(--bg-raised);
  font-size: var(--t-small);
  font-weight: 500;
  white-space: nowrap;
}

.model img {
  width: 1.25em;
  height: 1.25em;
  flex-shrink: 0;
  border-radius: 22%;
}

.model--more {
  background: none;
  border-style: dashed;
  color: var(--ink-mute);
}

/* ============================================================
   One section per screen

   Each band fills the viewport and centres its own content, so you
   never see the tail of one section and the head of the next at the
   same time. min-height, not height: a section whose content is
   taller than the screen (the stacked cards on a phone) simply grows
   instead of clipping.

   svh, not vh: on mobile browsers vh is measured against the LARGEST
   viewport, so 100vh sections sit taller than the visible area while
   the URL bar is showing. svh tracks the smallest, so nothing hides
   behind browser chrome.
   ============================================================ */

.hero,
.showcase,
.section,
.cta {
  display: flex;
  flex-direction: column;
  justify-content: center;
  /* The screen a section gets is the viewport MINUS the sticky nav that
     sits over it. Using the full viewport height centred content against
     space the nav was already covering, leaving everything sitting low. */
  min-height: calc(100vh - var(--nav-h));
  min-height: calc(100svh - var(--nav-h));
}

/* Snapping is proximity, never mandatory. Mandatory hijacks trackpad
   scrolling and can strand a user inside a section taller than their
   screen. Proximity lands cleanly when you stop near a boundary and
   otherwise stays out of the way.

   Desktop-only. On a phone the hero and the stacked feature cards
   genuinely exceed a screen, and snapping a section taller than the
   viewport drags the reader back to its top every time they try to
   reach the rest. Phones scroll normally; the full-height floor still
   keeps sections from crowding each other. */
@media (min-width: 821px) and (min-height: 640px) and (prefers-reduced-motion: no-preference) {
  html { scroll-snap-type: y proximity; }
  .hero, .showcase, .section, .cta { scroll-snap-align: start; }
}

/* ============================================================
   Section furniture
   ============================================================ */

/* Same reasoning as .hero — full-height sections supply the rhythm, so
   this is a floor for when content outgrows the screen, not spacing. */
.section { padding-block: clamp(2.5rem, 4vw, 3.5rem); }

.section__head { margin-bottom: clamp(2.5rem, 4vw, 3.5rem); }
.section__head--center { text-align: center; }

/* Measure is set per-element, not on the wrapper: `ch` resolves against
   each element's own font-size, so one wrapper width would strangle the
   big headline while leaving the small lead too wide. */
.section__title { font-size: var(--t-h2); max-width: 20ch; }
.section__lead { margin-top: 1.1rem; max-width: 44ch; font-size: var(--t-lead); color: var(--ink-mute); }
.section__head--center .section__title,
.section__head--center .section__lead { margin-inline: auto; }

/* ============================================================
   Feature cards — media on top, one line of copy
   ============================================================ */

.bento {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1px;
  background: var(--rule);
  border: 1px solid var(--rule);
}

/* No top padding: the framed device's clean-cut edge starts exactly at
   the card's top border, inside the box, and the media has no height
   cap — the whole screenshot stays visible down to the rounded bezel. */
.card {
  display: flex;
  flex-direction: column;
  padding: 0 clamp(1.75rem, 3vw, 2.5rem) clamp(1.75rem, 3vw, 2.5rem);
  background: var(--bg-raised);
  text-align: center;
  transition: background 0.3s ease;
}
.card:hover { background: #fff; }

/* overflow keeps the JS entrance slide clipped to the card — without
   it the devices visibly travel outside their boxes on the way in. */
.card__media {
  margin-bottom: clamp(1.75rem, 3vw, 2.5rem);
  display: flex;
  justify-content: center;
  overflow: hidden;
}
.card__media img {
  width: clamp(190px, 20vw, 240px);
  height: auto;
}

.card__title { font-size: var(--t-h3); }
.card__text { margin-top: 0.6rem; color: var(--ink-mute); font-size: var(--t-small); }

@media (max-width: 820px) {
  .bento { grid-template-columns: 1fr; }
}

/* ============================================================
   What's new — draggable ticker
   ============================================================ */

.ticker {
  margin-bottom: clamp(2rem, 4vw, 3rem);
  cursor: grab;
  overflow: hidden;
  -webkit-user-select: none;
  user-select: none;
}
.ticker[data-dragging='true'] { cursor: grabbing; }

.ticker__track {
  display: flex;
  padding-inline: var(--gutter);
  border-block: 1px solid var(--rule);
  will-change: transform;
}

/* Divider comes from a border, not the gap-over-background trick —
   that trick would paint grey into the gutter padding here. */
.release {
  flex: 0 0 clamp(240px, 25vw, 310px);
  padding: clamp(1.5rem, 2.5vw, 2rem);
  background: var(--bg-raised);
  border-right: 1px solid var(--rule);
}
.release:first-child { border-left: 1px solid var(--rule); }

.release__meta {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  font-size: var(--t-micro);
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--ink);
  font-weight: 500;
}
.release__dot { width: 6px; height: 6px; background: var(--teal); flex-shrink: 0; }
.release__title { margin-top: 1.25rem; font-size: 1.1rem; }
.release__text { margin-top: 0.5rem; font-size: var(--t-small); color: var(--ink-mute); }

/* ============================================================
   Reviews — copy left, staggered cards right
   ============================================================ */

.voices {
  display: grid;
  grid-template-columns: 0.85fr 1.15fr;
  gap: clamp(2.5rem, 6vw, 5rem);
  align-items: center;
}
.voices__intro .link-arrow { margin-top: 2rem; }

.voices__cards { display: flex; flex-direction: column; gap: 1.25rem; }

.quote {
  padding: clamp(1.5rem, 2.5vw, 2.25rem);
  background: var(--bg-raised);
  border: 1px solid var(--rule);
}
/* Offset the second card so the pair reads as a conversation
   rather than a stack. */
.quote--offset { margin-left: clamp(1.5rem, 6vw, 4rem); }

.quote__text { font-size: 1.05rem; line-height: 1.45; }
.quote__by { margin-top: 1.5rem; font-size: var(--t-small); color: var(--ink-faint); }
.quote__by strong { display: block; color: var(--ink); font-weight: 500; }

@media (max-width: 900px) {
  .voices { grid-template-columns: 1fr; }
  .quote--offset { margin-left: 0; }
}

/* ============================================================
   Closing CTA — Teal wind-field panel
   ============================================================ */

/* Plain Teal field — the wind-field dash pattern was removed here.
   The generator lived in main.js and is recoverable from git if it is
   ever wanted back (see commit 3f8aa4d and earlier). */
.cta { position: relative; overflow: hidden; background: var(--teal); color: var(--smoke); }

.cta__grid {
  display: grid;
  grid-template-columns: 1fr 0.85fr;
  gap: clamp(2rem, 5vw, 4rem);
  align-items: center;
  padding-block: clamp(2.5rem, 5vw, 4rem);
}

/* White display type on Teal is sanctioned for large headlines only. */
.cta__title { font-size: var(--t-h2); color: #fff; }
.cta__lead { max-width: 32ch; margin-top: 1.25rem; color: var(--smoke); opacity: 0.85; }
.cta__actions { margin-top: 2.25rem; }

.cta__aside {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(1.5rem, 2.5vw, 2rem);
}

/* Ivory surface so the Talk2View lockup keeps its contrast — both
   official variants use a teal glyph, which would vanish on the Teal
   field. Square corners, per the brand's web/product UI rule. */
.cta__brandcard {
  display: grid;
  place-items: center;
  width: min(100%, 320px);
  padding: clamp(2.25rem, 4vw, 3.25rem) clamp(1.5rem, 3vw, 2.5rem);
  background: var(--ivory);
}
.cta__brandcard img { width: min(100%, 210px); height: auto; }

/* Cross-link to the parent product. Deliberately quieter than the badge —
   it is the secondary path out of this section, and the download is the
   primary one. Smoke on Teal is the brand's safe pairing at 8.4:1. */
.cross-link {
  display: block;
  max-width: 300px;
  padding-top: 1.25rem;
  border-top: 1px solid rgba(2, 28, 37, 0.25);
  color: var(--smoke);
  text-decoration: none;
}
.cross-link__eyebrow {
  display: block;
  font-size: var(--t-micro);
  font-weight: 500;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  opacity: 0.7;
}
.cross-link__text {
  display: block;
  margin-top: 0.6rem;
  font-size: var(--t-small);
  line-height: 1.45;
  opacity: 0.9;
}
.cross-link__cta {
  display: inline-flex;
  align-items: center;
  gap: 0.45em;
  margin-top: 0.9rem;
  font-size: var(--t-small);
  font-weight: 500;
  border-bottom: 1px solid rgba(2, 28, 37, 0.35);
  padding-bottom: 0.15em;
  transition: border-color 0.2s ease;
}
.cross-link__cta::after { content: '↗'; transition: transform 0.2s ease; }
.cross-link:hover .cross-link__cta { border-bottom-color: var(--smoke); }
.cross-link:hover .cross-link__cta::after { transform: translate(2px, -2px); }

@media (max-width: 900px) {
  .cta__grid { grid-template-columns: 1fr; text-align: center; }
  .cta__lead { margin-inline: auto; }
  .cta__actions { display: flex; justify-content: center; }
  .cta__brandcard { width: min(100%, 280px); }
  .cross-link { text-align: center; }
  .cross-link__cta { margin-inline: auto; }
}

/* ============================================================
   App Store badge

   Apple's own artwork, used unmodified per their identity guidelines:
   never recoloured, redrawn or restyled, and given clear space of at
   least 10% of its height. The link is padded rather than the image so
   the hit area is comfortable without encroaching on that clear space.
   ============================================================ */

.badge {
  display: inline-block;
  padding: 4px;
  line-height: 0;
  transition: opacity 0.2s ease, transform 0.2s ease;
}
.badge img { width: auto; height: 44px; }
.badge:hover { opacity: 0.85; }
.badge:active { transform: translateY(1px); }

/* ============================================================
   Footer
   ============================================================ */

.footer {
  padding-block: clamp(3rem, 5vw, 4rem) 2rem;
  background: var(--smoke);
  color: var(--ivory);
}
.footer__top {
  display: flex;
  flex-wrap: wrap;
  gap: 1.5rem 2rem;
  align-items: center;
  justify-content: space-between;
  padding-bottom: 2rem;
  border-bottom: 1px solid rgba(242, 240, 236, 0.13);
}
.footer__brand img { height: 1.5rem; width: auto; }

.footer__links { display: flex; flex-wrap: wrap; gap: 1.5rem 2rem; }
.footer__links a {
  font-size: var(--t-small);
  color: var(--ivory);
  text-decoration: none;
  transition: color 0.18s ease;
}
.footer__links a:hover { color: var(--highlight); }

.footer__bottom {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 2rem;
  justify-content: space-between;
  padding-top: 1.5rem;
  font-size: var(--t-micro);
  color: var(--stone);
}
.footer__bottom a { text-decoration: none; }
.footer__bottom a:hover { color: var(--highlight); }

/* ============================================================
   Scroll reveal
   ============================================================ */

/* Hidden only when JS is present to animate them back in — otherwise a
   script failure would leave the whole page blank. main.js sets .js on
   <html> as its first statement. GSAP owns the transform from there.

   Desktop-only: on phones content is never parked at opacity 0 waiting
   for a ScrollTrigger — iOS Safari's URL-bar viewport games can leave a
   trigger unfired, and an invisible section is a far worse failure than
   a skipped fade. The tweens still run there; they just animate from
   visible instead of hidden. */
@media (min-width: 821px) {
  .js [data-reveal] { opacity: 0; }
}

/* SplitText masks each line in its own clipping wrapper so lines can
   rise from behind their own baseline. */
.split-line { display: block; overflow: hidden; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
  /* Belt and braces — main.js also bails out of every animation. */
  .js [data-reveal] { opacity: 1 !important; transform: none !important; }
}
