/* ==========================================================
   KooKoo — Component primitive styles
   ----------------------------------------------------------
   Matches preview/components.html specimens. Classes are the
   visual truth; components.jsx primitives render these class
   names. If you edit a button color, edit here, not inline.
   ========================================================== */

/* ─── BUTTON ──────────────────────────────────────────── */
.kk-btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-body);
  font-weight: var(--weight-semi-bold);
  font-size: 15px;
  letter-spacing: -0.01em;
  padding: 14px 24px;
  border-radius: var(--r-pill);
  border: 0;
  cursor: pointer;
  overflow: hidden;
  text-decoration: none;
  transition:
    transform 300ms ease,
    box-shadow 300ms ease,
    background 300ms ease,
    color 240ms ease,
    padding var(--cta-dur, 450ms) cubic-bezier(.2,.7,.2,1);
}
.kk-btn--sm { padding: 10px 18px; font-size: 13px; }
.kk-btn--lg { padding: 18px 32px; font-size: 16px; }

/* Variants */
/* Primary = warm brand gradient (pink → orange). */
.kk-btn--primary {
  background: var(--gradient-cta);
  color: var(--fg-on-dark);
}
.kk-btn--primary:hover {
  transform: scale3d(1.04, 1.04, 1.01);
  box-shadow: var(--shadow-cta);
}

.kk-btn--dark {
  background: var(--kk-plum-900);
  color: var(--fg-on-dark);
  box-shadow: inset 0 0 0 1px rgba(255,255,255,0.06);
}
.kk-btn--dark:hover {
  transform: scale3d(1.04, 1.04, 1.01);
  background: linear-gradient(180deg, #1c1358 0%, var(--kk-plum-900) 100%);
  box-shadow:
    inset 0 0 0 1px rgba(45,178,255,0.35),
    var(--shadow-dark-glow);
}

.kk-btn--outline {
  background: transparent;
  color: var(--fg-1);
  box-shadow: inset 0 0 0 1px var(--kk-plum-900);
}
.kk-btn--outline:hover {
  transform: scale3d(1.04, 1.04, 1.01);
  background: var(--kk-plum-900);
  color: var(--fg-on-dark);
  box-shadow:
    inset 0 0 0 1px var(--kk-plum-900),
    0 6px 18px -6px rgba(5,0,63,0.35);
}

.kk-btn--ghost {
  background: transparent;
  color: var(--fg-1);
}
.kk-btn--ghost:hover { color: var(--kk-pink); }

/* On-dark context — activated via `data-on-dark` attribute on the button */
.kk-btn--ghost[data-on-dark] { color: var(--fg-on-dark); }
.kk-btn--ghost[data-on-dark]:hover {
  color: var(--kk-cyan);
  background: rgba(45,178,255,0.08);
}
.kk-btn--outline[data-on-dark] {
  color: var(--fg-on-dark);
  box-shadow: inset 0 0 0 1.5px rgba(255,255,255,0.55);
  /* Color transition is paced to the white-fill sweep so the label text
     doesn't flash invisible while the ::before catches up. */
  transition:
    transform 300ms ease,
    box-shadow 300ms ease,
    background 300ms ease,
    color var(--cta-dur, 450ms) cubic-bezier(.2,.7,.2,1),
    padding var(--cta-dur, 450ms) cubic-bezier(.2,.7,.2,1);
}
/* White fill swipes in from the left on dark-context outline */
.kk-btn--outline[data-on-dark]::before {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--fg-on-dark);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform var(--cta-dur, 450ms) cubic-bezier(.2,.7,.2,1);
  z-index: 0;
  border-radius: inherit;
}
.kk-btn--outline[data-on-dark]:hover {
  color: var(--kk-plum-900);
  background: transparent;
  box-shadow: inset 0 0 0 1.5px rgba(255,255,255,0);
  transform: scale3d(1.04, 1.04, 1.01);
}
.kk-btn--outline[data-on-dark]:hover::before { transform: scaleX(1); }

/* On-LIGHT context — the same swipe recipe, inverted. The overlay must flip
   with the surface it sits on: cream swipe + plum label on dark, plum swipe +
   cream label on light. Without this twin, an outline CTA on a light surface
   fell back to the plain `.kk-btn--outline` hover (an instant background fill,
   no left-origin swipe), which reads as a different button. Mirrors the
   platform's theme-adaptive `.kk-btn-cta-on-dark` recipe, whose class name is
   likewise historical: the mechanic is surface-relative, not dark-only.
   Opt-in via `data-on-light`; the base `.kk-btn--outline` hover is untouched
   so every existing outline button keeps its approved behaviour. */
