/* ============================================================================
   POWER DIALER DESIGN SYSTEM — marketing.css
   The MARKETING TIER: landing pages, pricing, auth. Load AFTER the core
   pack (tokens → core → components → motion → this).

   The product tier is a control surface worked 8 hours straight; this tier
   is looked at once, so it earns a slower clock and a small set of licensed
   effects the product is never allowed:

     SHADER FIELD   a slow warm fog with traveling light streaks (calls
                    going out) behind the hero. WebGL, painted by
                    marketing.js, static frame under reduced motion.
     SPOTLIGHT      a cursor-tracked pool of light on feature/pricing cards
                    that reveals a dot-grid texture inside the light.
     GLASS          frosted panels (nav, badges) over the shader field —
                    a web approximation, solid fallback provided.
     SHEEN          a one-shot diagonal light sweep on hover.
     TILT           ≤3° pointer tilt on ONE hero object.
     REVEAL         rise-and-fade on scroll entry, 500ms, stagger ≤6.
     GRAIN          a fixed film-grain wash. pointer-events: none.

   Everything else (buttons, fields, chips, toggles, odometers, toasts)
   comes from the core pack unchanged. Colors are the same brand tokens.
   ========================================================================== */

:root {
  /* The marketing clock. Product tiers (90/140/180) still apply to any
     product component embedded on the page. */
  --t-hero: 700ms;          /* hero entrance                                */
  --t-reveal: 500ms;        /* scroll reveals                               */
  --t-spot: 200ms;          /* spotlight opacity ease-in                    */
  --t-sheen: 650ms;         /* the light sweep                              */

  --text-hero: clamp(38px, 5.2vw, 68px);
  --text-section: clamp(26px, 3vw, 38px);

  --glass-bg: rgba(245, 243, 239, .58);
  --glass-edge: rgba(255, 255, 255, .5);
  --spot-size: 340px;
}
[data-theme="dark"] {
  --glass-bg: rgba(38, 36, 33, .55);
  --glass-edge: rgba(255, 255, 255, .08);
}

/* ---------------------------------------------------------------------------
   PAGE SCAFFOLD
--------------------------------------------------------------------------- */
.mkt { overflow-x: clip; }
.mkt-wrap { max-width: 1200px; margin: 0 auto; padding: 0 32px; }
.mkt-section { padding: 96px 0; }
.mkt-section-title {
  font: 800 var(--text-section)/1.15 var(--font-ui);
  letter-spacing: -.025em;
  color: var(--ink-1);
  max-width: 620px;
  text-wrap: balance;
}
.mkt-section-sub {
  font: 500 15px/1.6 var(--font-ui);
  color: var(--ink-2);
  max-width: 560px;
  margin-top: 12px;
}

/* ---------------------------------------------------------------------------
   NAV — glass over the shader field. 64px, one line.
   (Frosted-glass web approximation; solid fallback below.)
--------------------------------------------------------------------------- */
.mkt-nav {
  position: sticky;
  top: 0;
  z-index: var(--z-sticky);
  height: 64px;
  display: flex;
  align-items: center;
  gap: 22px;
  padding: 0 28px;
  background: var(--glass-bg);
  -webkit-backdrop-filter: blur(18px) saturate(160%);
  backdrop-filter: blur(18px) saturate(160%);
  border-bottom: 1px solid var(--glass-edge);
  box-shadow: 0 1px 0 rgb(var(--sh-dark) / .06);
}
.mkt-nav .brand {
  display: flex;
  align-items: center;
  gap: 10px;
  font: 800 15px/1 var(--font-ui);
  letter-spacing: -.02em;
  color: var(--ink-1);
}
.mkt-nav .nav-links { display: flex; gap: 4px; margin-left: 8px; }
.nav-link {
  height: 32px;
  padding: 0 12px;
  display: inline-flex;
  align-items: center;
  border-radius: var(--r-control);
  font: 600 13px/1 var(--font-ui);
  color: var(--ink-2);
  transition: background-color var(--t-press) var(--ease), color var(--t-press) var(--ease);
}
.nav-link:hover { background: color-mix(in srgb, var(--surface-sunk) 65%, transparent); color: var(--ink-1); text-decoration: none; }
@media (prefers-reduced-transparency: reduce) {
  .mkt-nav { background: var(--surface-raised); -webkit-backdrop-filter: none; backdrop-filter: none; }
}

/* ---------------------------------------------------------------------------
   THE HEADER SIGN-IN — dead centre of the bar.

   Asked for: "Add the button to sign in. Make the button seem like the bright
   light/dark on the homepage. Put it at the top, at the very center of the
   header."

   CENTRED MEANS CENTRED. The bar is a flex row whose left cluster (brand +
   links) and right cluster (the buy button) are nowhere near the same width,
   so a third flex item lands wherever the two of them leave it — which is not
   the middle. It is absolutely positioned against the bar instead (`.mkt-nav`
   is sticky, so it is already the containing block) and translated off its own
   half-width, which puts it on the bar's true centre line at every width and
   leaves the existing brand/links/CTA balance exactly as it was.

   THE TREATMENT IS THE THEME TOGGLE'S, because that is the control he pointed
   at. That button is `.btn.ghost`: no surface at rest, and on hover it takes
   the raised surface, the raise-1 shadow and the darker ink. This borrows that
   ladder wholesale — same states, same tokens, same durations, one size up so
   it reads at 64px of chrome — and carries the toggle's ◐ as a drawn mark
   rather than a typed glyph, so it looks like the light/dark control's sibling
   without claiming to be another one. The mark is --ink-1 against the hairline
   it is ringed in; there is no new colour anywhere in here.

   It is an <a href="/login">. Middle-click, ⌘-click and Enter all have to work
   on the one control in the header that leaves the marketing site.
--------------------------------------------------------------------------- */
.mkt-nav .nav-signin {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  height: 34px;
  padding: 0 15px;
  gap: 9px;
  border-radius: var(--r-control);
  font: 600 13px/1 var(--font-ui);
  letter-spacing: -.005em;
  text-decoration: none;
  white-space: nowrap;
}
.mkt-nav .nav-signin:hover { text-decoration: none; }
/* The ◐, drawn. Half lit, half not — the same two-tone read as the glyph in
   the footer's theme button, at a size that survives being 13px tall. */
.nav-signin .signin-mark {
  width: 14px;
  height: 14px;
  flex: none;
  border-radius: var(--r-full);
  background: linear-gradient(90deg, var(--ink-2) 0 50%, transparent 50% 100%);
  box-shadow: inset 0 0 0 1.5px var(--ink-2);
  transition: background-image var(--t-state) var(--ease),
              box-shadow var(--t-state) var(--ease);
}
.mkt-nav .nav-signin:hover .signin-mark {
  background-image: linear-gradient(90deg, var(--ink-1) 0 50%, transparent 50% 100%);
  box-shadow: inset 0 0 0 1.5px var(--ink-1);
}

