/* ============================================================================
   TAPN Entry — Site styles
   Components are ports of AttendeeOS primitives. Origin noted per block.

   ── The three rules this file enforces ─────────────────────────────────────
   1. SPACE. Every margin, padding and gap resolves to --s-1…--s-9. There are
      no hand-typed pixel values below and none in the markup. Sections all
      take --section-y; no section overrides it.
   2. TYPE. Seven sizes, three roles (see tokens.css). Nunito is the page's
      own voice — h1/h2 and the two numeric displays. Everything inside a
      component is DM Sans.
   3. COLOR. Marketing surfaces use ink / muted / line / white + one accent
      (teal). The status ramps (ok / warn / bad) are not used out here — they
      belong to the demos, where they encode real state. Colored elevation
      (--sh-teal) is reserved for the primary button and the active tab.
   ========================================================================== */

/* ---- Reset ------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

html {
  scroll-behavior: smooth;
  /* website_spec.md §G.3.4 — focus must not land under the sticky nav */
  scroll-padding-top: calc(var(--nav-h) + var(--s-5));
  -webkit-text-size-adjust: 100%;
}

body {
  font-family: var(--font);
  font-size: var(--fs-body);
  line-height: 1.6;
  color: var(--ink);
  background: var(--surface-wash);
  background-attachment: fixed;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

/* Any explicit `display` beats the UA's `[hidden] { display: none }`, which
   would leave the inactive demo panels (display:grid) visible. */
[hidden] { display: none !important; }

img, svg, video { display: block; max-width: 100%; }
img { height: auto; }
a { color: inherit; text-decoration: none; }
button, input, select, textarea { font: inherit; color: inherit; }
ul, ol { list-style: none; }

/* Focus. The product never draws focus rings (it is touch/mouse first), so
   this is a web-only addition — required by WCAG 2.2, drawn in brand teal. */
:focus-visible {
  outline: 3px solid var(--teal);
  outline-offset: 3px;
  border-radius: var(--s-1);
}

.skip {
  position: absolute; left: var(--s-4); top: -100px; z-index: 200;
  background: var(--navy); color: #fff; padding: var(--s-3) var(--s-5);
  border-radius: var(--r-pill); font-weight: 800; font-size: var(--fs-sm);
  transition: top var(--t) var(--ease);
}
.skip:focus { top: var(--s-4); }

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ---- Layout ------------------------------------------------------------
   One container, one measure. Nothing breaks out of it — content aligns to
   the same two edges from the nav to the footer.                           */
.wrap { width: 100%; max-width: var(--measure); margin: 0 auto; padding: 0 var(--gutter); }
.wrap--narrow { max-width: var(--measure-narrow); }

section { padding: var(--section-y) 0; position: relative; }

/* Section head. Left-aligned in every section on the page — the only
   centered block is the closing CTA, which is a full-bleed card rather than
   a section head, so the two never alternate mid-scroll. */
.sec-head { max-width: 44rem; margin-bottom: var(--s-7); }
.sec-head .sub { margin-top: var(--s-4); }

/* Vertical run of blocks inside a section. This is what replaces the
   `style="margin-top:18px"` / `26px` / `38px` / `52px` that used to set the
   spacing between a grid and whatever followed it. */
.sec-body { display: grid; gap: var(--s-5); }
.sec-body--tight { gap: var(--s-4); }
.sec-body--loose { gap: var(--s-7); }

/* ---- Type -------------------------------------------------------------- */
/* Kicker — signin.php:294-301 .welcome-kicker. The one label size. */
.kicker {
  display: inline-block;
  font-size: var(--fs-label);
  font-weight: 800;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--teal);
  margin-bottom: var(--s-3);
}

/* 700, not the 600 this carried under Fraunces. A serif's stems and
   brackets read heavier than their nominal weight; swapping in a rounded
   sans at the same number left h2s lighter than the DM Sans callouts
   beneath them. 700 restores the weight the page had. Tracking tightens
   for the same reason — Nunito's round forms sit wider than Fraunces'. */
h1, h2 {
  font-family: var(--font-display);
  font-weight: 700;
  line-height: 1.06;
  letter-spacing: -.02em;
  text-wrap: balance;
}
h1 { font-size: var(--fs-d1); }
h2 { font-size: var(--fs-d2); }

/* h3/h4 are component-level and sit in the body role — see tokens.css. */
h3 { font-family: var(--font); font-size: var(--fs-body); font-weight: 800; line-height: 1.3; }
h4 { font-family: var(--font); font-size: var(--fs-label); font-weight: 800; line-height: 1.3; }

/* The one sub-head that keeps the display face: a second beat inside a long
   section (the live dashboard, the three demo panels). */
.h-sub { font-family: var(--font-display); font-size: var(--fs-d3); font-weight: 700; letter-spacing: -.02em; line-height: 1.15; }
.h-sub + .checks, .h-sub + .sub { margin-top: var(--s-5); }

/* A section head that introduces a panel rather than a section. */
.sec-head--sm { margin-bottom: var(--s-5); }

/* The product's supporting-copy convention: app_dashboard.css:775-782 */
.sub {
  color: var(--muted);
  font-size: var(--fs-lead);
  line-height: 1.55;
  max-width: 60ch;
  text-wrap: pretty;
}
/* Emphasis inside supporting copy steps up to full ink rather than adding a
   colour. This is the only weight/colour change allowed inside a .sub. */
.sub b { color: var(--ink); font-weight: 700; }

