/* ============================================================================
   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; }
}

/* ---------------------------------------------------------------------------
   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; }
.seat-row .seat-count { font: 700 15px/1 var(--font-num); color: var(--ink-1); min-width: 60px; }

/* 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;
}
/* 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 26px;
  font: 800 clamp(30px, 4vw, 52px)/1.1 var(--font-ui);
  letter-spacing: -.028em;
}
.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; }
  .mkt-nav .nav-links { 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));
}