/* ---------------------------------------------------------------------------
   HERO — asymmetric split over the shader field.
--------------------------------------------------------------------------- */
.mkt-hero {
  position: relative;
  min-height: 88dvh;
  display: flex;
  align-items: center;
  padding: 48px 0 72px;
}
.mkt-hero-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr);
  gap: 56px;
  align-items: center;
  width: 100%;
}
.mkt-eyebrow {
  font: 500 11px/1 var(--font-num);
  letter-spacing: .14em;
  text-transform: uppercase;
  /* ink-3 measured 3.09:1 on --surface-base and 2.55:1 over the hero field.
     ink-2 is 6.61:1 light / 6.12:1 dark. An 11px label has to clear 4.5:1. */
  color: var(--ink-2);
}
.mkt-hero h1 {
  margin: 14px 0 0;
  font: 800 var(--text-hero)/1.04 var(--font-ui);
  letter-spacing: -.03em;
  color: var(--ink-1);
  text-wrap: balance;
}
.mkt-hero h1 .accent-word { color: var(--accent); }
.mkt-hero .hero-sub {
  margin-top: 18px;
  font: 500 16px/1.6 var(--font-ui);
  color: var(--ink-2);
  max-width: 46ch;
}
.mkt-hero .hero-ctas { display: flex; gap: 12px; margin-top: 26px; }

/* Hero entrance: label → headline → sub → CTAs → preview, one cascade. */
.hero-enter { opacity: 0; transform: translateY(14px); }
.mkt.is-loaded .hero-enter {
  opacity: 1;
  transform: translateY(0);
  transition: opacity var(--t-hero) var(--ease), transform var(--t-hero) var(--ease);
  transition-delay: calc(var(--i, 0) * 90ms);
}

/* The shader field + grain sit behind everything in the hero. */
.mkt-field {
  position: absolute;
  inset: 0;
  z-index: -1;
  overflow: hidden;
  pointer-events: none;
}
.mkt-field canvas { width: 100%; height: 100%; display: block; }
/* Two fades, both painted in --surface-base so the field always melts into
   the page it sits on.
     180deg  the original: the field dissolves into the section below.
      90deg  NEW: a legibility ramp under the copy column. Measured over the
             live field (sunset palette, strength .34): the hero sub sat at
             5.46:1 worst case in light and 2.45:1 MEDIAN in dark, because
             mixing a dark ground toward a light palette lifts the whole
             field to a mid-brown. On the ramp the copy column reads on
             --surface-base itself: 6.61:1 light, 6.12:1 dark. The field is
             untouched from ~64% rightward, which is where the preview card
             and the open right margin are. */
.mkt-field::after {
  content: "";
  position: absolute;
  inset: 0;
  background:
    linear-gradient(90deg,
      var(--surface-base) 0%,
      var(--surface-base) 34%,
      color-mix(in srgb, var(--surface-base) 55%, transparent) 50%,
      transparent 70%),
    linear-gradient(180deg,
      transparent 0%,
      transparent 55%,
      var(--surface-base) 96%);
}
/* Dark carries a longer ramp: the same field is far brighter over a dark
   ground, so the copy needs the base further out (measured above). */
[data-theme="dark"] .mkt-field::after {
  background:
    linear-gradient(90deg,
      var(--surface-base) 0%,
      var(--surface-base) 52%,
      color-mix(in srgb, var(--surface-base) 55%, transparent) 66%,
      transparent 80%),
    linear-gradient(180deg,
      transparent 0%,
      transparent 55%,
      var(--surface-base) 96%);
}
.mkt-grain {
  position: fixed;
  inset: 0;
  z-index: 70;
  pointer-events: none;
  opacity: .28;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.5'/%3E%3C/svg%3E");
  mix-blend-mode: overlay;
}
[data-theme="dark"] .mkt-grain { opacity: .2; }

/* The hero's product preview card — the one tilt object on the page. */
.hero-preview {
  border-radius: var(--r-shell);
  background: var(--surface-base);
  box-shadow: var(--shadow-raise-2), 0 24px 60px rgb(var(--sh-dark) / .12);
  overflow: hidden;
  transform-style: preserve-3d;
  will-change: transform;
}

/* ---------------------------------------------------------------------------
   LOGO MARQUEE — the page's single marquee. Real marks, currentColor.
--------------------------------------------------------------------------- */
.mkt-logos { padding: 34px 0; border-top: 1px solid var(--hairline-soft); border-bottom: 1px solid var(--hairline-soft); }
.mkt-logos .logos-label {
  font: 500 10px/1 var(--font-num);
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--ink-2);          /* ink-4 measured 1.99:1 here. AA needs 4.5. */
  text-align: center;
  margin-bottom: 20px;
}
.fx-marquee { overflow: hidden; position: relative; }
/* The track is exactly two identical halves and travels -50%, so a half has
   to be at least as wide as the viewport or the loop shows a hole at the
   wrap. It did: at 1440 a half measured 672px. Each half is now pinned to
   100vw with space-around doing the spacing, the track carries no gap of its
   own, and -50% lands the second half exactly where the first started. */
.fx-marquee .marquee-track {
  display: flex;
  align-items: center;
  gap: 0;
  width: max-content;
  animation: mkt-marquee 30s linear infinite;
}
.fx-marquee:hover .marquee-track { animation-play-state: paused; }
.fx-marquee::before, .fx-marquee::after {
  content: "";
  position: absolute;
  top: 0; bottom: 0;
  width: 90px;
  z-index: 1;
  pointer-events: none;
}
.fx-marquee::before { left: 0;  background: linear-gradient(90deg, var(--surface-base), transparent); }
.fx-marquee::after  { right: 0; background: linear-gradient(270deg, var(--surface-base), transparent); }
.marquee-logo {
  display: flex;
  align-items: center;
  gap: 10px;
  color: var(--ink-2);          /* 14px/700 names: ink-4 was 1.99:1 */
  transition: color var(--t-state) var(--ease);
  flex: none;
}
.marquee-logo:hover { color: var(--ink-1); }
.marquee-logo svg { width: 26px; height: 26px; fill: currentColor; }
.marquee-logo .logo-name { font: 700 14px/1 var(--font-ui); letter-spacing: -.01em; }
@keyframes mkt-marquee { to { transform: translateX(-50%); } }
@media (prefers-reduced-motion: reduce) {
  .fx-marquee .marquee-track { animation: none; flex-wrap: wrap; justify-content: center; width: 100%; }
  .fx-marquee .marquee-half { min-width: 0; flex-wrap: wrap; justify-content: center; }
  .fx-marquee .marquee-half:last-child { display: none; }
}
.marquee-half {
  display: flex;
  align-items: center;
  gap: 72px;
  flex: none;
  min-width: 100vw;
  justify-content: space-around;
}