.muted { color: var(--muted); }
.accent { color: var(--teal); }
.fine { font-size: var(--fs-label); color: var(--muted); }
.fine a, .inline-link { color: var(--teal-deep); font-weight: 700; text-decoration: underline; }

/* Gradient wordmark — app_dashboard.css:404-409 .sidebar-powered-by__brand */
.grad-word {
  font-weight: 900;
  background: linear-gradient(135deg, var(--teal-deep), var(--teal), var(--teal-2));
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent;
}

/* ---- Buttons ----------------------------------------------------------- */
/* app_dashboard.css:698-735 — pill, 800 weight, 1px hairline, -1px hover lift. */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--s-2);
  border: 1px solid var(--line);
  background: var(--card);
  color: var(--ink);
  padding: var(--s-3) var(--s-5);
  min-height: 46px;              /* WCAG 2.2 SC 2.5.8 — floor is 24px, we use 46 */
  border-radius: var(--r-pill);
  font-size: var(--fs-sm);
  font-weight: 800;
  cursor: pointer;
  user-select: none;
  white-space: nowrap;
  transition: transform var(--t-fast) var(--ease),
              box-shadow var(--t-fast) var(--ease),
              border-color var(--t-fast) var(--ease),
              background var(--t-fast) var(--ease);
}
.btn:hover { transform: translateY(-1px); box-shadow: var(--sh-hover); }
.btn:active { transform: translateY(0); }

/* The only two colored shadows on the page live here and on .tab. */
.btn--primary {
  border-color: rgba(15,187,179,.35);
  background: var(--brand-grad);
  color: #fff;
  box-shadow: var(--sh-teal);
}
.btn--primary:hover { box-shadow: 0 14px 28px rgba(15,187,179,.26); }

.btn--ghost { background: var(--card-glass); box-shadow: var(--sh-sm); }

.btn--lg { padding: var(--s-4) var(--s-6); min-height: 54px; font-size: var(--fs-body); }
.btn--sm { padding: var(--s-2) var(--s-4); min-height: 38px; font-size: var(--fs-label); }
.btn--block { width: 100%; }

.btn-row { display: flex; flex-wrap: wrap; gap: var(--s-3); }
.btn-row--center { justify-content: center; }

/* ---- Cards ------------------------------------------------------------- */
/* app_dashboard.css:671-680 */
.card {
  background: var(--card-glass);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--sh);
  display: flex;
  flex-direction: column;
  padding: var(--s-5);
}
.card__title { font-size: var(--fs-body); font-weight: 800; margin-bottom: var(--s-2); }
.card__body { color: var(--muted); font-size: var(--fs-sm); line-height: 1.6; }
.card .actions { margin-top: auto; padding-top: var(--s-4); }
.card > .checks { margin-top: var(--s-4); }

/* Hover lift matches .event-list__item / .app-item (app_dashboard.css:833, 893) */
.card--lift { transition: transform var(--t) var(--ease), box-shadow var(--t) var(--ease), border-color var(--t) var(--ease); }
.card--lift:hover { transform: translateY(-2px); border-color: var(--line-strong); box-shadow: var(--sh-lg); }

/* Top accent bar — signin.php:249-256 .card::before */
.card--accent { position: relative; overflow: hidden; }
.card--accent::before {
  content: ''; position: absolute; inset: 0 0 auto 0; height: var(--s-1);
  background: linear-gradient(90deg, var(--teal), var(--navy));
}

/* Tints — app_dashboard.css:784-800. Only the teal tint is used on marketing
   surfaces; the other three stay defined for the ported demo components. */
.card--teal   { background: linear-gradient(180deg, var(--tint-teal),   var(--card-glass)); border-color: var(--line-teal); }
.card--orange { background: linear-gradient(180deg, var(--tint-orange), var(--card-glass)); border-color: rgba(245,158,11,.15); }
.card--blue   { background: linear-gradient(180deg, var(--tint-blue),   var(--card-glass)); border-color: rgba(59,130,246,.15); }
.card--pink   { background: linear-gradient(180deg, var(--tint-pink),   var(--card-glass)); border-color: rgba(236,112,99,.18); }

/* app_dashboard.css:802-811 .card-hero, deepened.
   The shipped gradient ends at rgba(20,212,203,.5), which composites to
   ~#85e7e3 — white body copy on that measures 1.44:1, i.e. illegible. This
   runs teal-ink → navy instead: the same pairing already used by
   .card--accent::before, and white clears AA across the whole sweep
   (4.85:1 at the light end, 15.97:1 at the dark). */