.kk-btn--outline[data-on-light] {
  /* Resting state is a SOFT FILLED pill, not a transparent ringed one: muted
     lavender surface + plum label, no ring. That is what the app shows on a
     cream surface, and it is what makes the plum swipe read as an inversion
     on hover rather than as a box appearing from nowhere. */
  background: var(--surface-muted);
  color: var(--fg-1);
  box-shadow: none;
  transition:
    transform 300ms ease,
    box-shadow 300ms ease,
    background 300ms ease,
    color var(--cta-dur, 450ms) cubic-bezier(.2,.7,.2,1),
    padding var(--cta-dur, 450ms) cubic-bezier(.2,.7,.2,1);
}
.kk-btn--outline[data-on-light]::before {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--kk-plum-900);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform var(--cta-dur, 450ms) cubic-bezier(.2,.7,.2,1);
  z-index: 0;
  border-radius: inherit;
}
/* Hover keeps the muted fill underneath (the ::before plum sweep is what the
   eye reads) and must beat the base `.kk-btn--outline:hover`, which would
   otherwise slam the whole background to plum instantly and kill the swipe. */
.kk-btn--outline[data-on-light]:hover {
  color: var(--fg-on-dark);
  background: var(--surface-muted);
  box-shadow: none;
  transform: scale3d(1.04, 1.04, 1.01);
}
.kk-btn--outline[data-on-light]:hover::before { transform: scaleX(1); }
/* The arrow circle pairs with the SWIPE, not the page: cream disc + plum
   arrow so it reads against the plum fill sliding in beneath it. */
.kk-btn--outline[data-on-light] .cta-circle { background: var(--fg-on-dark); }
.kk-btn--outline[data-on-light] .cta-circle svg path { stroke: var(--kk-plum-900); }

/* ─── CTA-CIRCLE STRETCH ──────────────────────────────
   The .cta-circle is absolutely positioned and invisible at rest, so
   the resting button keeps the variant's natural symmetric padding —
   no reserved gap on the right. On hover the right pad opens enough
   to seat the circle (30/38px) plus a small breathing buffer. */
.kk-btn:has(.cta-circle):hover           { padding-right: 56px; }
.kk-btn--sm:has(.cta-circle):hover       { padding-right: 44px; }
.kk-btn--lg:has(.cta-circle):hover       { padding-right: 64px; }