/* ---------------------------------------------------------------------------
   SPOTLIGHT CARD — the cursor carries a pool of warm light across the card;
   inside the pool, a dot-grid texture surfaces. Border brightens toward the
   cursor. (Adapted from the aceternity Card Spotlight; rebuilt on tokens.)
   JS sets --mx / --my per card.
--------------------------------------------------------------------------- */
.fx-spot {
  position: relative;
  border-radius: var(--r-panel);
  background: var(--surface-base);
  border: 1px solid var(--hairline);
  overflow: hidden;
  transition: border-color var(--t-spot) var(--ease);
}
/* the light pool */
.fx-spot::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  opacity: 0;
  transition: opacity var(--t-spot) var(--ease);
  background: radial-gradient(var(--spot-size) circle at var(--mx, 50%) var(--my, 50%),
    color-mix(in srgb, var(--sh-light) 60%, transparent) 0%,
    color-mix(in srgb, var(--accent) 7%, transparent) 34%,
    transparent 70%);
  pointer-events: none;
}
/* the dot-grid that only exists inside the light */
.fx-spot::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  opacity: 0;
  transition: opacity var(--t-spot) var(--ease);
  background-image: radial-gradient(color-mix(in srgb, var(--ink-3) 45%, transparent) 1px, transparent 1.4px);
  background-size: 14px 14px;
  -webkit-mask-image: radial-gradient(calc(var(--spot-size) * .8) circle at var(--mx, 50%) var(--my, 50%), #000 0%, transparent 68%);
  mask-image: radial-gradient(calc(var(--spot-size) * .8) circle at var(--mx, 50%) var(--my, 50%), #000 0%, transparent 68%);
  pointer-events: none;
}
.fx-spot:hover::before, .fx-spot:hover::after { opacity: 1; }
.fx-spot:hover { border-color: color-mix(in srgb, var(--accent) 28%, var(--hairline)); }
.fx-spot > * { position: relative; z-index: 1; }
[data-theme="dark"] .fx-spot::before {
  background: radial-gradient(var(--spot-size) circle at var(--mx, 50%) var(--my, 50%),
    rgba(255, 255, 255, .06) 0%,
    color-mix(in srgb, var(--accent) 12%, transparent) 30%,
    transparent 70%);
}

/* ---------------------------------------------------------------------------
   FEATURE BENTO — asymmetric. Cells are spotlight cards; two cells carry
   real visual variation (a shader cell, a live-component cell).
--------------------------------------------------------------------------- */
.mkt-bento {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--gutter);
  margin-top: 40px;
}
.mkt-bento .cell { padding: 24px; min-height: 220px; display: flex; flex-direction: column; }
.mkt-bento .cell-wide { grid-column: span 2; }
.cell h3 { margin: 0 0 8px; font: 700 17px/1.3 var(--font-ui); letter-spacing: -.015em; color: var(--ink-1); }
.cell p { margin: 0; font: 500 13px/1.6 var(--font-ui); color: var(--ink-2); max-width: 52ch; }
.cell .cell-demo { margin-top: auto; padding-top: 18px; }
/* shader cell: canvas fills the background, ink flips for legibility.
   The solid dark fallback keeps the cell legible even with WebGL off. */
.cell-shader { position: relative; border: 0; background: #262421; }
.cell-shader canvas { position: absolute; inset: 0; width: 100%; height: 100%; }
.cell-shader h3, .cell-shader p { position: relative; color: #EDEAE3; }
.cell-shader p { color: rgba(237, 234, 227, .75); }
.cell-shader::before, .cell-shader::after { display: none; }

/* ---------------------------------------------------------------------------
   BLOOM FIELD — the CSS-only soft-gradient background (no WebGL needed):
   four blurred brand-tint blobs + a vignette; pair with .mkt-grain for film
   grain. (Adapted from 21st's featured Bloom Field gradient, rebuilt on
   brand tints.) Use where the shader field is too heavy: auth sides,
   settings headers, empty states.
--------------------------------------------------------------------------- */
.fx-bloom {
  position: relative;
  overflow: hidden;
  isolation: isolate;
}
.fx-bloom::before {
  content: "";
  position: absolute;
  inset: -20%;
  z-index: -1;
  background:
    radial-gradient(38% 34% at 22% 24%, color-mix(in srgb, var(--accent) 14%, transparent), transparent 70%),
    radial-gradient(42% 40% at 82% 18%, color-mix(in srgb, var(--ringing) 13%, transparent), transparent 70%),
    radial-gradient(46% 44% at 76% 82%, color-mix(in srgb, var(--connected) 11%, transparent), transparent 70%),
    radial-gradient(36% 32% at 16% 78%, color-mix(in srgb, var(--failed) 8%, transparent), transparent 70%),
    var(--surface-base);
  filter: blur(42px);
}
.fx-bloom::after {  /* vignette */
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: radial-gradient(120% 100% at 50% 45%, transparent 55%,
    color-mix(in srgb, rgb(var(--sh-dark)) 7%, transparent));
  pointer-events: none;
}

/* ---------------------------------------------------------------------------
   BORDER BEAM — a light that travels the border of one featured container.
   (Adapted from 21st border-beam components: a rotating conic trace masked
   to the border ring.) Marketing-tier ambient loop: ONE per view, on the
   thing that deserves the eye (featured price card, the onboarding step).
--------------------------------------------------------------------------- */
@property --beam-angle { syntax: "<angle>"; initial-value: 0deg; inherits: false; }
.fx-beam { position: relative; }
.fx-beam::before {
  content: "";
  position: absolute;
  inset: -1px;
  border-radius: inherit;
  padding: 1.5px;
  background: conic-gradient(from var(--beam-angle),
    transparent 0 82%,
    color-mix(in srgb, var(--accent) 30%, transparent) 90%,
    var(--accent) 96%,
    color-mix(in srgb, var(--sh-light) 80%, var(--accent)) 98%,
    transparent 100%);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask-composite: exclude;
  animation: mkt-beam 5s linear infinite;
  pointer-events: none;
}
@keyframes mkt-beam { to { --beam-angle: 360deg; } }
@media (prefers-reduced-motion: reduce) { .fx-beam::before { animation: none; opacity: .5; } }

/* ---------------------------------------------------------------------------
   TEXT SHIMMER — a light sweeping through WORKING-state text only (motion
   as "still busy", never decoration on static copy). From ibelick's Text
   Shimmer / 21st Agent Elements, on brand ink.
--------------------------------------------------------------------------- */
.fx-shimmer-text {
  background: linear-gradient(90deg,
    var(--ink-3) 0%, var(--ink-3) 38%,
    var(--ink-1) 50%,
    var(--ink-3) 62%, var(--ink-3) 100%);
  background-size: 220% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  animation: mkt-shimmer-text 1.8s linear infinite;
}
@keyframes mkt-shimmer-text { from { background-position: 120% 0; } to { background-position: -120% 0; } }
@media (prefers-reduced-motion: reduce) { .fx-shimmer-text { animation: none; color: var(--ink-2); background: none; } }

/* ---------------------------------------------------------------------------
   SHEEN — one diagonal light sweep on hover. One-shot, never loops.
--------------------------------------------------------------------------- */
.fx-sheen { position: relative; overflow: hidden; }
.fx-sheen .sheen-beam {
  position: absolute;
  top: -20%;
  left: -70%;
  width: 45%;
  height: 140%;
  transform: skewX(-18deg) translateX(0);
  background: linear-gradient(90deg, transparent,
    color-mix(in srgb, var(--sh-light) 55%, transparent) 50%, transparent);
  pointer-events: none;
  opacity: 0;
}
.fx-sheen:hover .sheen-beam { animation: mkt-sheen var(--t-sheen) var(--ease) 1; }
@keyframes mkt-sheen {
  0%   { opacity: 0; transform: skewX(-18deg) translateX(0); }
  15%  { opacity: 1; }
  100% { opacity: 0; transform: skewX(-18deg) translateX(420%); }
}

/* ---------------------------------------------------------------------------
   SCROLL REVEAL — rise + fade on entry. JS adds .is-in via IO.
--------------------------------------------------------------------------- */
.fx-reveal { opacity: 0; transform: translateY(18px); }
.fx-reveal.is-in {
  opacity: 1;
  transform: translateY(0);
  transition: opacity var(--t-reveal) var(--ease), transform var(--t-reveal) var(--ease);
  transition-delay: calc(var(--i, 0) * 70ms);
}
@media (prefers-reduced-motion: reduce) {
  .fx-reveal, .hero-enter { opacity: 1 !important; transform: none !important; }
}

/* ---------------------------------------------------------------------------
   THE DARK BAND — the page's one deliberate theme switch. A full-bleed
   [data-theme="dark"] wrapper: every token inside flips automatically.
--------------------------------------------------------------------------- */
.mkt-band {
  background: var(--surface-base);
  border-top: 1px solid var(--hairline);
  border-bottom: 1px solid var(--hairline);
  padding: 88px 0;
}
.mkt-band .band-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 56px;
  align-items: center;
}
.block-chips { display: flex; flex-wrap: wrap; gap: 8px; }

/* ---------------------------------------------------------------------------
   PRICING — one real price, an odometer that reacts, and a checkout that
   feels like the product. Cards are spotlight surfaces.
--------------------------------------------------------------------------- */
.mkt-pricing-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr);
  gap: var(--gutter);
  margin-top: 40px;
  align-items: start;
}
.price-card { padding: 28px; }
.price-card .plan-name { font: 700 15px/1 var(--font-ui); color: var(--ink-1); display: flex; align-items: center; gap: 10px; }
.price-line { display: flex; align-items: baseline; gap: 8px; margin: 18px 0 4px; }
.price-line .cur { font: 600 20px/1 var(--font-num); color: var(--ink-2); }
.price-line .num { font: 800 56px/1 var(--font-num); letter-spacing: -.02em; color: var(--ink-1); font-variant-numeric: tabular-nums; }
.price-line .per { font: 500 13px/1.3 var(--font-ui); color: var(--ink-2); }
.price-note { font: 500 12px/1.5 var(--font-ui); color: var(--ink-2); min-height: 18px; }
.plan-list { list-style: none; margin: 20px 0 24px; padding: 0; display: flex; flex-direction: column; gap: 9px; }
.plan-list li { display: flex; gap: 9px; font: 500 13px/1.5 var(--font-ui); color: var(--ink-2); }
.plan-list li::before { content: "✓"; color: var(--connected); font-weight: 700; flex: none; }
.seat-row { display: flex; align-items: center; gap: 12px; margin: 4px 0 16px; flex-wrap: wrap; }
/* text-align was missing, and it is the whole of the "it's off-center" bug:
   `min-width` blockifies a flex item, so the count sat hard against the LEFT
   of a 60px box while the + key floated 45px away from it. Centring it costs
   nothing and fixes the owner Billing stepper at the same time. */