.card--hero {
  background:
    radial-gradient(380px 200px at 20% 10%, rgba(15,187,179,.28), transparent 60%),
    linear-gradient(135deg, var(--teal-ink), var(--navy));
  color: #fff;
  border-color: rgba(255,255,255,.18);
  box-shadow: 0 20px 44px rgba(10,142,136,.24);
}
.card--hero .card__body, .card--hero .muted, .card--hero .sub { color: rgba(255,255,255,.92); }
.card--hero .kicker { color: #fff; }
/* On the teal card, primary reads as the solid white fill; the secondary has
   to invert or the two buttons compete. */
.card--hero .btn--ghost {
  background: rgba(255,255,255,.14);
  border-color: rgba(255,255,255,.55);
  color: #fff;
  box-shadow: none;
}
.card--hero .btn--ghost:hover { background: rgba(255,255,255,.24); box-shadow: 0 10px 22px rgba(2,6,23,.14); }

/* Icon chip — app_dashboard.css:334-347 .nav-icon */
.chip {
  width: 42px; height: 42px; border-radius: var(--r-sm);
  display: inline-grid; place-items: center; flex: none;
  background: rgba(15,187,179,.08);
  color: var(--teal-deep);
  margin-bottom: var(--s-4);
}
.chip--solid { background: var(--brand-grad); color: #fff; }

/* ---- Status pill -------------------------------------------------------
   app_dashboard.css:930-959. Only .pill--teal appears on marketing surfaces.
   ok / warn / bad stay defined for the demos, which use them for real state. */
.pill {
  display: inline-flex; align-items: center; gap: var(--s-2);
  padding: var(--s-2) var(--s-3); border-radius: var(--r-pill);
  font-weight: 800; font-size: var(--fs-label);
  border: 1px solid transparent;
}
.pill i { width: var(--s-2); height: var(--s-2); border-radius: var(--r-pill); flex: none; }
.pill--ok   { background: var(--ok-bg);   border-color: var(--ok-line);   color: var(--ok-ink); }
.pill--ok   i { background: var(--ok); }
.pill--warn { background: var(--warn-bg); border-color: var(--warn-line); color: var(--warn-ink); }
.pill--warn i { background: var(--warn); }
.pill--bad  { background: var(--bad-bg);  border-color: var(--bad-line);  color: var(--bad-ink); }
.pill--bad  i { background: var(--bad); }
.pill--teal { background: var(--tint-teal); border-color: var(--line-teal); color: var(--teal-deep); }
.pill--teal i { background: var(--teal); }

/* ---- Nav --------------------------------------------------------------- */
/* Horizontal port of the AttendeeOS sidebar. Hover/active behaviour is
   .nav-item verbatim (app_dashboard.css:274-295). */
.nav {
  position: sticky; top: 0; z-index: 100;
  height: var(--nav-h);
  display: flex; align-items: center;
  background: color-mix(in srgb, var(--card) 78%, transparent);
  backdrop-filter: blur(16px) saturate(1.4);   /* app_dashboard.css:84 sidebar */
  -webkit-backdrop-filter: blur(16px) saturate(1.4);
  border-bottom: 1px solid var(--line);
}
.nav__inner { display: flex; align-items: center; gap: var(--s-4); width: 100%; max-width: var(--measure); margin: 0 auto; padding: 0 var(--gutter); }

/* Lockup follows signin.php:265-280 .tapn-logo-fallback. The shipped `entry`
   logo asset already contains a TAPN mark of its own, so pairing the two would
   print the brand twice; the product name is set in type instead. */
.brand { display: flex; align-items: center; gap: var(--s-3); flex: none; margin-right: auto; }
.brand__mark { height: 21px; width: auto; }
.brand__div { width: 1px; height: 18px; background: var(--line-strong); }
.brand__name {
  font-family: var(--font-display);
  font-size: var(--fs-d3); font-weight: 700; letter-spacing: -.3px;
  color: var(--ink); line-height: 1;
}

.nav__links { display: flex; align-items: center; gap: var(--s-1); }
.nav__link {
  display: inline-flex; align-items: center;
  padding: var(--s-2) var(--s-3); min-height: 40px;
  border-radius: var(--r-sm);
  border: 1px solid transparent;
  color: var(--muted);
  font-size: var(--fs-sm); font-weight: 700;
  transition: transform var(--t) var(--ease), background var(--t) var(--ease),
              border-color var(--t) var(--ease), color var(--t) var(--ease);
}
.nav__link:hover { background: color-mix(in srgb, var(--card) 72%, transparent); border-color: var(--line); color: var(--ink); transform: translateY(-1px); }
/* Unqualified: `page` on the standalone pages, `true` from the scrollspy. */
.nav__link[aria-current] {
  background: var(--card); border-color: var(--line-teal);
  color: var(--ink); font-weight: 800;
}
.nav__ctas { display: flex; align-items: center; gap: var(--s-2); flex: none; }
.nav__cta { flex: none; }
.nav__signin { flex: none; border-color: var(--line); }
.nav__toggle { display: none; }
.nav__link--mobile-only { display: none; }

@media (max-width: 900px) {
  .nav__links {
    position: fixed; inset: var(--nav-h) 0 auto 0;
    flex-direction: column; align-items: stretch; gap: var(--s-1);
    padding: var(--s-4) var(--gutter) var(--s-5);
    background: color-mix(in srgb, var(--card) 97%, transparent);
    backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px);
    border-bottom: 1px solid var(--line);
    box-shadow: var(--sh);
    transform: translateY(-8px); opacity: 0; pointer-events: none;
    transition: opacity var(--t) var(--ease), transform var(--t) var(--ease);
  }
  .nav__links[data-open='true'] { transform: none; opacity: 1; pointer-events: auto; }
  .nav__link { min-height: 48px; font-size: var(--fs-body); }
  .nav__toggle {
    display: inline-grid; place-items: center;
    width: 44px; height: 44px; border-radius: var(--r-sm);
    border: 1px solid var(--line); background: var(--card); cursor: pointer;
  }
  .nav__ctas { display: none; }
  .nav__link--mobile-only { display: inline-flex; }
}

/* ---- Hero --------------------------------------------------------------
   Centered headline, real event badges floating around it. The hero takes
   the same --section-y as every other section, plus extra top/bottom room
   for the floating badges to clear the headline on desktop.                */
.hero {
  padding: calc(var(--section-y) + var(--s-4)) 0;
  min-height: 92vh; min-height: 92dvh;
  display: flex; align-items: center;
  position: relative; overflow: hidden;
}
.hero > .wrap { width: 100%; }
.hero__center { max-width: var(--measure-narrow); margin: 0 auto; text-align: center; position: relative; z-index: 2; }
.hero__center .kicker { display: inline-block; }
/* The one headline off the shared scale — see tokens.css --fs-hero. It
   takes the display face from h1 like every other heading, and only
   overrides what its size demands: more weight than a section h2 needs,
   tighter tracking, and leading under 1 (at 84px the stock 1.06 opens gaps
   the badge scatter then has to work around).                             */
.hero__title {
  font-weight: 800;
  font-size: var(--fs-hero);
  line-height: .98;
  letter-spacing: -.028em;
  margin: var(--s-4) 0 var(--s-5);
}
.hero .sub { font-size: var(--fs-lead); max-width: 46ch; margin-left: auto; margin-right: auto; }
.hero .btn-row { flex-direction: column; align-items: center; margin-top: var(--s-6); }
.hero .btn-row .btn { width: min(320px, 100%); }

@media (max-width: 640px) { .hero { min-height: 0; } }

/* Three-beat reassurance strip under the hero CTAs. Teal ticks, not bullets —
   the same check mark used by .checks, at list scale. */
.hero__points { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--s-2) var(--s-5); margin-top: var(--s-5); }
.hero__points li {
  display: flex; align-items: center; gap: var(--s-2);
  font-size: var(--fs-sm); font-weight: 700; color: var(--ink-65);
}
.hero__points li::before {
  content: ''; flex: none; width: var(--s-4); height: var(--s-4); border-radius: var(--r-pill);
  background: var(--tint-teal);
  box-shadow: inset 0 0 0 1px var(--line-teal);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%230a8e88' stroke-width='3.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6L9 17l-5-5'/%3E%3C/svg%3E");
  background-size: 10px 10px; background-position: center; background-repeat: no-repeat;
}