.cta-circle {
  position: absolute;
  top: 50%;
  right: 6px;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: var(--cta-circle-bg, #FFF);
  opacity: 0;
  transform: translate(22px, -50%) scale(.6);
  transition:
    opacity 200ms ease,
    transform 320ms cubic-bezier(.2,.7,.2,1);
  pointer-events: none;
  z-index: 2;
}
.kk-btn--lg .cta-circle { width: 38px; height: 38px; right: 8px; }
.cta-circle svg {
  width: 14px;
  height: 14px;
  display: block;
}
.kk-btn--lg .cta-circle svg { width: 18px; height: 18px; }
.cta-circle svg path,
.cta-circle svg circle {
  stroke: var(--cta-circle-fg, var(--kk-orange));
}
.kk-btn:hover .cta-circle {
  opacity: 1;
  transform: translate(0, -50%) scale(1);
}

.cta-label {
  display: inline-block;
  position: relative;
  z-index: 2;
  transition: transform var(--cta-dur, 450ms) cubic-bezier(.2,.7,.2,1);
}
.kk-btn:hover .cta-label { transform: translateX(-2px); }

/* Per-variant circle tones */
.kk-btn--primary .cta-circle { background: #FFFFFF; }
.kk-btn--primary .cta-circle svg path { stroke: var(--kk-green-cta); }
.kk-btn--dark .cta-circle { background: var(--kk-cyan); }
.kk-btn--dark .cta-circle svg path { stroke: var(--kk-plum-900); }
.kk-btn--outline .cta-circle { background: var(--kk-plum-900); }
.kk-btn--outline .cta-circle svg path { stroke: #FFFFFF; }
.kk-btn--ghost .cta-circle { background: var(--kk-plum-900); }
.kk-btn--ghost .cta-circle svg path { stroke: #FFFFFF; }
.kk-btn--outline[data-on-dark] .cta-circle { background: var(--kk-plum-900); }
.kk-btn--outline[data-on-dark] .cta-circle svg path { stroke: #FFFFFF; }

/* — Accordion body: animated open/close via the grid-rows 0fr->1fr
     trick (content is always in the DOM; the row track collapses).
     Used by FAQ + Book-a-Call FAQ strip. — */
.kk-acc-body {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 480ms cubic-bezier(0.22, 1, 0.36, 1);
}
.kk-acc-body.is-open { grid-template-rows: 1fr; }
.kk-acc-body > div { overflow: hidden; min-height: 0; }

@media (prefers-reduced-motion: reduce) {
  .kk-btn,
  .kk-btn *,
  .cta-circle,
  .cta-label { transition: none !important; }
  .kk-btn:hover { transform: none !important; }
}

/* ─── CHIP ───────────────────────────────────────────── */
.kk-chip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 6px 12px;
  border-radius: var(--r-pill);
  font: var(--weight-semi-bold) 11px/1 var(--font-mono);
  letter-spacing: 1.5px;
  text-transform: uppercase;
}
.kk-chip--neutral   { background: var(--bg-2);                 color: var(--fg-1); }
.kk-chip--filling   { background: var(--kk-red-100);           color: var(--kk-red-400); }
.kk-chip--confirmed { background: var(--kk-green-100);         color: #00a582; }
.kk-chip--watching  { background: var(--kk-blue-100);          color: var(--kk-blue); }
.kk-chip--price     { background: var(--kk-plum-900);          color: var(--fg-on-dark); letter-spacing: 0; }
.kk-chip--live      { background: rgba(45,178,255,0.12);       color: var(--kk-cyan); }

.kk-chip .kk-chip-dot {
  width: 8px; height: 8px; border-radius: 50%;
  background: currentColor;
  animation: kk-pulse 1.4s ease-in-out infinite;
}
@keyframes kk-pulse { 0%,100% { opacity: 1 } 50% { opacity: .3 } }

/* RouteArrow dots (components/site/RouteArrow.jsx) — a staggered pulse that
   cascades toward the chevron, reusing the same kk-pulse beat the corridor
   card's live dot already runs so the whole card breathes together. The
   per-dot 0/90/180ms offset is set inline. Guarded by no-preference rather
   than disabled under `reduce`, so reduced-motion users get three static
   dots and never a partially-applied animation. The chevron is deliberately
   NOT in this selector: it anchors the eye and must not pulse. */
@media (prefers-reduced-motion: no-preference) {
  .kk-route-arrow-dot { animation: kk-pulse 1.4s ease-in-out infinite; }
}

/* One-shot cross-fade for swappable inline labels (hero country word,
   airport-code rotators). Apply via `animation: kk-swap 600ms ease`
   on a span keyed to the swapping value so React remounts on change. */
@keyframes kk-swap {
  0%   { opacity: 0; transform: translateY(6px); }
  100% { opacity: 1; transform: translateY(0); }
}

@media (prefers-reduced-motion: reduce) {
  .kk-chip .kk-chip-dot { animation: none; }
}

/* ─── EYEBROW — add pink + white variants to the set in colors_and_type.css ─── */
.kk-eyebrow--pink  { color: var(--kk-pink); }
.kk-eyebrow--white { color: rgba(255,255,255,0.8); }

/* ─── EVENTS GRID (app-style event cards) ─────────────
   3-up on desktop → 2-up tablet → 1-up phone. Mirrors the app's
   Explore · Events card grid. */
.kk-events-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 24px;
}
@media (min-width: 640px) {
  .kk-events-grid { grid-template-columns: 1fr 1fr; }
}
@media (min-width: 1000px) {
  .kk-events-grid { grid-template-columns: repeat(3, 1fr); }
}

/* ─── EVENT DETAIL — photo gallery mosaic ─────────────
   Big-left + stacked-middle + tall-right, mirroring the app's event
   detail gallery. Collapses to a simple stack on phones. */
.kk-ev-gallery {
  display: grid;
  grid-template-columns: 1.6fr 1fr 1fr;
  grid-template-rows: 1fr 1fr;
  gap: 12px;
  height: 440px;
}
.kk-ev-gallery > div {
  background-size: cover;
  background-position: center;
  border-radius: var(--r-5);
  min-height: 0;
}
.kk-ev-gallery .kk-ev-a { grid-row: 1 / span 2; grid-column: 1; }
.kk-ev-gallery .kk-ev-d { grid-row: 1 / span 2; grid-column: 3; }
@media (max-width: 760px) {
  .kk-ev-gallery { grid-template-columns: 1fr 1fr; grid-template-rows: 200px 150px 200px; grid-auto-rows: 150px; height: auto; }
  .kk-ev-gallery .kk-ev-a { grid-row: 1; grid-column: 1 / span 2; }
  .kk-ev-gallery .kk-ev-d { grid-row: 3; grid-column: 1 / span 2; }
}

/* Event detail — 3-up section grids (what's inside / passes) */
.kk-ev-cols {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 20px;
  align-items: stretch;
}
@media (max-width: 860px)  { .kk-ev-cols { grid-template-columns: 1fr; } }

/* ─── PILL INPUT ──────────────────────────────────────
   Full-pill form controls with a green focus ring — mirrors the
   app's filter bar and sign-up inputs (visual design audit). Apply
   .kk-input to <input>/<textarea>/<select>; .kk-field wraps a
   label + control column. Textareas opt into a softer radius via
   .kk-input--area so multi-line boxes don't look like giant pills. */
.kk-input {
  width: 100%;
  font-family: var(--font-body);
  font-size: 15px;
  font-weight: var(--weight-medium);
  letter-spacing: -0.01em;
  color: var(--fg-1);
  background: var(--kk-neutral-100);
  border: 1.5px solid var(--border-input);
  border-radius: var(--r-pill);
  padding: 13px 20px;
  transition: border-color 200ms ease, box-shadow 200ms ease, background 200ms ease;
  appearance: none;
}
.kk-input::placeholder { color: var(--fg-3); }
.kk-input:hover { border-color: var(--border-3); }
.kk-input:focus,
.kk-input:focus-visible {
  outline: none;
  border-color: var(--focus-ring);
  box-shadow: 0 0 0 3px rgba(37, 211, 102, 0.18);
  background: var(--kk-neutral-100);
}
/* Multi-line + inline-editable controls soften from full pill to a
   rounded rectangle so tall boxes don't read as lozenges. */
.kk-input--area { border-radius: var(--r-4); padding: 14px 18px; resize: vertical; }
/* On dark surfaces (hero rails, dark forms). */
.kk-input--on-dark {
  color: var(--fg-on-dark);
  background: rgba(255, 255, 255, 0.06);
  border-color: rgba(255, 255, 255, 0.16);
}
.kk-input--on-dark::placeholder { color: var(--fg-on-dark-3); }
.kk-input--on-dark:hover { border-color: rgba(255, 255, 255, 0.30); }
.kk-input--on-dark:focus,
.kk-input--on-dark:focus-visible {
  border-color: var(--kk-green-cta);
  box-shadow: 0 0 0 3px rgba(22, 180, 92, 0.28);
  background: rgba(255, 255, 255, 0.08);
}
/* Native select — keep the pill, add a chevron affordance. */
select.kk-input {
  background-image:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23515b79' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 18px center;
  padding-right: 44px;
  cursor: pointer;
}
.kk-field { display: flex; flex-direction: column; gap: 8px; }
.kk-field > label,
.kk-field > .kk-field-label {
  font: var(--weight-semi-bold) 12px/1 var(--font-eyebrow);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--fg-3);
}

/* ─── DOT GRID GAUGE ─────────────────────────────────── */
.kk-dot-grid {
  display: grid;
  grid-template-columns: repeat(10, 1fr);
  gap: 5px;
  margin: 4px 0 10px;
}
.kk-dot-grid--sm { gap: 4px; }
.kk-dot-grid--lg { gap: 8px; }
.kk-dot-grid .kk-dot {
  aspect-ratio: 1;
  width: 100%;
  border-radius: 50%;
  background: var(--kk-dot-off, #DDE1EA);
  transition:
    background 400ms ease,
    transform 400ms ease;
}
.kk-dot-grid .kk-dot--on                      { background: var(--kk-cyan); }
.kk-dot-grid.kk-dot-grid--orange .kk-dot--on  { background: var(--kk-orange); }
.kk-dot-grid.kk-dot-grid--pink   .kk-dot--on  { background: var(--kk-pink); }
.kk-dot-grid.kk-dot-grid--green  .kk-dot--on  { background: var(--kk-teal); }
.kk-dot-grid.kk-dot-grid--purple .kk-dot--on  { background: var(--kk-purple); }
.kk-dot-grid.kk-dot-grid--plum   .kk-dot--on  { background: var(--kk-plum-900); }
/* Route STATUS tones — colour encodes pool lifecycle (mirrors the app).
   Prefer these over raw colours for seat / capacity gauges. */
.kk-dot-grid.kk-dot-grid--discovered .kk-dot--on { background: var(--status-discovered); }
.kk-dot-grid.kk-dot-grid--pooling    .kk-dot--on { background: var(--status-pooling); }
.kk-dot-grid.kk-dot-grid--warming    .kk-dot--on { background: var(--status-warming); }
.kk-dot-grid.kk-dot-grid--confirmed  .kk-dot--on { background: var(--status-confirmed); }
.kk-dot-grid.kk-dot-grid--activated  .kk-dot--on { background: var(--status-activated); }
.kk-dot-grid.kk-dot-grid--did-not-activate .kk-dot--on { background: var(--status-did-not-activate); }
.kk-dot-grid.kk-dot-grid--gradient .kk-dot--on {
  background: hsl(var(--h, 180) 90% 55%);
}
.kk-dot-grid.kk-dot-grid--sunset .kk-dot--on {
  background: hsl(var(--h, 20) 92% 58%);
}

/* Single-row variant — used by HUD threshold gauges. Consumer passes
   total, dots lay out in one row with tier-sized gap. Width tracks
   container; dots stay round via aspect-ratio. */
.kk-dot-grid--row {
  grid-template-columns: repeat(var(--kk-dot-grid-cols, 24), 1fr);
}

/* ─── DOT MATRIX (fixed-size) ──────────────────────────
   Unlike .kk-dot-grid which sizes dots by container width,
   .kk-dot-matrix renders dots at a FIXED pixel size via design
   tokens. Use this anywhere dot size must be consistent across
   cards with different seat counts, or across breakpoints.
   Pick size via modifier: --sm / --md / --lg / --xl / --2xl.
   Default: xl (18px). Wraps naturally. */
/* ─── DOT MATRIX (fixed-size) ──────────────────────────
   Fixed-pixel dot size via design tokens — consistent across seat
   counts and breakpoints. DEFAULT behavior: dots wrap naturally
   when they hit the container edge (auto-fill columns at fixed size).
   Pick size via modifier: --sm / --md / --lg / --xl / --2xl.
   Opt into a fixed single-line row with --row (consumer passes
   --kk-matrix-cols), or a fixed grid with --cols (same var). */
.kk-dot-matrix {
  --dot-size: var(--dot-size-xl);
  --dot-gap: var(--dot-gap-md);
  display: grid;
  grid-template-columns: repeat(auto-fill, var(--dot-size));
  gap: var(--dot-gap);
  justify-content: start;
}
.kk-dot-matrix--sm  { --dot-size: var(--dot-size-sm);  --dot-gap: var(--dot-gap-sm); }
.kk-dot-matrix--md  { --dot-size: var(--dot-size-md);  --dot-gap: var(--dot-gap-md); }
.kk-dot-matrix--lg  { --dot-size: var(--dot-size-lg);  --dot-gap: var(--dot-gap-md); }
.kk-dot-matrix--xl  { --dot-size: var(--dot-size-xl);  --dot-gap: var(--dot-gap-md); }
.kk-dot-matrix--2xl { --dot-size: var(--dot-size-2xl); --dot-gap: var(--dot-gap-lg); }

/* Fixed column count (wraps into multi-row if N dots > fits-per-row). */
.kk-dot-matrix--cols {
  grid-template-columns: repeat(var(--kk-matrix-cols, 8), var(--dot-size));
}
/* Forced single-line row. Will overflow if container too narrow —
   use only when you've sized the container to fit. */
.kk-dot-matrix--row {
  grid-auto-flow: column;
  grid-auto-columns: var(--dot-size);
  grid-template-columns: none;
}

/* Dot base (off-state colors are contextual: light-surface default,
   dark-surface override via --on-dark). */
.kk-dot-matrix .kk-dot {
  width: var(--dot-size);
  height: var(--dot-size);
  border-radius: 50%;
  background: var(--border-1, #DDE1EA);
  transition: background 300ms ease, transform 300ms ease, box-shadow 300ms ease;
}
.kk-dot-matrix--on-dark .kk-dot {
  background: rgba(255,255,255,0.15);
}

/* Active state — MUST come after off-state rules to win the cascade. */
.kk-dot-matrix .kk-dot.kk-dot--on,
.kk-dot-matrix--on-dark .kk-dot.kk-dot--on {
  background: var(--kk-dot-color, var(--kk-cyan));
  box-shadow:
    0 0 0 1px rgba(0,0,0,0.02),
    0 4px 10px color-mix(in oklch, var(--kk-dot-color, var(--kk-cyan)) 28%, transparent);
}

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

/* ─── PLACEHOLDER MEDIA ───────────────────────────────
   Designed placeholder for card image slots. Gradient + dot
   pattern + mono label + watermark. When the real image
   arrives, replace with <img> or `background-image`. */
.kk-placeholder-media {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 10;
  overflow: hidden;
  background:
    linear-gradient(135deg, var(--kk-placeholder-color, var(--kk-plum-700)) 0%, var(--kk-plum-900) 100%);
  color: var(--fg-on-dark);
}
.kk-placeholder-media--16-8  { aspect-ratio: 16 / 8; }
.kk-placeholder-media--16-9  { aspect-ratio: 16 / 9; }
.kk-placeholder-media--4-3   { aspect-ratio: 4 / 3; }
.kk-placeholder-media--1-1   { aspect-ratio: 1 / 1; }

.kk-placeholder-media::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image: radial-gradient(rgba(255,255,255,0.10) 1px, transparent 1px);
  background-size: 14px 14px;
  pointer-events: none;
}
/* faint brand-tint wash */
.kk-placeholder-media::after {
  content: "";
  position: absolute;
  inset: 0;
  background:
    radial-gradient(600px circle at 20% 30%, rgba(255,255,255,0.10), transparent 60%),
    radial-gradient(400px circle at 85% 85%, rgba(0,0,0,0.25), transparent 60%);
  pointer-events: none;
}

.kk-placeholder-media__label {
  position: absolute;
  top: 14px;
  left: 16px;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font: var(--weight-semi-bold) 10px/1 var(--font-mono);
  letter-spacing: 2px;
  text-transform: uppercase;
  color: rgba(255,255,255,0.72);
  z-index: 2;
}
.kk-placeholder-media__label::before {
  content: "";
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--kk-placeholder-color, currentColor);
  box-shadow: 0 0 0 2px rgba(255,255,255,0.15);
}

.kk-placeholder-media__watermark {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  padding: 24px;
  text-align: center;
  font: var(--weight-semi-bold) clamp(18px, 2.8vw, 32px)/1.1 var(--font-display);
  letter-spacing: -0.02em;
  color: rgba(255,255,255,0.28);
  z-index: 1;
  pointer-events: none;
}

.kk-placeholder-media__caption {
  position: absolute;
  bottom: 14px;
  left: 16px;
  right: 16px;
  font: var(--weight-medium) 11px/1.3 var(--font-mono);
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: rgba(255,255,255,0.5);
  z-index: 2;
}

/* ─── VIEWFRAME ───────────────────────────────────────
   Locks Hero + Trust to exactly one viewport combined, minus the
   sticky header height — so the section *below* them (Problem)
   never peeks on any viewport (large or ultrawide). First child
   flex-grows; last child stays at its natural height.
   `--kk-header-h` is the Header's natural slot height; tweak if the
   Header grows. Uses 100dvh where supported so mobile URL-bar
   collapse doesn't leak the Problem section. */
:root { --kk-header-h: 72px; }
.kk-viewframe {
  position: relative;
  height: calc(100vh - var(--kk-header-h));
  height: calc(100dvh - var(--kk-header-h));
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.kk-viewframe > :first-child { flex: 1 1 0; min-height: 0; }
.kk-viewframe > :last-child  { flex: 0 0 auto; }

/* ─── HOW-IT-WORKS HERO ──────────────────────────────
   Full-viewport hero with precursor step cards. */
.kk-hw-pc {
  will-change: transform;
}
.kk-hw-pc:hover {
  background: rgba(255, 255, 255, 0.08) !important;
  border-color: rgba(255, 255, 255, 0.22) !important;
  transform: translateY(-3px);
}
.kk-hw-pc:hover .kk-hw-pc__arrow {
  color: rgba(255, 255, 255, 0.95) !important;
  transform: translateY(3px);
}
.kk-hw-pc:focus-visible {
  outline: 2px solid var(--kk-cyan);
  outline-offset: 3px;
}

@media (max-width: 980px) {
  .kk-hw-hero__grid {
    grid-template-columns: 1fr !important;
    gap: 48px !important;
  }
}
@media (max-width: 520px) {
  .kk-hw-hero__cards {
    grid-template-columns: 1fr !important;
  }
}

/* Routes card — keyboard focus lands on the Reveal wrapper (tabIndex=0).
   Default :focus-visible ring would clash with the active-state
   border; use an offset outline that reads as a second ring. */
.kk-routes-grid [role="group"][tabindex="0"]:focus-visible {
  outline: 2px solid var(--kk-cyan);
  outline-offset: 4px;
  border-radius: var(--r-4);
}

/* ─── PAGE RHYTHM TOKENS ──────────────────────────────
   Shared cadence for Home / Flights / Routes / HowItWorks /
   Manifesto / Press. Every page hero starts text at the same
   Y, every H1 uses the same clamp, every section breathes at
   the same rate. Mobile shrinks all three in proportion.
   ─────────────────────────────────────────────────────── */
:root {
  --page-hero-pt:    168px;   /* hero top pad — anchors headline Y */
  --page-hero-pb:    96px;    /* hero bottom pad */
  --page-section-py: 128px;   /* all non-hero section vertical */
  --page-h1-scale:   clamp(44px, 7vw, 104px);
  --page-h1-lh:      1.0;
  --page-h1-ls:      -0.035em;
  --page-h2-scale:   clamp(36px, 5vw, 72px);
  --page-h2-lh:      1.05;
  --page-h2-ls:      -0.025em;
  --page-lead-scale: clamp(17px, 1.4vw, 20px);
}
@media (max-width: 900px) {
  :root {
    --page-hero-pt:    128px;
    --page-hero-pb:    72px;
    --page-section-py: 88px;
  }
}
@media (max-width: 480px) {
  :root {
    --page-hero-pt:    112px;
    --page-hero-pb:    56px;
    --page-section-py: 72px;
    --page-h1-scale:   clamp(36px, 10vw, 56px);
    --page-h2-scale:   clamp(30px, 8vw, 44px);
  }
}

.kk-page-hero {
  position: relative;
  padding-top: var(--page-hero-pt);
  padding-bottom: var(--page-hero-pb);
  overflow: hidden;
}
.kk-page-section {
  padding-top: var(--page-section-py);
  padding-bottom: var(--page-section-py);
  position: relative;
}
.kk-page-h1 {
  font-family: var(--font-display);
  font-size: var(--page-h1-scale);
  font-weight: 600;
  line-height: var(--page-h1-lh);
  letter-spacing: var(--page-h1-ls);
  text-wrap: balance;
  margin: 0;
}
.kk-page-h2 {
  font-family: var(--font-display);
  font-size: var(--page-h2-scale);
  font-weight: 600;
  line-height: var(--page-h2-lh);
  letter-spacing: var(--page-h2-ls);
  text-wrap: balance;
  margin: 0;
}
.kk-page-lead {
  font-size: var(--page-lead-scale);
  line-height: 1.5;
  text-wrap: pretty;
  margin: 0;
}
/* Mobile: center hero content. Desktop: keep left-align. */
@media (max-width: 640px) {
  .kk-page-hero__align {
    text-align: center;
  }
  .kk-page-hero__align > * {
    margin-left: auto;
    margin-right: auto;
  }
}

/* ─── MOBILE GRID STACKING ───────────────────────────
   Multi-column card grids on Press / Routes / Cargo / Home collapse
   to a single column on phones so card body copy gets readable line
   length instead of one-word-per-line. */
@media (max-width: 640px) {
  .kk-pp-problems,           /* Press: "Why it's broken" — 2 cols */
  .kk-pp-stats,              /* Press: "Market opportunity" — 3 cols */
  .kk-pp-loop,               /* Press: Signal/Pool/Threshold — 4 cols */
  .kk-rt-feat,               /* Routes: Featured corridor — 2 cols */
  .kk-festivals-grid,        /* Routes: Festivals — 2 cols */
  .kk-home-hero-grid,        /* Home: hero left/right — 2 cols */
  .kk-home-trust,            /* Home: trust strip — 4 cols */
  .kk-home-problem-grid,     /* Home: 3 pain points — 3 cols */
  .kk-home-solution-grid,    /* Home: solution — 2 cols */
  .kk-home-personas-grid,    /* Home: who it's for — 3 cols */
  .kk-home-routes-grid       /* Home: routes — 3 cols */ {
    grid-template-columns: 1fr !important;
  }
  /* Live corridor card: cap width so it stays "card-sized" not
     edge-to-edge, and center it once the hero grid stacks. */
  .kk-home-corridor-card {
    max-width: 320px;
    margin-left: auto;
    margin-right: auto;
    width: 100%;
    padding: 24px !important;
    min-height: 0 !important;
  }
  /* Airport codes (BGI, KIN, etc.) inside the live corridor card.
     Fixed 64px overflowed the constrained card on phones; clamp
     scales them so they always fit two codes + arrow comfortably. */
  .kk-home-corridor-card .kk-corridor-code {
    font-size: clamp(32px, 12vw, 56px) !important;
  }
  /* Ecosystem rows: stack name / description / status pill vertically
     so descriptions like "The trust product." don't get clipped by the
     280px name column on phones. Status pill self-aligns to start. */
  .kk-home-eco-row {
    grid-template-columns: 1fr !important;
    gap: 10px !important;
    padding: 20px 20px !important;
    align-items: start !important;
  }
  .kk-home-eco-row > :last-child {
    justify-self: start !important;
  }
}

/* ================================================================
   BODY ELEVATION — landing-page motion primitives (July 2026).
   Charter: house ease everywhere, fire-once reveals, scrub budget
   already spent by the globe hero + Intent. See
   .planning/landing-elevation-plan.md
   ================================================================ */

/* — StackDeck: pure-CSS sticky pile (clay/fora grammar).
     Each card sticks 16px lower than the previous, so earlier cards
     peek out above the one covering them. No JS, no scroll lock.
     Gotcha guard: never put overflow-x:hidden on an ancestor. — */
.kk-stack { position: relative; }
.kk-stack__card {
  position: sticky;
  top: calc(var(--kk-stack-top, 84px) + var(--stack-i, 0) * 18px);
  min-height: clamp(340px, 52vh, 520px);
  border-radius: var(--r-7);
  --stack-pad: clamp(32px, 5vw, 64px);
  padding: var(--stack-pad);
  box-sizing: border-box;
  display: grid;
  align-content: center;
  /* upward shadow: the arriving card casts onto the pile beneath */
  box-shadow: 0 -16px 48px rgba(21, 11, 55, 0.12);
}
/* media cards: the image hugs the frame (22px to the card edge on its outer
   three sides) while the text keeps its original inset via its own padding. */
.kk-stack__card--media { padding: 22px; align-content: stretch; }
@media (min-width: 768px) {
  .kk-stack__card--media .kk-stack__text {
    padding: calc(var(--stack-pad) - 22px);
    padding-left: 0;
  }
  .kk-stack__card--media .kk-stack__grid--flip .kk-stack__text {
    padding-left: calc(var(--stack-pad) - 22px);
    padding-right: 0;
  }
}
.kk-stack__card + .kk-stack__card { margin-top: 28px; }
.kk-stack__grid {
  display: grid;
  grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.15fr);
  gap: clamp(24px, 4vw, 72px);
  align-items: end;
}
/* media variant: one side is a framed image, inset so the card frames it */
.kk-stack__grid--media { align-items: stretch; }
.kk-stack__media { position: relative; min-height: 300px; }
.kk-stack__grid--flip .kk-stack__media { order: 2; }
@media (max-width: 767px) {
  .kk-stack__card { position: relative; top: 0; min-height: 0; }
  .kk-stack__grid { grid-template-columns: 1fr; align-items: start; gap: 20px; }
  /* stacked layout: the image keeps its big, frame-hugging presence
     (22px card padding does the hug); text gets its inset back below. */
  .kk-stack__media { min-height: clamp(240px, 38vh, 320px); order: 0 !important; }
  .kk-stack__card--media .kk-stack__text { padding: 2px 10px 12px; }
}

/* — Curtain modal (incredibles grammar): panel grows up out of a
     narrow rounded slot at the bottom, content wipes in staggered,
     close drops away. Driven by .is-open / .is-closing on the
     overlay; children opt in with .kk-curtain-item + --i. — */
.kk-curtain-overlay {
  opacity: 0;
  transition: opacity 400ms ease;
}
.kk-curtain-overlay.is-open { opacity: 1; }
.kk-curtain-overlay.is-closing { opacity: 0; transition-delay: 120ms; }
.kk-curtain-panel {
  clip-path: inset(96% 22% 0% 22% round 24px);
  transform: translateY(56px);
  transition:
    clip-path 950ms cubic-bezier(0.22, 1, 0.36, 1),
    transform 950ms cubic-bezier(0.22, 1, 0.36, 1),
    opacity 320ms ease;
}
.is-open .kk-curtain-panel {
  clip-path: inset(0% 0% 0% 0% round 24px);
  transform: translateY(0);
}
.is-closing .kk-curtain-panel {
  transform: translateY(90px) scale(0.94);
  opacity: 0;
  transition:
    transform 320ms cubic-bezier(0.22, 1, 0.36, 1),
    opacity 260ms ease;
}
.kk-curtain-item {
  opacity: 0;
  transform: translateY(26px);
  transition:
    opacity 650ms cubic-bezier(0.22, 1, 0.36, 1),
    transform 650ms cubic-bezier(0.22, 1, 0.36, 1);
  transition-delay: calc(340ms + var(--i, 0) * 85ms);
}
.is-open .kk-curtain-item { opacity: 1; transform: none; }
.is-closing .kk-curtain-item { transition: opacity 200ms ease; transition-delay: 0ms; opacity: 0; }

/* — Routes gauge: dots light up one after another on first view.
     Fill rule (Math.round(pool/cap*10)) and colors untouched;
     this is entrance timing only. — */
.kk-gauge-dot {
  transition: background 420ms ease, box-shadow 420ms ease;
  transition-delay: calc(var(--i, 0) * 55ms);
}

/* — Ecosystem engine constellation: products orbit the engine as dots;
     hovering a node highlights its row and vice versa (.is-hot mirrors
     :hover on both sides). Constellation is desktop-only. — */
.kk-eco-wrap { display: grid; grid-template-columns: minmax(360px, 470px) 1fr; align-items: center; }
.kk-eco-orrery { padding: 36px 10px 36px 30px; }
.kk-eco-orrery svg { display: block; width: 100%; height: auto; }
.kk-eco-rows { border-left: 1px solid var(--border-1); }
.kk-orrery-node .kk-orrery-dot { transform-box: fill-box; transform-origin: center; }
.kk-orrery-node.is-hot .kk-orrery-dot { transform: scale(1.5); }
.kk-eco-arrow {
  display: inline-block;
  transition: transform 300ms cubic-bezier(0.22, 1, 0.36, 1);
}
.kk-home-eco-row:hover .kk-eco-arrow,
.kk-home-eco-row.is-hot .kk-eco-arrow { transform: translateX(5px); }
/* rows aren't clickable destinations yet: hover is a quiet lift, no bg swap */
.kk-home-eco-row:hover,
.kk-home-eco-row.is-hot { transform: translateY(-3px); }
@media (max-width: 899px) {
  .kk-eco-wrap { grid-template-columns: 1fr; }
  .kk-eco-orrery { display: none; }
  .kk-eco-rows { border-left: none; }
}
@media (prefers-reduced-motion: reduce) {
  .kk-eco-orrery circle, .kk-eco-orrery text { transition: none !important; animation: none !important; }
}

/* — Personas spotlight: stacks to one column on small screens — */
@media (max-width: 899px) {
  .kk-personas-spotlight { grid-template-columns: 1fr !important; }
  .kk-personas-portrait { position: static !important; }
}

/* — Closing CTA: ambient glow drift — */
@keyframes kk-glow-drift {
  from { transform: translate3d(-3%, -2%, 0) scale(1); }
  to   { transform: translate3d(4%, 3%, 0) scale(1.12); }
}
.kk-cta-glow { animation: kk-glow-drift 16s ease-in-out infinite alternate; }

@media (prefers-reduced-motion: reduce) {
  .kk-curtain-overlay, .kk-curtain-panel, .kk-curtain-item {
    transition: none !important;
    clip-path: none !important;
    transform: none !important;
    opacity: 1 !important;
  }
  .kk-curtain-overlay.is-closing { opacity: 0 !important; }
  .kk-gauge-dot { transition: none !important; }
  .kk-cta-glow { animation: none !important; }
  .kk-acc-body { transition: none !important; }
}