.seat-row .seat-count {
  font: 700 15px/1 var(--font-num);
  color: var(--ink-1);
  min-width: 60px;
  text-align: center;
  font-variant-numeric: tabular-nums;
}

/* --------------------------------------------------------------------------
   THE SEAT STEPPER — marketing tier only.

   Reported: "the whole little plus-minus thing looks a little AI-ish. It's
   off-center, and it looks not big enough. Not enough attention is drawn to
   it." All three were literally true. The centring is fixed above. The size
   was --control-h, 28px — the value this pack spends on a table's row action
   — sitting under a 56px price number, and it is in fact the control that
   decides what the customer pays.

   The vocabulary is the pack's own and is the only one the rules allow:
   SUNK = INPUT, so the track a quantity is set in is sunk; RAISED =
   ACTIONABLE, so the two keys that change it stay raised. A raised key inside
   a sunk track is not nested elevation — NEVER NEST ELEVATION forbids
   raised-in-raised and sunk-in-sunk — it is exactly what `.seg` already does
   with its thumb, one size up. And `.price-card` is a FLAT surface
   (--surface-base + hairline, see .fx-spot), so nothing is sunk in sunk.
-------------------------------------------------------------------------- */
.mkt .seat-row { gap: 16px; margin: 6px 0 14px; }
.mkt .seat-stepper {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 5px;
  border-radius: 14px;
  background: var(--surface-sunk);
  box-shadow: var(--shadow-sink-soft);
}
.mkt .seat-stepper .btn.btn-icon {
  width: 42px;
  height: 42px;
  border-radius: 10px;
  padding: 0;
  font: 600 22px/1 var(--font-ui);
}
.mkt .seat-stepper .seat-count {
  min-width: 66px;
  font: 800 26px/1 var(--font-num);
  letter-spacing: -.01em;
}
/* THE TOTAL OWNS ITS OWN LINE.

   Reported: "when I raised the seat count … it just looks cramped. You just
   push the two things together." Literally what the markup did. The total was
   an inline span sitting to the RIGHT of the stepper with a flex spacer
   between them, so the wider the number grew the less air was left: at three-
   and four-figure totals ($1,161 at nine seats, $6,450 at fifty) the spacer
   collapsed to nothing and the two controls met at the 16px gap. A layout
   whose breathing room is whatever the price did not use is a layout that
   gets tighter exactly as the number gets more important.

   So the readout is a row of its own now, in the same label-left /
   amount-right shape the buy card beside it already uses for every figure it
   quotes. It is FLAT = DATA — one hairline and two type sizes, no surface of
   its own, nothing raised, nothing sunk — so it cannot crowd the stepper at
   any seat count, and the two cards read as one system rather than two.
   `.seat-row` keeps the stepper and nothing else. */
.mkt .seat-total {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  margin: 0 0 18px;
  padding: 13px 0 0;
  border-top: 1px solid var(--hairline-soft);
  font: 600 13px/1 var(--font-ui);
  color: var(--ink-2);
}
.mkt .seat-total .seat-amount { display: inline-flex; align-items: baseline; }
.mkt .seat-total .t-mono {
  /* 24px, one step under the 26px seat count: the stepper is the control and
     keeps primacy; this is its readout. */
  font: 700 24px/1 var(--font-num);
  color: var(--ink-1);
  font-variant-numeric: tabular-nums;
  letter-spacing: -.01em;
}
.mkt .seat-total .per { font: 500 12px/1 var(--font-ui); color: var(--ink-2); margin-left: 3px; }

/* The buy card's period suffix and its secondary outreach line. Contact is
   still on the page — it is just no longer the thing being asked for. */
.pay-total .amount .per { font: 500 12px/1 var(--font-ui); color: var(--ink-2); margin-left: 2px; }
.pay-alt {
  margin: 14px 0 0;
  text-align: center;
  font: 500 12px/1.6 var(--font-ui);
  color: var(--ink-2);
}
.pay-alt a { color: var(--ink-1); text-decoration: underline; text-underline-offset: 2px; }
.close-alt {
  margin: 18px 0 0;
  font: 500 13px/1.6 var(--font-ui);
  color: var(--ink-2);
}
.close-alt a { color: var(--ink-1); text-decoration: underline; text-underline-offset: 2px; }

/* Checkout card. In production the flow is Stripe-hosted checkout; this is
   the house style for any on-page payment UI. */
.pay-card { padding: 28px; }
.pay-card .pay-title { font: 700 15px/1 var(--font-ui); margin-bottom: 18px; display: flex; align-items: center; gap: 8px; }
.pay-total {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  padding: 12px 0;
  margin-top: 6px;
  border-top: 1px solid var(--hairline-soft);
  font: 600 13px/1 var(--font-ui);
  color: var(--ink-2);
}
.pay-total .amount { font: 700 22px/1 var(--font-num); color: var(--ink-1); font-variant-numeric: tabular-nums; }
.pay-secure {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  margin-top: 12px;
  font: 500 11px/1 var(--font-num);
  letter-spacing: .04em;
  color: var(--ink-2);
}
.pay-secure svg { width: 30px; height: 14px; fill: currentColor; }