/* ---- Hero word reveal ---------------------------------------------------
   Each word ships visible (`.rw__inner` sits at rest). `.js` on <html> is
   the only thing that hides them first — a JS failure never removes text.
   `.rw` clips its own word so the inner span can slide up from underneath
   it instead of the whole line sliding, which is what "letters typing in"
   actually reads as at this size.                                          */
/* `.rw` clips to its own line box, which is `line-height` tall — under 1em
   here. Glyphs are centred in that box by half-leading, so anything past
   the baseline (the y of "ecosystem") is cut off. The padding drops the
   clip edge below the deepest descender; the matching negative margin
   keeps it out of the line box so the three lines don't space apart.      */
.rw {
  display: inline-block; overflow: hidden; vertical-align: top;
  padding-bottom: .2em; margin-bottom: -.2em;
}
.rw__inner { display: inline-block; }
/* Start offset is in em, not the old 115%: a percentage resolves against
   the inner's own box, which the same half-leading makes shorter than the
   glyphs it holds, so the word's ascenders sat inside the taller clip box
   before the animation began.                                             */
.js .rw__inner {
  opacity: 0; transform: translateY(1.7em);
  animation: rw-in .7s var(--ease) forwards;
  animation-delay: calc(var(--i, 0) * 70ms + .05s);
}
/* .grad-word's gradient is painted via background-clip:text on the element
   that owns the glyphs. Here that's .rw__inner (two spans deep for the word
   reveal), not .accent itself — so the gradient has to be re-applied at that
   level or the inherited transparent fill leaves the words invisible.       */
.hero__title .accent.grad-word .rw__inner {
  background: linear-gradient(135deg, var(--teal-deep), var(--teal), var(--teal-2));
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent;
}
.js .hero__center .reveal-word {
  opacity: 0; transform: translateY(14px);
  animation: word-in .6s var(--ease) forwards;
  animation-delay: calc(var(--i, 0) * 70ms + .05s);
}
@keyframes rw-in   { to { opacity: 1; transform: translateY(0); } }
@keyframes word-in { to { opacity: 1; transform: translateY(0); } }

/* ---- Hero badges ---------------------------------------------------------
   Real badges printed on Entry (same assets as the proof rail below),
   scattered around the headline the way a Luma-style hero uses event art.

   The scatter is deliberately off-grid: no badge shares a top edge with the
   one across from it, sizes step between 7% and 13% of the hero width, and
   five of them bleed past an edge so the field reads as a slice of
   something larger rather than a tidy frame. One crosses the centre column
   below the reassurance strip, which is what stops the arrangement reading
   as two columns with a hole in the middle.

   Positions are % of the hero box so the whole field scales with it. Each
   badge is a wrapper (one-shot entrance) around an <img> (endless drift):
   two elements because both animations drive `transform`, and the last
   animation on an element wins outright.                                   */
.hero__badges { position: absolute; inset: 0; z-index: 1; pointer-events: none; }
.hero__badge { position: absolute; display: block; width: 132px; }
.hero__badge img {
  display: block; width: 100%; height: auto;
  border-radius: var(--r-md); box-shadow: var(--sh-lg);
  border: 1px solid var(--line);
  transform: rotate(var(--r, 0deg));
  animation: badge-drift var(--dur, 16s) ease-in-out infinite alternate;
  animation-delay: var(--dd, 0s);
}
.js .hero__badge {
  opacity: 0; transform: translateY(28px) scale(.82);
  animation: badge-in .8s var(--ease) forwards;
  animation-delay: var(--d, .3s);
}
@keyframes badge-in { to { opacity: 1; transform: none; } }