/* ---------------------------------------------------------------------------
   AUTH — the owner sign-in card. Denial gets a 240ms nudge; reps never see
   a password at all (they get links), and the card says so.
--------------------------------------------------------------------------- */
.mkt-auth-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 400px;
  gap: 64px;
  align-items: center;
}
.auth-card {
  padding: 30px 28px;
  border-radius: var(--r-shell);
  background: var(--surface-base);
  box-shadow: var(--shadow-raise-2);
}
.auth-card .auth-title { font: 700 18px/1.2 var(--font-ui); letter-spacing: -.015em; margin: 0 0 4px; }
.auth-card .auth-sub { font: 500 12px/1.5 var(--font-ui); color: var(--ink-2); margin-bottom: 20px; }
.auth-card .stack { gap: 14px; }
.auth-alt {
  margin-top: 18px;
  padding-top: 14px;
  border-top: 1px solid var(--hairline-soft);
  font: 500 12px/1.6 var(--font-ui);
  color: var(--ink-2);
  text-align: center;
}

/* THE SEQUENCE beside the sign-in card.

   The heading here used to be "Owners sign in. Reps just get a link.", which
   described the shape of the product in the week the pack was drawn: an owner
   signed in and wired everything up by hand. Reps still get links — that half
   is as true as it ever was — but the buying and setup either side of it are
   not the same flow any more, and a page that narrates a sequence the product
   stopped running is worse than a page that narrates none.

   FLAT = DATA, four rows, hairlines between them. The number is set in the
   numeral face so the column reads as an order and not as decoration; it is
   --ink-3, which is the pack's label ink and clears AA on every marketing
   ground. Nothing here is raised: it is a list of facts, not four buttons. */
.flow-steps { list-style: none; margin: 22px 0 0; padding: 0; counter-reset: flow; }
.flow-steps li {
  display: flex;
  gap: 14px;
  align-items: baseline;
  padding: 12px 0;
  border-top: 1px solid var(--hairline-soft);
  font: 500 13px/1.55 var(--font-ui);
  color: var(--ink-2);
}
.flow-steps li:first-child { border-top: 0; padding-top: 0; }
.flow-steps li::before {
  counter-increment: flow;
  content: counter(flow, decimal-leading-zero);
  flex: none;
  font: 600 11px/1.55 var(--font-num);
  letter-spacing: var(--track-caps);
  color: var(--ink-3);
}
.flow-steps b { color: var(--ink-1); font-weight: 700; }
/* denial nudge: 240ms, ±3px, three beats. state feedback, not spectacle */
.fx-nudge { animation: mkt-nudge 240ms var(--ease) 1; }
@keyframes mkt-nudge {
  0%, 100% { transform: translateX(0); }
  25% { transform: translateX(-3px); }
  55% { transform: translateX(3px); }
  80% { transform: translateX(-2px); }
}

/* ---------------------------------------------------------------------------
   CLOSING CTA + FOOTER
--------------------------------------------------------------------------- */
.mkt-close {
  text-align: center;
  padding: 110px 0 120px;
}
.mkt-close h2 {
  margin: 0 0 18px;
  font: 800 clamp(30px, 4vw, 52px)/1.1 var(--font-ui);
  letter-spacing: -.028em;
}
/* THE CLOSING QUOTE — the same numbers the calculator is showing, restated at
   the bottom of the page where the decision actually gets made.

   Asked for: "the Start Today looks really good, but it should update live
   with what you have. If I move the counter to five seats … it should show
   that on the Start Today." A closing CTA that says "Start dialing" under a
   calculator the reader has just spent thirty seconds setting is a CTA that
   has forgotten what they told it. This line and the button above the fold
   are fed by ONE render pass off ONE seat count, so there is no arithmetic
   here that can disagree with the price card.

   FLAT = DATA: no surface, no rule, just ink. Colour would make it a state. */
.close-quote {
  /* Block-level flex, not inline-flex: the CTA under it is itself an
     inline-flex .btn, so an inline quote and the button share a line and the
     button stops reading as the thing to press. The quote gets its own line
     and the button gets its own. */
  margin: 0 0 26px;
  font: 600 14px/1 var(--font-ui);
  color: var(--ink-2);
  display: flex;
  align-items: baseline;
  gap: 10px;
  flex-wrap: wrap;
  justify-content: center;
}
.close-quote .sep { color: var(--ink-4); }
.close-quote .close-amount { display: inline-flex; align-items: baseline; }
.close-quote .t-mono {
  font: 700 20px/1 var(--font-num);
  color: var(--ink-1);
  font-variant-numeric: tabular-nums;
  letter-spacing: -.01em;
}
.close-quote .per { font: 500 12px/1 var(--font-ui); color: var(--ink-2); margin-left: 3px; }
.mkt-footer {
  border-top: 1px solid var(--hairline);
  padding: 26px 0;
  display: flex;
  align-items: center;
  gap: 16px;
  font: 500 12px/1 var(--font-ui);
  color: var(--ink-2);
}

/* ---------------------------------------------------------------------------
   MARKETING-TIER CONTRAST FLOOR
   This block used to raise four classes because --ink-3 measured 3.09:1 on
   --surface-base. It no longer does: the token was fixed at source and is now
   4.52:1 light / 4.64:1 dark base (4.24:1 light sunk and 4.22:1 dark raised
   being the worst ground in each theme). So the three --ink-3 overrides —
   .t-label-mono, .field .hint and .capsule-chev — are redundant and gone; a
   marketing label is now the same ink as the product label it shares a class
   with. What is left is --ink-4, still 1.99:1 light / 3.16:1 dark, and correct
   to leave that way: it is the disabled / not-attempted token. Two marketing
   classes reach for it to set copy a stranger reads once rather than to mark
   something unavailable, so those two are still raised here.
--------------------------------------------------------------------------- */
.mkt .brand-capsule .capsule-n { color: var(--ink-2); }   /* --ink-4, 1.99:1 */
.mkt .lost-hero .lost-code { color: var(--ink-2); }       /* --ink-4, 1.99:1 */

/* ---------------------------------------------------------------------------
   RESPONSIVE COLLAPSE — every multi-column layout goes single-column < 900px.
--------------------------------------------------------------------------- */
/* ===========================================================================
   PAGE LAYOUTS — pricing, auth, legal (pages/marketing/*)
   ========================================================================= */

/* ---- Auth split: shader pane left, card right. ---- */
.auth-split {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 520px;
  min-height: 100dvh;
}
.auth-side {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  padding: 28px 36px 36px;
  overflow: hidden;
}
.auth-side .mkt-field { z-index: 0; }
.auth-side .mkt-field::after { background: none; }
.auth-side > * { position: relative; z-index: 1; }
.auth-side .side-line {
  font: 800 clamp(26px, 2.6vw, 38px)/1.15 var(--font-ui);
  letter-spacing: -.025em;
  color: var(--ink-1);
  max-width: 420px;
  text-wrap: balance;
}
.auth-side .side-proof { display: flex; flex-direction: column; gap: 10px; max-width: 430px; }
.auth-pane {
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 48px 56px;
  background: var(--surface-base);
  border-left: 1px solid var(--hairline);
}
.auth-pane .auth-card { box-shadow: none; padding: 0; background: none; border-radius: 0; }
.auth-foot {
  margin-top: 26px;
  font: 500 12px/1.6 var(--font-ui);
  color: var(--ink-3);
}
@media (max-width: 900px) {
  .auth-split { grid-template-columns: 1fr; }
  .auth-side { display: none; }
  .auth-pane { padding: 40px 24px; border-left: 0; }
}