/* Ping-pong rather than a loop: `alternate` means the badge eases back the
   way it came, so there's no seam where it snaps home. Durations are all
   mutually indivisible (13/15/17/19/21/23s) so the ten never fall into step
   with each other — that phasing is the whole reason it reads as drift and
   not as a carousel.                                                       */
@keyframes badge-drift {
  from { transform: translate3d(0, 0, 0) rotate(var(--r, 0deg)); }
  to   { transform: translate3d(var(--dx, 0px), var(--dy, -16px), 0)
                    rotate(calc(var(--r, 0deg) + var(--dr, 2deg))); }
}

.hero__badge--1  { --r: -7deg;  --d: .30s; --dx: 14px;  --dy: -20px; --dr:  2.5deg; --dur: 17s; --dd: -3s;  top:   3%; left:  1.5%; width: 12.5%; }
.hero__badge--2  { --r:  5deg;  --d: .52s; --dx: -12px; --dy:  18px; --dr: -2deg;   --dur: 21s; --dd: -8s;  top:  -6%; left: 19%;   width: 8.5%;  }
.hero__badge--3  { --r:  8deg;  --d: .40s; --dx: 16px;  --dy:  14px; --dr: -3deg;   --dur: 15s; --dd: -11s; top:  43%; left: -3%;   width: 10%;   }
.hero__badge--4  { --r: -5deg;  --d: .70s; --dx: -10px; --dy: -22px; --dr:  2deg;   --dur: 23s; --dd: -5s;  top:  68%; left:  8.5%; width: 12.5%; }
.hero__badge--5  { --r:  6deg;  --d: .36s; --dx: -16px; --dy:  16px; --dr: -2.5deg; --dur: 19s; --dd: -14s; top:  -9%; left: 75.5%; width: 11%;   }
.hero__badge--6  { --r: -8deg;  --d: .58s; --dx: 12px;  --dy: -18px; --dr:  3deg;   --dur: 13s; --dd: -2s;  top:  24%; left: 90.5%; width: 10%;   }
.hero__badge--7  { --r:  4deg;  --d: .46s; --dx: -14px; --dy: -14px; --dr: -2deg;   --dur: 21s; --dd: -17s; top:  62%; left: 73%;   width: 8%;    }
.hero__badge--8  { --r: -6deg;  --d: .76s; --dx: 10px;  --dy:  20px; --dr:  2.5deg; --dur: 17s; --dd: -9s;  top:  76%; left: 86.5%; width: 11.5%; }
.hero__badge--9  { --r:  9deg;  --d: .64s; --dx: -18px; --dy:  12px; --dr: -3deg;   --dur: 15s; --dd: -6s;  top:  30%; left: 21.5%; width: 7.5%;  }
.hero__badge--10 { --r: -9deg;  --d: .68s; --dx: 15px;  --dy: -16px; --dr:  2deg;   --dur: 19s; --dd: -12s; bottom: -14%; left: 55%; width: 9.5%; }

/* The two centre-column badges are the first to go: below this width the
   headline grows into the space they occupy. */
@media (max-width: 1180px) {
  .hero__badge--9, .hero__badge--10 { display: none; }
}
@media (max-width: 980px) { .hero__badges { display: none; } }
@media (max-width: 640px) { .hero { padding: var(--section-y) 0; } }

/* ---- The device -------------------------------------------------------
   `kiosk-frame.png` is the unit photographed head-on with the screen cut out
   as a transparent aperture, so anything positioned behind it shows through
   and the bezel overlaps the content edges the way real glass does.

   The aperture was measured off the asset itself (1680 × 1050):
     x 373–1306, y 246–756  →  933 × 510 px
   expressed below as percentages so the frame can be any size. Note the
   aperture is 1.83:1, not the panel's true 16:10 — the unit is shot at a
   slight downward angle, so the screen is foreshortened.
   ---------------------------------------------------------------------- */
.device { position: relative; width: 100%; }
.device__frame {
  position: relative; z-index: 2;
  width: 100%;
  pointer-events: none;        /* clicks fall through to the live screen */
  filter: drop-shadow(0 26px 44px rgba(15,23,42,.16));
}
.device__screen {
  position: absolute; z-index: 1;
  left: 22.2024%; top: 23.4286%;
  width: 55.5357%; height: 48.5714%;
  overflow: hidden;
  background: #0b0e14;
}
.device__screen > video,
.device__screen > img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* The kiosk demo, mounted in the aperture. Its own chrome comes off — the
   photograph is the chrome now. */
.device__screen .kiosk { border-radius: 0; height: 100%; }
.device__screen .kiosk-device { padding: 0; border-radius: 0; background: none; box-shadow: none; height: 100%; }
.device__screen .kiosk-screen { aspect-ratio: auto; height: 100%; border-radius: 0; }

/* Below this width the aperture is ~200px across, which puts every kiosk key
   under WCAG 2.2 SC 2.5.8's 24×24 floor. The frame comes off and the demo
   returns to the full-width treatment it has everywhere else. */
@media (max-width: 760px) {
  .device--demo .device__frame { display: none; }
  .device--demo .device__screen {
    position: static; width: 100%; height: auto; background: none;
  }
  .device--demo .device__screen .kiosk-device {
    padding: clamp(9px, 1.1vw, 16px);
    border-radius: var(--r-lg);
    background: linear-gradient(160deg, #2c3346, #1b2131 55%, #171c29);
    box-shadow: var(--sh-lg), inset 0 1px 0 rgba(255,255,255,.14);
    height: auto;
  }
  .device--demo .device__screen .kiosk-screen {
    aspect-ratio: 4 / 3; height: auto;
    border-radius: var(--r-md);
  }
  .device--demo .device__screen .kiosk { height: auto; }
}

/* ---- Proof rail --------------------------------------------------------
   A band rather than a section: it sits directly under the hero as the last
   beat of the first fold, so it takes half the section rhythm, not the whole
   step. Centered — it is a marquee, and it is the only centered band above
   the closing CTA.                                                          */
.rail { padding: var(--s-7) 0; overflow: hidden; }
.rail__lede { text-align: center; font-size: var(--fs-label); font-weight: 800; color: var(--muted); letter-spacing: .1em; text-transform: uppercase; margin-bottom: var(--s-5); }
.rail__track { display: flex; gap: var(--s-4); width: max-content; animation: rail 46s linear infinite; }
.rail:hover .rail__track, .rail:focus-within .rail__track { animation-play-state: paused; }
@keyframes rail { to { transform: translateX(-50%); } }

.rail__card {
  flex: none; width: 152px;
  background: var(--card-glass); border: 1px solid var(--line);
  border-radius: var(--r-md); box-shadow: var(--sh-sm);
  padding: var(--s-3); text-align: center;
  transition: transform var(--t) var(--ease), box-shadow var(--t) var(--ease);
}
.rail__card:hover { transform: translateY(-3px); box-shadow: var(--sh); }
.rail__card img { width: 100%; border-radius: var(--r-xs); background: #fff; }
.rail__card figcaption { margin-top: var(--s-2); font-size: var(--fs-label); font-weight: 800; color: var(--muted); }

/* ---- Grids ------------------------------------------------------------- */
.grid { display: grid; gap: var(--s-5); }
.grid--2 { grid-template-columns: repeat(2, minmax(0,1fr)); }
.grid--3 { grid-template-columns: repeat(3, minmax(0,1fr)); }
.grid--4 { grid-template-columns: repeat(4, minmax(0,1fr)); }
@media (max-width: 940px) { .grid--3, .grid--4 { grid-template-columns: repeat(2, minmax(0,1fr)); } }
@media (max-width: 640px) { .grid--2, .grid--3, .grid--4 { grid-template-columns: 1fr; } }

/* ---- Stats ------------------------------------------------------------- */
.stat { text-align: center; padding: var(--s-6) var(--s-4); }
.stat b { display: block; font-family: var(--font-display); font-size: clamp(2rem, 4vw, 2.5rem); font-weight: 700; letter-spacing: -1px; line-height: 1; }
.stat span { display: block; margin-top: var(--s-2); font-size: var(--fs-label); font-weight: 700; color: var(--muted); }

/* Panel: a card whose interior is divided by hairlines rather than padding —
   the stats strip and the live dashboard. Replaces the inline `padding:0;
   overflow:hidden` + per-child border rules the markup used to carry. */
.panel { padding: 0; overflow: hidden; }
.panel .grid { gap: 0; }
.panel .grid > * + * { border-left: 1px solid var(--line); }
@media (max-width: 940px) {
  .panel .grid--4 > :nth-child(odd),
  .panel .grid--3 > :nth-child(odd) { border-left: 0; }
  .panel .grid > :nth-child(n+3) { border-top: 1px solid var(--line); }
}
@media (max-width: 640px) {
  .panel .grid > * + * { border-left: 0; border-top: 1px solid var(--line); }
}
.panel__row { padding: var(--s-5); }
.panel__row + .panel__row { border-top: 1px solid var(--line); }
.panel__head { display: flex; align-items: center; justify-content: space-between; gap: var(--s-3); flex-wrap: wrap; }
.panel__head b { display: block; font-size: var(--fs-sm); font-weight: 800; }
.panel__head span { display: block; font-size: var(--fs-label); color: var(--muted); }
.panel__label { font-size: var(--fs-label); font-weight: 800; text-transform: uppercase; letter-spacing: .1em; color: var(--muted); margin-bottom: var(--s-3); }
.panel__tags { display: flex; gap: var(--s-2); flex-wrap: wrap; }

/* ---- Two-column contrast ----------------------------------------------- */
.contrast { display: grid; gap: var(--s-5); grid-template-columns: 1fr 1fr; align-items: start; }
.contrast--center { align-items: center; }
@media (max-width: 800px) { .contrast { grid-template-columns: 1fr; } }

.stack-list { display: grid; margin: var(--s-4) 0 0; }
.stack-list li {
  display: flex; justify-content: space-between; align-items: baseline; gap: var(--s-4);
  padding: var(--s-3) 0; border-bottom: 1px solid var(--line);
  font-size: var(--fs-sm);
}
.stack-list li:last-child { border-bottom: 0; }
.stack-list li span { color: var(--muted); }
.stack-list li b { font-variant-numeric: tabular-nums; font-weight: 800; }

.total { display: flex; justify-content: space-between; align-items: baseline; gap: var(--s-4); padding-top: var(--s-4); margin-top: var(--s-2); border-top: 2px solid var(--line-strong); }
.total span { font-size: var(--fs-label); font-weight: 800; text-transform: uppercase; letter-spacing: .1em; color: var(--muted); }
.total b { font-family: var(--font-display); font-size: var(--fs-d3); font-weight: 700; letter-spacing: -.5px; }
.card--hero .total { border-top-color: rgba(255,255,255,.3); }
.card--hero .total span { color: rgba(255,255,255,.82); }
.card--hero .stack-list li { border-bottom-color: rgba(255,255,255,.18); }
.card--hero .stack-list li span { color: rgba(255,255,255,.92); }

.note { padding: var(--s-5); border-radius: var(--r-lg); background: var(--card-veil); border: 1px solid var(--line); color: var(--ink-80); font-size: var(--fs-sm); line-height: 1.6; }
.note b { font-weight: 800; color: var(--ink); }

/* ---- Check list -------------------------------------------------------- */
.checks { display: grid; gap: var(--s-3); }
.checks li { display: flex; gap: var(--s-3); align-items: flex-start; font-size: var(--fs-sm); line-height: 1.5; }
.checks svg { flex: none; margin-top: var(--s-1); color: var(--teal); }
.card--hero .checks svg { color: #fff; }

/* ---- Steps ------------------------------------------------------------- */
.steps { counter-reset: s; display: grid; gap: var(--s-5); }
.steps > li { counter-increment: s; display: flex; gap: var(--s-4); align-items: flex-start; }
.steps > li::before {
  content: counter(s);
  flex: none; width: var(--s-6); height: var(--s-6); border-radius: var(--r-pill);
  display: grid; place-items: center;
  background: var(--brand-grad); color: #fff;
  font-size: var(--fs-label); font-weight: 800;
}
.steps h3 { margin-bottom: var(--s-1); }
.steps p { color: var(--muted); font-size: var(--fs-sm); line-height: 1.55; }

/* ---- Price card -------------------------------------------------------- */
.price { display: grid; grid-template-columns: minmax(0, .8fr) minmax(0, 1.2fr); gap: 0; overflow: hidden; padding: 0; }
@media (max-width: 820px) { .price { grid-template-columns: 1fr; } }

.price__left { padding: var(--s-7) var(--s-6); background: linear-gradient(180deg, var(--tint-teal), rgba(255,255,255,.5)); border-right: 1px solid var(--line); }
@media (max-width: 820px) { .price__left { border-right: 0; border-bottom: 1px solid var(--line); } }
.price__num { font-family: var(--font-display); font-size: clamp(3rem, 7vw, 4rem); font-weight: 700; letter-spacing: -2px; line-height: 1; margin: var(--s-3) 0 var(--s-2); }
.price__num sup { font-size: .4em; vertical-align: super; letter-spacing: 0; }
.price__per { color: var(--muted); font-size: var(--fs-sm); margin-bottom: var(--s-5); }
.price__fine { margin-top: var(--s-3); font-size: var(--fs-label); color: var(--muted); }
.price__right { padding: var(--s-7) var(--s-6); }
.price__right .grid { gap: var(--s-3) var(--s-5); margin-top: var(--s-4); }

/* Three-tier pricing grid — same primitives as .price, laid out per-card
   instead of split left/right since each tier needs its own CTA. */
.tier-grid { margin-bottom: var(--s-6); align-items: stretch; }
.tier { text-align: center; align-items: center; }
.tier .pill { margin-bottom: var(--s-4); }
.tier__name { font-family: var(--font-display); font-size: var(--fs-d3); font-weight: 700; }
.tier .price__num { font-size: clamp(2.25rem, 4vw, 3rem); margin: var(--s-2) 0; }
.tier .price__per { margin-bottom: var(--s-5); }
.tier .btn--block { width: 100%; }
.tier .price__fine { margin-bottom: 0; }

/* Photography inside a content column takes the card's radius and elevation
   so it sits in the same material family as everything around it. */
.media { border-radius: var(--r-lg); box-shadow: var(--sh-lg); }

/* ---- Quotes ------------------------------------------------------------ */
.quote { display: flex; flex-direction: column; }
.quote svg.qm { color: var(--teal); opacity: .35; margin-bottom: var(--s-3); }
/* 500 rather than the 400 this ran at under Fraunces. A serif pull quote
   reads as its own voice at book weight; a rounded sans at 400 just reads
   as body copy in a bigger size. One step up is enough to separate it
   without turning it into a heading. */
.quote blockquote { font-family: var(--font-display); font-size: var(--fs-d3); font-weight: 500; line-height: 1.35; letter-spacing: -.2px; margin-bottom: var(--s-5); }
.quote figcaption { display: flex; align-items: center; gap: var(--s-3); margin-top: auto; }
.quote figcaption img { width: 46px; height: 46px; border-radius: var(--r-pill); object-fit: cover; border: 1px solid var(--line); }
.quote b { display: block; font-size: var(--fs-sm); font-weight: 800; }
.quote cite { display: block; font-style: normal; font-size: var(--fs-label); color: var(--muted); }

/* ---- Tabs + FAQ -------------------------------------------------------- */
.tabs { display: flex; flex-wrap: wrap; gap: var(--s-2); margin-bottom: var(--s-5); }
.tab {
  padding: var(--s-3) var(--s-4); min-height: 42px;
  border-radius: var(--r-pill); border: 1px solid var(--line);
  background: var(--card); color: var(--muted);
  font-size: var(--fs-label); font-weight: 800; cursor: pointer;
  transition: all var(--t) var(--ease);
}
.tab:hover { transform: translateY(-1px); box-shadow: var(--sh-hover); color: var(--ink); }
.tab[aria-selected='true'] { background: var(--brand-grad); border-color: rgba(15,187,179,.35); color: #fff; box-shadow: var(--sh-teal); }

.faq { display: grid; gap: var(--s-3); }
.faq details {
  background: var(--card-glass); border: 1px solid var(--line);
  border-radius: var(--r-md); box-shadow: var(--sh-sm);
  transition: border-color var(--t) var(--ease), box-shadow var(--t) var(--ease);
}
.faq details[open] { border-color: var(--line-teal); }
.faq summary {
  list-style: none; cursor: pointer; padding: var(--s-4) var(--s-5);
  display: flex; align-items: center; justify-content: space-between; gap: var(--s-4);
  font-size: var(--fs-body); font-weight: 800; min-height: 56px;
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary::after {
  content: ''; flex: none; width: 11px; height: 11px;
  border-right: 2.2px solid var(--teal); border-bottom: 2.2px solid var(--teal);
  transform: rotate(45deg) translate(-2px, -2px);
  transition: transform var(--t) var(--ease);
}
.faq details[open] summary::after { transform: rotate(225deg) translate(-3px, -3px); }
.faq .a { padding: 0 var(--s-5) var(--s-5); color: var(--muted); font-size: var(--fs-sm); line-height: 1.65; max-width: 76ch; }

/* ---- Forms ------------------------------------------------------------- */
/* website_spec.md §C.2 — labels above fields, required AND optional marked. */
.form { display: grid; gap: var(--s-4); }
.field { display: grid; gap: var(--s-2); }
.field label { font-size: var(--fs-sm); font-weight: 800; }
.field label .opt { font-weight: 500; color: var(--muted); }
.field input, .field select, .field textarea {
  width: 100%;
  padding: var(--s-3) var(--s-4);
  min-height: 50px;
  border: 1px solid var(--line-strong);
  border-radius: var(--r-md);
  background: var(--card);
  font-size: var(--fs-body);
  transition: border-color var(--t) var(--ease), box-shadow var(--t) var(--ease);
}
.field textarea { min-height: 110px; resize: vertical; line-height: 1.55; }
.field input:focus, .field select:focus, .field textarea:focus {
  outline: none; border-color: var(--teal);
  box-shadow: 0 0 0 4px rgba(15,187,179,.14);
}
.field input:user-invalid, .field select:user-invalid { border-color: var(--bad); }
.field .err { font-size: var(--fs-label); color: var(--bad-ink); font-weight: 700; min-height: 1em; }
.form__divider { height: 1px; background: var(--line); margin: var(--s-1) 0; }
.form .btn { margin-top: var(--s-2); }

/* ---- Callout ----------------------------------------------------------
   #form-note, which site.js fills in either as a real warning (no endpoint,
   client validation) or a success note (?sent=1 after form.php redirects
   back). Warning is the default ramp; --ok switches to the same ramp used
   elsewhere for confirmed/positive state. */
.callout {
  display: flex; gap: var(--s-3); align-items: flex-start;
  padding: var(--s-4) var(--s-5); border-radius: var(--r-md);
  background: var(--warn-bg); border: 1px solid var(--warn-line);
  color: var(--warn-ink); font-size: var(--fs-sm); line-height: 1.55; font-weight: 600;
}
.callout svg { flex: none; margin-top: 2px; }
.callout--ok { background: var(--ok-bg); border-color: var(--ok-line); color: var(--ok-ink); }

/* ---- Final CTA ---------------------------------------------------------
   The one centered block on the page. It is a full-bleed card, not a section
   head, so it does not read as an alignment change mid-scroll.              */
.final .card { align-items: center; text-align: center; padding: var(--s-9) var(--s-6); }
.final h2 { margin-bottom: var(--s-4); }
.final .sub { margin: 0 auto var(--s-6); }

/* ---- Footer ------------------------------------------------------------ */
footer {
  border-top: 1px solid var(--line);
  background: color-mix(in srgb, var(--card) 60%, transparent);
  padding: var(--s-8) 0 var(--s-6);
}
.foot__grid { display: grid; gap: var(--s-7); grid-template-columns: 1.4fr repeat(3, 1fr); }
@media (max-width: 860px) { .foot__grid { grid-template-columns: 1fr 1fr; } }
@media (max-width: 520px) { .foot__grid { grid-template-columns: 1fr; } }
.foot h4 { text-transform: uppercase; letter-spacing: .12em; color: var(--muted); margin-bottom: var(--s-3); }
.foot ul { display: grid; gap: var(--s-2); }
/* Scoped to list links — an unscoped `.foot a` outranks `.brand` and turns the
   footer lockup into an inline-block, which breaks the flex row. */
.foot li a { font-size: var(--fs-sm); color: var(--ink-80); transition: color var(--t) var(--ease); display: inline-block; padding: 2px 0; }
.foot li a:hover { color: var(--teal-deep); }
.foot__blurb { color: var(--muted); font-size: var(--fs-sm); line-height: 1.6; max-width: 34ch; margin-top: var(--s-3); }
.foot__bar { display: flex; flex-wrap: wrap; gap: var(--s-3); justify-content: space-between; align-items: center; margin-top: var(--s-7); padding-top: var(--s-5); border-top: 1px solid var(--line); font-size: var(--fs-label); color: var(--muted); }

/* ---- Reveal ------------------------------------------------------------ */
/* website_spec.md §C.3.4 — content ships visible; JS opts in to the animation.
   If JS fails, nothing disappears. */
.js .reveal { opacity: 0; transform: translateY(14px); transition: opacity .5s var(--ease), transform .5s var(--ease); }
.js .reveal.is-in { opacity: 1; transform: none; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
  .js .reveal { opacity: 1; transform: none; }
  .rail__track { animation: none; }
}

/* ---- Print ------------------------------------------------------------- */
@media print {
  .nav, .rail, footer, .btn { display: none; }
  body { background: #fff; }
}