/* ---- Signup steps: two labels, one sliding ink bar. ---- */
.steps { display: flex; gap: 20px; margin-bottom: 22px; }
.step {
  display: flex;
  align-items: center;
  gap: 8px;
  font: 600 12px/1 var(--font-ui);
  color: var(--ink-4);
  transition: color var(--t-state) var(--ease);
}
.step .step-dot {
  width: 18px; height: 18px;
  border-radius: var(--r-full);
  display: grid;
  place-items: center;
  font: 600 10px/1 var(--font-num);
  background: var(--surface-sunk);
  box-shadow: var(--shadow-sink-soft);
  color: var(--ink-3);
  transition: background-color var(--t-state) var(--ease), color var(--t-state) var(--ease);
}
.step.is-active { color: var(--ink-1); }
.step.is-active .step-dot { background: var(--accent); color: var(--on-accent); box-shadow: var(--shadow-knob); }
.step.is-done .step-dot { background: var(--connected); color: var(--on-accent); box-shadow: none; }

/* ---- Pricing page: the included matrix + FAQ. ---- */
.included-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 0;
  border-top: 1px solid var(--hairline);
  margin-top: 36px;
}
.included-col { padding: 20px 22px 24px 0; border-right: 1px solid var(--hairline-soft); }
.included-col:last-child { border-right: 0; }
.included-col + .included-col { padding-left: 22px; }
.included-col h4 {
  margin: 0 0 12px;
  font: 600 11px/1.2 var(--font-ui);
  letter-spacing: var(--track-caps);
  text-transform: uppercase;
  color: var(--ink-2);
}
.faq { max-width: 860px; margin-top: 36px; }
.faq .collapse-trigger { padding: 14px 0; font-size: 14px; border-bottom: 1px solid var(--hairline-soft); }
.faq .collapse-inner .t-body { padding: 4px 0 16px; }

/* ---- Legal template: sticky TOC + prose. Deliberately quiet. ---- */
.legal-grid {
  display: grid;
  grid-template-columns: 220px minmax(0, 1fr);
  gap: 56px;
  padding: 56px 0 96px;
}
.legal-toc { position: sticky; top: 88px; align-self: start; display: flex; flex-direction: column; gap: 2px; }
.legal-toc a {
  padding: 6px 10px;
  border-radius: var(--r-chip);
  font: 500 12px/1.4 var(--font-ui);
  color: var(--ink-3);
  transition: background-color var(--t-press) var(--ease), color var(--t-press) var(--ease);
}
.legal-toc a:hover { background: color-mix(in srgb, var(--surface-sunk) 60%, transparent); color: var(--ink-1); text-decoration: none; }
.legal h1 { font: 800 34px/1.15 var(--font-ui); letter-spacing: -.025em; margin: 0 0 6px; }
.legal .legal-meta { font: 500 12px/1 var(--font-num); color: var(--ink-3); margin-bottom: 34px; }
.legal h2 { font: 700 19px/1.3 var(--font-ui); letter-spacing: -.015em; margin: 40px 0 10px; }
.legal p, .legal li { font: 500 14px/1.75 var(--font-ui); color: var(--ink-2); max-width: 68ch; }
.legal ul { padding-left: 20px; }
.legal .legal-note {
  margin-top: 48px;
  padding: 12px 14px;
  border-radius: 10px;
  background: var(--ringing-wash);
  box-shadow: inset 3px 0 0 var(--ringing);
  font: 500 12px/1.6 var(--font-ui);
  color: var(--ink-2);
  max-width: 68ch;
}
@media (max-width: 900px) {
  .legal-grid { grid-template-columns: 1fr; gap: 24px; }
  .legal-toc { position: static; flex-direction: row; flex-wrap: wrap; }
}

/* QA shot mode: viewport-height sections become content-height so tall
   headless captures stay representative. Never used by real visitors. */
.is-shotmode .mkt-hero { min-height: auto; padding: 88px 0 96px; }

@media (max-width: 900px) {
  /* The included matrix is a set of vertical rules at desktop. Stacked, those
     rules become a stray line down the right of every block and a 22px indent
     on all but the first, so the columns become rows with a rule on top. */
  .included-col { border-right: 0; padding: 20px 0 22px; }
  .included-col + .included-col { padding-left: 0; border-top: 1px solid var(--hairline-soft); }

  .mkt-wrap { padding: 0 20px; }
  .mkt-hero { min-height: auto; padding: 40px 0 56px; }
  .mkt-hero-grid, .mkt-band .band-grid, .mkt-pricing-grid, .mkt-auth-grid {
    grid-template-columns: 1fr;
    gap: 32px;
  }
  .mkt-bento { grid-template-columns: 1fr; }
  .mkt-bento .cell-wide { grid-column: auto; }
  .mkt-section { padding: 64px 0; }
}

/* --- the nav's own ladder, now that something lives in the middle of it ---
   Measured, not guessed: the link row ends at a fixed 424px (it is packed
   against the left padding), and the centred pill starts at half the viewport
   minus its own 42px, so the two meet at 957px. Everything below is that one
   number with room either side of it. */
@media (max-width: 1120px) {
  /* First the bar tightens — 8px off the outer padding and 3px off every link
     buys back 40px before anything has to be dropped. */
  .mkt-nav { gap: 14px; padding: 0 18px; }
  .mkt-nav .nav-link { padding: 0 9px; }
  .mkt-nav .nav-signin { padding: 0 11px; gap: 7px; }
}
@media (max-width: 1000px) {
  /* Then the link row goes, before it can reach under the pill. It used to go
     at 900px; the 100px it moved up is the price of a centred control, and it
     is a fair one — the links are all in the footer, "Pricing" is the section
     the page scrolls to anyway, and the two things a stranger came to the bar
     for (sign in, start) are the two that survive. A centred element that
     collides is worse than one link row fewer. */
  .mkt-nav .nav-links { display: none; }
}
@media (max-width: 560px) {
  /* Brand + pill + buy button stop fitting on one 64px line here. The sign-in
     is the one of the three that is ALSO in the footer, so it is the one that
     goes; the buy button is the whole point of the page and stays. */
  .mkt-nav .nav-signin { display: none; }
}


/* ===========================================================================
   OWNER PANEL — SURFACE PLANES
   Opt-in via `<body class="ui-planes">`. Carried today by the six owner pages
   and the rep help page. The dialer does not load this file at all, and no
   page without the class is touched.

   THE PROBLEM, MEASURED. `.panel` filled with --surface-base and `body`
   filled with --surface-base, so every card in the owner panel was painted
   the same colour as the ground it sat on: 1.000:1, separated by one hairline
   measuring 1.226:1. Nothing read as layered, and a screen that is 94% one
   near-white tone reads as "bright white" however warm the hex is. (It is
   warm — but --surface-base is L* 94.15 at C* 2.16, and 2.16 of chroma at
   that lightness is below where the eye calls something cream.)

   THE FIX IS THE DIALER'S FIX, APPLIED TO THIS TIER. tokens.css now carries
   --surface-shell, a ground below --surface-sunk that nothing is ever written
   on (C* 4.90 — more than twice the chroma of the pane it sits under), plus
   --shadow-float, a plane elevation distinct from the pressable raise/sink
   pair. app.css spends both on the dialer's four panes. This does the same
   thing to the owner shell, with the SAME recipe, so the two halves of the
   product are cut from one material:

       plane L*        shell 87.49 · sunk 91.67 · base 94.15 · raised 95.89
       pane vs ground  1.000:1 -> 1.192:1   (dL* 0 -> 6.66)   light
                       1.000:1 -> 1.198:1   (dL* 0 -> 8.32)   dark

   NOT ONE TEXT PAIRING INSIDE A PANE MOVES. Sidebar, topbar and every panel
   keep --surface-base exactly, so --ink-3's hard-won 4.52:1 and every
   measurement taken against a panel still holds to the digit.

   The one place text meets the new ground is the page header row, and in
   LIGHT --ink-3 on the shell measures 3.79:1 — under AA. So tertiary ink that
   sits directly on the ground steps up one level to --ink-2: 5.54:1 light,
   7.33:1 dark. (Dark's --ink-3 on the shell is 5.56:1 and would have passed;
   it takes the same step so the themes do not diverge in hierarchy.)
   ========================================================================= */
body.ui-planes { background: var(--surface-shell); }

/* The shell becomes the ground, with the same top-lit gradient the dialer
   shell carries, and the chrome floats on it with a real gutter. The content
   column is left OPEN ground: its panels are the panes there, and wrapping it
   in a pane of its own would put base on base again — the exact bug. */
body.ui-planes .app-shell {
  --pane-gap: 10px;
  padding: var(--pane-gap);
  gap: var(--pane-gap);
  background: var(--surface-shell)
              linear-gradient(180deg,
                color-mix(in srgb, var(--surface-shell) 62%, var(--surface-base)) 0%,
                var(--surface-shell) 30%);
}
body.ui-planes .app-main { gap: var(--pane-gap); }

/* One recipe, shared verbatim with app.css's dialer panes. --shadow-float has
   no white light-source edge and no 2-3px offset, so a pane can never be
   mistaken for something pressable and the raised buttons inside it are still
   the only actionable objects on screen. Rule 1 holds. */
body.ui-planes .sidebar,
body.ui-planes .topbar,
body.ui-planes .panel,
body.ui-planes .card {
  border-radius: var(--r-panel);
  background: var(--surface-base);
  box-shadow: var(--shadow-float),
              inset 0 0 0 1px var(--glass-ring),
              inset 0 1px 0 var(--glass-edge);
}
/* The nav count on the ACTIVE sidebar item sits on --surface-sunk, where
   --ink-3 is 4.24:1 — under AA, and it is a number an owner reads. --ink-2 is
   6.20:1 light / 6.72:1 dark. (Pre-existing: the active item has always been
   sunk. Fixed here for the pages this file reaches; components.css should
   take the same step for the rep pages.) */
body.ui-planes .sidebar-item.is-active .count { color: var(--ink-2); }

/* Panes separate by gutter and edge now, so the rules that used to do it come
   off — including the topbar's sticky positioning, which has nothing to hold
   on to inside a fixed shell. */
body.ui-planes .app-shell .sidebar { border-right: 0; }
body.ui-planes .topbar { border-bottom: 0; position: static; }
body.ui-planes .panel,
body.ui-planes .card { border: 0; }

/* The shell already holds 10px off every edge, so the content column gives
   back the same amount: 10 + 16 = the 26px side inset the page had before,
   and 10 + 12 = the 22px top. The rhythm is unchanged; only the ground under
   it is. */
body.ui-planes .app-content {
  background: transparent;
  padding: 12px 16px 48px;
}

/* Tertiary ink that meets the ground steps up one level: --ink-3 measures
   3.79:1 on --surface-shell in light, and 5.54:1 as --ink-2. Written as a
   step-then-restore pair rather than a list of the places text happens to sit
   today: everything --ink-3-ish inside the content column steps up, and then
   anything inside a PANE is put straight back, because --ink-3 on
   --surface-base is still the measured 4.52:1 / 4.64:1 it always was. A new
   empty state or hint dropped on the ground next month is covered without
   anyone remembering this rule exists.

   Specificity does the restoring: :is() takes the weight of one class, so the
   pane rule is (0,4,1) against the ground rule's (0,3,1). */
body.ui-planes .app-content
  :is(.page-sub, .t-label-mono, .section-label, .hint, .dim, .t-ink-3,
      .empty-label, .empty-hint) { color: var(--ink-2); }
body.ui-planes .app-content :is(.panel, .card)
  :is(.page-sub, .t-label-mono, .section-label, .hint, .dim, .t-ink-3,
      .empty-label, .empty-hint) { color: var(--ink-3); }

/* Tab and segment labels are the other --ink-3 that lands on bare ground —
   the settings tab rail sits above its panel, not inside it, so its five
   inactive labels went 4.52:1 -> 3.79:1 the moment the ground dropped. The
   ACTIVE one is --ink-1 and is left alone; hover already goes to --ink-2, so
   this only removes the difference between resting and hovered, which the
   sliding indicator and the bolded active label were already carrying. */
body.ui-planes .app-content :is(.tabs-item, .seg-item):not(.is-active) { color: var(--ink-2); }
/* A tab rail inside a pane is back on --surface-base, where --ink-3 is still
   4.52:1, so it restores. A SEGMENT does not: `.seg`'s track is
   --surface-sunk wherever it sits, and --ink-3 on sunk is 4.24:1 — under AA
   before any of this. It keeps --ink-2 (6.20:1 / 6.72:1) everywhere, which
   costs nothing: the active item is --ink-1 at weight 700 with the raised
   thumb under it, and hover was already --ink-1. */
body.ui-planes .app-content :is(.panel, .card) .tabs-item:not(.is-active) { color: var(--ink-3); }

/* A BANNER IS A PANE TOO, and its wash is translucent.
   components.css measured every banner label on the wash composited over
   --surface-base: `.banner.info`'s --accent label reads 4.6:1 there. Drop the
   same banner on the new ground and the composite darkens to (208,206,209),
   which takes that label to 3.88:1 — under AA, caused entirely by moving the
   ground. So a banner in the content column gets an opaque --surface-base
   underlay and the wash rides on top of it: the composite is byte-for-byte
   the one the pack measured, and the banner reads as a pane against the
   shell for free. Harmless where a banner already sits inside a pane — the
   underlay is the colour that was behind it anyway. */
body.ui-planes .app-content .banner      { background: var(--surface-base) linear-gradient(var(--ringing-wash), var(--ringing-wash)); }
body.ui-planes .app-content .banner.info { background: var(--surface-base) linear-gradient(var(--accent-wash), var(--accent-wash)); }
body.ui-planes .app-content .banner.ok   { background: var(--surface-base) linear-gradient(var(--connected-wash), var(--connected-wash)); }
body.ui-planes .app-content .banner.err  { background: var(--surface-base) linear-gradient(var(--failed-wash), var(--failed-wash)); }

/* A table inside a floating pane gets its header as a rail rather than a bare
   row of 9px caps: --surface-sunk sits 1.076:1 under the pane fill, and the
   caps step to --ink-2 because --ink-3 on sunk is 4.24:1. 6.20:1 after,
   6.72:1 dark. */
body.ui-planes .panel .table thead th {
  background: var(--surface-sunk);
  color: var(--ink-2);
}
body.ui-planes .panel .table thead th:first-child { border-radius: var(--r-chip) 0 0 var(--r-chip); }
body.ui-planes .panel .table thead th:last-child  { border-radius: 0 var(--r-chip) var(--r-chip) 0; }

/* The shell's own scrollbars belong to the content column, not to the ground. */
@media (max-width: 760px) {
  body.ui-planes .app-shell { --pane-gap: 6px; }
}


/* ===========================================================================
   CREDENTIAL STRINGS — a link an owner reads out loud.

   `.badge` is 10px uppercase mono with .06em tracking, which is right for a
   state word (LIVE, PAUSED) and wrong for the one string a rep's whole
   onboarding depends on: it rendered a dialer link as
   LOCALHOST:4100/DIALER#AT=ADK_41F6D5BE… — a value that will not authenticate
   if anyone retypes what they see. `.mono-link` is the opt-out: real case,
   readable size, no tracking, and it may wrap rather than being clipped to a
   width nobody chose.
   ========================================================================= */
.badge.mono-link {
  text-transform: none;
  font-size: 11.5px;
  letter-spacing: 0;
  color: var(--ink-1);
  height: auto;
  min-height: var(--badge-h);
  padding: 4px 8px;
  white-space: normal;
  overflow-wrap: anywhere;
  line-height: 1.45;
  text-align: left;
  user-select: all;         /* one click selects the whole credential */
}


/* ===========================================================================
   HELP — the answer list and the ask composer.
   ========================================================================= */
.help-item + .help-item { border-top: 1px solid var(--hairline-soft); }
.help-item .collapse-inner > p:first-child { margin-top: 0; }
.help-item .collapse-inner { padding-bottom: 4px; }
.help-item .collapse-trigger { font-size: 13px; }
/* A help table carries PROSE, not row data. `.table` is built for 30px
   nowrap rows, which pushed the explanation column off the right edge behind
   a horizontal scrollbar — the one thing a reference table must not do. Inside
   a help answer the cells wrap and grow, and the first column is held narrow
   so the explanation gets the room. */
.help-item .table { margin-top: 2px; table-layout: fixed; width: 100%; }
.help-item .table th,
.help-item .table td {
  white-space: normal;
  height: auto;
  padding: 8px 10px;
  line-height: 1.5;
  vertical-align: top;
}
.help-item .table th:first-child { width: 30%; }
.help-item .table th:first-child:last-of-type { width: auto; }
.help-item .table th.n { width: 84px; }
.help-item .table td.n { white-space: nowrap; }
.help-item .table th:nth-child(3) { width: 22%; }

/* The context block in the ask modal: it is quoted machine text, so it is
   mono on the sunk ground every read-only value in the product uses. It
   scrolls rather than stretching the modal on a long user-agent string. */
.ask-context {
  margin: 0;
  padding: 10px 12px;
  border-radius: var(--r-control);
  background: var(--surface-sunk);
  box-shadow: var(--shadow-sink-soft);
  color: var(--ink-2);
  font: var(--text-mono-s);
  line-height: 1.7;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  max-height: 168px;
  overflow-y: auto;
}

/* The CSV drop zone is a real <button> wrapping the .empty component, so it
   is keyboard-reachable and announces itself; the dashed edge is the only
   thing that says "drop here". `is-over` is the 90ms press-tier confirmation
   that the drag is in the right place — no scale, no colour fill. */
.empty[id="importDrop"] { transition: box-shadow var(--t-press) var(--ease), border-color var(--t-press) var(--ease); }
/* Once a file is chosen this label IS the file's name. `.empty-label` is 11px
   mono caps, which turned west-renewals.csv into WEST-RENEWALS.CSV — the same
   mistake the dialer link made, in a smaller hole. Real case, real size. */
.empty[id="importDrop"] .empty-label {
  text-transform: none;
  letter-spacing: 0;
  font: 600 13px/1.4 var(--font-ui);
  color: var(--ink-1);
}
.empty[id="importDrop"]:hover { border-color: var(--ink-4); }
.empty[id="importDrop"].is-over { border-color: var(--accent); box-shadow: var(--focus-ring); }
.empty[id="importDrop"]:focus-visible { box-shadow: var(--focus-ring); }


/* ===========================================================================
   DESCRIBED MENU — an overflow menu whose items explain themselves.

   For rows of controls where the difference between two of them is the whole
   question: Revoke a link vs Deactivate a rep, Rotate vs Sign out. A 26px
   `nowrap` menu item cannot carry that, so `.menu-desc` items are two lines,
   and the ones that take something away wear --failed-ink on the TITLE only —
   a whole item in red reads as an error, not as a warning.

   Measured on the menu's own --surface-raised ground:
     .mi-title  --ink-1      15.68:1 light · 11.72:1 dark
     .mi-title  --failed-ink  5.44:1 light ·  4.56:1 dark   (components.css §00)
     .mi-sub    --ink-2       6.91:1 light ·  5.57:1 dark
     .menu-label --ink-2      6.91:1 light ·  5.57:1 dark   (--ink-3 was 4.22:1
                                                             dark — under AA)
   ========================================================================= */
.menu.align-right { left: auto; right: 0; }
.menu.menu-desc { min-width: 244px; max-width: 288px; }
.menu-desc .menu-item {
  height: auto;
  align-items: flex-start;
  padding: 8px 9px;
  white-space: normal;
}
.menu-desc .menu-label { color: var(--ink-2); }
.menu-desc .mi-text { display: block; min-width: 0; }
.menu-desc .mi-title { display: block; font: 600 12px/1.35 var(--font-ui); color: var(--ink-1); }
.menu-desc .menu-item.danger .mi-title { color: var(--failed-ink); }
.menu-desc .mi-sub {
  display: block;
  margin-top: 2px;
  font: 500 11px/1.45 var(--font-ui);
  color: var(--ink-2);
}


/* ===========================================================================
   BANNER LABELS — one measured correction, and a request to move it upstream.

   components.css §00 derives --ringing-ink / --connected-ink / --failed-ink
   against --surface-sunk, which is right for a badge or a chip. A BANNER
   label does not sit on sunk: it sits on the banner's own accent wash, which
   composites to a different ground — and on dark, to a much darker one. So
   the labels that clear AA everywhere else do not clear it here. Measured on
   the composited wash (over --surface-base):

       light  .banner.ok    --connected-ink   4.41:1   FAIL
       dark   .banner       --ringing-ink     4.17:1   FAIL
       dark   .banner.err   --failed-ink      4.15:1   FAIL
       dark   .banner.info  --accent          4.15:1   FAIL
       dark   .banner.ok    --connected-ink   4.57:1   passes, left alone
       light  .banner / .err / .info                   pass, left alone

   Corrected below with the SAME recipe §00 uses — the accent moved toward the
   shadow ink in light, toward --ink-1 in dark — so no new hue enters the
   system. After: 4.68 / 5.69 / 4.61 / 4.88:1.

   This is scoped to `.ui-planes` because it is not this file's call to make
   for the whole product. THE REAL FIX IS IN components.css §00: the ink
   variants need a second derivation for text on a wash, and then this block
   deletes itself.
   ========================================================================= */
body.ui-planes .banner.ok .banner-label {
  color: color-mix(in srgb, var(--connected) 74%, rgb(var(--sh-dark)));
}
[data-theme="dark"] body.ui-planes .banner.ok .banner-label { color: var(--connected-ink); }
[data-theme="dark"] body.ui-planes .banner:not(.ok):not(.err):not(.info) .banner-label {
  color: color-mix(in srgb, var(--ringing) 88%, var(--ink-1));
}
[data-theme="dark"] body.ui-planes .banner.err .banner-label {
  color: color-mix(in srgb, var(--failed) 82%, var(--ink-1));
}
[data-theme="dark"] body.ui-planes .banner.info .banner-label {
  color: color-mix(in srgb, var(--accent) 84%, var(--ink-1));
}
