/* ============================================================================
   Sarabellum — public marketing site
   ============================================================================
   "Ink & Neurospice" — the design direction picked 2026-07-17.

   This file is the whole site's CSS. It is deliberately a SEPARATE file from
   the app's index.css/App.css rather than an import of them, because the two
   have opposite jobs: the app optimizes for information density on a screen
   you stare at daily, the site optimizes for a stranger's first ten seconds.
   What they DO share is the palette — the custom properties below are copied
   from index.css on purpose so the site and the app are visibly one company.
   If a brand colour changes there, change it here too.

   Site-only additions on top of the app's tokens:
     --spice        the coral CTA colour (the app has no CTA of this weight)
     --font-display Bricolage Grotesque — a deliberately wonky grotesque.
                    The brief's one hard "no": nothing that reads as a generic
                    SaaS template. A slightly odd typeface does more work than
                    any amount of gradient.
   ========================================================================= */

:root {
  /* — Palette, mirrored from src/index.css (dark is the default brand look) — */
  --bg: #0f1115;
  --bg-elevated: #171a21;
  --bg-card: #1b1f28;
  --border: #2a2f3a;
  --border-soft: #232733;
  --text-primary: #eef0f4;
  --text-secondary: #9aa1af;
  /* Was overridden here because the app's #6b7180 measured 3.87:1. The app
     adopted this value at source (index.css) on 2026-07-17, so this is now
     just a copy of the app's token like every other line in this block. */
  --text-muted: #8a91a0;
  --accent: #7c9cff;
  --accent-soft: rgba(124, 156, 255, 0.15);
  --accent-mood: #f2a65a;
  --accent-energy: #5ad1a8;
  --danger: #e0607a;

  /* — Site-only — */
  --spice: #ff5c39;
  --spice-soft: rgba(255, 92, 57, 0.14);
  --spice-ink: #14171d; /* text ON coral — ink, not white: white on #ff5c39 fails contrast */
  /* --spice used AS text. Dark passes as-is (6.16:1 on ink); light's #f2542d
     measures 2.94:1 on the page — a fail even at large-text size — so light
     gets a darker ember below. Fill stays --spice; ink is --spice-text. */
  --spice-text: var(--spice);
  --glow: rgba(124, 156, 255, 0.28);
  --wire: rgba(154, 161, 175, 0.22);

  --radius-sm: 8px;
  --radius: 14px;
  --radius-lg: 24px;

  /* Dark mode separates a card from the page with a lighter fill and a border,
     which is why it needs no shadow. Light mode can't do that — white on
     near-white measures 1.08:1 and simply isn't visible — so light gets a real
     shadow instead. Contrast ratio is the wrong instrument for "is this a
     card": elevation is. */
  --card-shadow: none;
  --frame-shadow: 0 30px 80px -30px rgba(0, 0, 0, 0.55);

  --font-display: 'Bricolage Grotesque', 'Segoe UI', system-ui, sans-serif;
  --font-body: 'Public Sans', 'Segoe UI', system-ui, sans-serif;

  --shell: 1120px;
  --prose: 680px;

  color-scheme: dark;
}

:root[data-theme='light'] {
  /* Deepened from the app's #f5f6f8: white cards on it measured 1.08:1 and the
     whole page read as washed-out mush. The page has to recede for the cards to
     exist. --bg-elevated stays white so .section--alt bands still read as a
     distinct surface — light mode alternates page/white, dark alternates
     ink/less-ink. */
  --bg: #eaedf2;
  --bg-elevated: #ffffff;
  --bg-card: #ffffff;
  --border: #d5dae3;
  --border-soft: #e1e5ec;
  --text-primary: #1a1d24;
  --text-secondary: #5b6270;

  /* ── Divergences from the app's light palette ──────────────────────────
     This list used to be three long. The app has since fixed two of them at
     source (2026-07-17), which is the better outcome — a divergence is a
     workaround, and the app had the same failures for the same reasons:

       --text-muted  now identical to the app's (#616875). Fixed in index.css.
       --spice-ink   site-only, because --spice is site-only. White on coral
                     is 3.45:1; the CTA is the last place to ship a near-miss,
                     so the button keeps ink text in both themes.

     --accent stays #4560a8 HERE and #5e79c9 in the app, and that difference is
     real rather than laziness: the app uses --accent as a solid button fill
     six times, so darkening it there would fix link text by breaking
     .btn-primary's ink (4.55:1 → 3.14:1). The site never fills with it — zero
     solid-accent surfaces — so it can darken the token outright. The app
     solved the same problem with a separate --accent-text ink token instead.
     Same failure, two structures, two correct answers.
     ------------------------------------------------------------------- */
  --text-muted: #616875;
  --accent: #4560a8;
  --accent-soft: rgba(94, 121, 201, 0.12);
  --accent-mood: #c97b2e;
  --accent-energy: #1f9a76;
  --danger: #c94360;

  --spice: #f2542d;
  --spice-soft: rgba(242, 84, 45, 0.1);
  --spice-ink: #14171d;
  /* Ember for text: 4.20:1 on the page (h1 punchline, large-text bar is 3.0)
     and 4.92:1 on white (the 14px ticker em, body-text bar is 4.5). */
  --spice-text: #cc3e12;
  --glow: rgba(94, 121, 201, 0.18);
  --wire: rgba(91, 98, 112, 0.26);

  /* Two shadows, not one: a tight contact shadow so the card sits ON the page,
     and a wide soft one so it lifts off it. One shadow always looks like a
     sticker. */
  --card-shadow:
    0 1px 2px rgba(16, 20, 30, 0.05),
    0 10px 28px -14px rgba(16, 20, 30, 0.14);
  --frame-shadow:
    0 2px 4px rgba(16, 20, 30, 0.06),
    0 28px 60px -28px rgba(16, 20, 30, 0.24);

  color-scheme: light;
}

/* Self-hosted so the site makes zero third-party requests — the privacy
   policy promises no tracking, and a Google Fonts <link> would quietly hand
   every visitor's IP to Google on page load. */
@font-face {
  font-family: 'Bricolage Grotesque';
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url('/fonts/bricolage-grotesque-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F,
    U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Public Sans';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('/fonts/public-sans-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F,
    U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Public Sans';
  font-style: italic;
  font-weight: 400 700;
  font-display: swap;
  src: url('/fonts/public-sans-latin-italic.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F,
    U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

* {
  box-sizing: border-box;
}

html {
  scroll-behavior: smooth;
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }
  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text-primary);
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
}

h1,
h2,
h3,
h4 {
  font-family: var(--font-display);
  font-weight: 700;
  letter-spacing: -0.03em;
  line-height: 1.05;
  margin: 0;
  color: var(--text-primary);
  /* Long compound words ("neurodivergent") must never widen a phone. */
  overflow-wrap: break-word;
  /* Headings are short and set huge, which is exactly where a greedy line-break
     algorithm embarrasses you: the hero was ragging to four lines with "is."
     alone on the last one. `balance` evens the lines out instead of filling
     each one to the brim. It's capped at ~6 lines by the spec and ignored by
     browsers that don't support it, so the fallback is simply today's ragging —
     nothing to guard. Deliberately NOT applied to body text: `balance` on long
     copy is both slow and wrong (that's what `pretty` is for). */
  text-wrap: balance;
}

a {
  color: var(--accent);
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
}

img,
svg {
  max-width: 100%;
}

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 4px;
}

/* Skip link — the nav is long, and a keyboard/screen-reader visitor
   shouldn't have to walk it on every page. */
.skip {
  position: absolute;
  left: -9999px;
  top: 8px;
  z-index: 100;
  background: var(--bg-card);
  border: 1px solid var(--accent);
  border-radius: var(--radius-sm);
  padding: 10px 16px;
  color: var(--text-primary);
  text-decoration: none;
}

.skip:focus {
  left: 8px;
}

/* ── Layout ─────────────────────────────────────────────────────────────── */

.shell {
  width: 100%;
  max-width: var(--shell);
  margin: 0 auto;
  padding: 0 20px;
}

@media (min-width: 900px) {
  .shell {
    padding: 0 32px;
  }
}

.section {
  padding: 64px 0;
}

.section--tight {
  padding: 40px 0;
}

@media (min-width: 900px) {
  .section {
    padding: 104px 0;
  }
  .section--tight {
    padding: 64px 0;
  }
}

.section--alt {
  background: var(--bg-elevated);
  border-top: 1px solid var(--border-soft);
  border-bottom: 1px solid var(--border-soft);
}

.stack {
  display: flex;
  flex-direction: column;
}

/* ── Type ───────────────────────────────────────────────────────────────── */

.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin: 0 0 16px;
}

.eyebrow--accent {
  color: var(--accent);
}

/* clamp() rather than breakpoints: the hero is the one place where type has
   to feel intentional at every width, not at three widths.

   The 9vw/4.5rem it shipped with was measured wrong and looked it. The hero
   sits in a HALF-width column (~496px at 1280), but vw units scale off the
   whole viewport, so the type grew to 72px in a box that couldn't hold it and
   the headline ragged to four lines with "is." stranded on the last one.

   Measured, at a 496px column: "Your to-do list is." needs 509px at 72px and
   458px at 64px. So 4rem is the largest size where the coral punchline stays
   on ONE line — which is the whole job of that line. 5.4vw keeps it honest
   down at 900px (where the split first appears and the column is only 386px)
   instead of slamming into the cap and overflowing again.

   If you change the hero copy, re-measure. The constraint is the column, not
   the screen. */
.h-hero {
  font-size: clamp(2.5rem, 5.4vw, 4rem);
  font-weight: 800;
}

.h-section {
  font-size: clamp(1.9rem, 5.2vw, 3rem);
}

/* The missing rung. The scale jumped 3rem -> 1.25rem with nothing between, so
   every page that wanted a heading quieter than a hero but louder than a card
   reached for `style="font-size:2rem"` — 17 times, which is a type scale
   telling you what it's short of. This is that size, named, and it keeps a
   clamp so it still moves on a phone instead of freezing at 2rem the way the
   inline overrides did. */
.h-sub {
  font-size: clamp(1.5rem, 3.6vw, 2rem);
}

.h-card {
  font-size: 1.25rem;
  letter-spacing: -0.02em;
}

.lead {
  font-size: clamp(1.05rem, 2.4vw, 1.3rem);
  line-height: 1.55;
  color: var(--text-secondary);
  margin: 20px 0 0;
  max-width: 44ch;
}

.lead--center {
  margin-left: auto;
  margin-right: auto;
}

.muted {
  color: var(--text-muted);
}

.small {
  font-size: 0.85rem;
}

.center {
  text-align: center;
}

.spice-text {
  /* --spice-text, not --spice: the fill colour fails AA as TEXT in light mode
     (the hero punchline measured 2.94:1 on the page — under even the 3.0
     large-text bar). Same fill-vs-ink split the app made for --accent. */
  color: var(--spice-text);
}

/* The section intro block — heading + lead, used on nearly every page. */
.intro {
  max-width: 46ch;
  margin-bottom: 40px;
}

.intro--center {
  margin-left: auto;
  margin-right: auto;
  text-align: center;
}

.intro .lead {
  max-width: none;
}

/* ── Buttons ────────────────────────────────────────────────────────────── */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 14px 24px;
  border-radius: 999px;
  border: 1px solid transparent;
  font-family: var(--font-body);
  font-size: 15px;
  font-weight: 700;
  text-decoration: none;
  cursor: pointer;
  transition:
    transform 0.15s ease,
    background 0.15s ease,
    border-color 0.15s ease;
  white-space: nowrap;
}

.btn:hover {
  transform: translateY(-1px);
}

.btn:active {
  transform: translateY(0);
}

.btn--spice {
  background: var(--spice);
  color: var(--spice-ink);
  box-shadow: 0 6px 24px -8px var(--spice);
}

.btn--spice:hover {
  background: color-mix(in srgb, var(--spice) 88%, #fff);
}

.btn--ghost {
  background: transparent;
  border-color: var(--border);
  color: var(--text-primary);
}

.btn--ghost:hover {
  border-color: var(--accent);
  color: var(--accent);
}

.btn--sm {
  padding: 9px 16px;
  font-size: 14px;
}

.btn-row {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  margin-top: 32px;
}

.btn-row--center {
  justify-content: center;
}

/* ── Badges / pills ─────────────────────────────────────────────────────── */

.badge {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 6px 13px;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.04em;
  border: 1px solid var(--border);
  background: var(--bg-card);
  color: var(--text-secondary);
}

.badge--spicy {
  border-color: var(--spice-soft);
  background: var(--spice-soft);
  color: var(--spice);
}

.badge--soon {
  border-style: dashed;
  color: var(--text-muted);
  background: transparent;
}

/* ── Cards ──────────────────────────────────────────────────────────────── */

.card {
  background: var(--bg-card);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-lg);
  padding: 28px;
  box-shadow: var(--card-shadow);
  transition:
    border-color 0.18s ease,
    transform 0.18s ease;
}

.card--link:hover {
  border-color: var(--accent);
  transform: translateY(-2px);
}

.card h3 {
  margin-bottom: 10px;
}

.card p {
  color: var(--text-secondary);
  margin: 0;
}

.card__icon {
  font-size: 26px;
  line-height: 1;
  display: block;
  margin-bottom: 16px;
}

a.card {
  text-decoration: none;
  color: inherit;
  display: block;
}

.grid {
  display: grid;
  gap: 16px;
  grid-template-columns: 1fr;
}

@media (min-width: 700px) {
  .grid--2,
  .grid--3,
  .grid--4 {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (min-width: 1000px) {
  .grid--3 {
    grid-template-columns: repeat(3, 1fr);
  }
  .grid--4 {
    grid-template-columns: repeat(4, 1fr);
  }
}

/* ── Placeholder marking ────────────────────────────────────────────────────
   Anything wearing .ph is content Alex hasn't written yet. It is deliberately
   and visibly unfinished — dashed, muted, tagged — because the one rule the
   brief is strict about is that a placeholder must never read as a fact. The
   layout is real; the words are not. Search the repo for `.ph` to find every
   remaining blank to fill.
   ------------------------------------------------------------------------ */

.ph {
  position: relative;
  border-style: dashed !important;
  border-color: var(--wire) !important;
  background: transparent;
  /* Placeholders must never be elevated — they aren't real content, so they
     shouldn't sit on the page like it. Flat and dashed is the whole point. */
  box-shadow: none;
}

.ph__tag {
  display: inline-block;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-muted);
  border: 1px dashed var(--wire);
  border-radius: 999px;
  padding: 3px 9px;
  margin-bottom: 14px;
}

.ph__text {
  color: var(--text-muted);
  font-style: italic;
}

/* ── The spiral / hero atmosphere ───────────────────────────────────────── */

.glow {
  position: relative;
  overflow: hidden;
  isolation: isolate;
}

/* A single soft radial bloom behind the hero. Not a purple gradient — it's
   the brand periwinkle at low alpha, doing the job a background image would
   do at 200KB. */
.glow::before {
  content: '';
  position: absolute;
  z-index: -1;
  top: -30%;
  left: 50%;
  width: min(1000px, 150vw);
  aspect-ratio: 1;
  transform: translateX(-50%);
  background: radial-gradient(circle, var(--glow) 0%, transparent 62%);
  pointer-events: none;
}

.spiral-mark {
  display: block;
  animation: breathe 5.5s ease-in-out infinite;
}

@keyframes breathe {
  0%,
  100% {
    transform: scale(1);
    filter: drop-shadow(0 0 11px var(--glow));
  }
  50% {
    transform: scale(1.05);
    filter: drop-shadow(0 0 24px var(--glow));
  }
}

/* ── Header ─────────────────────────────────────────────────────────────── */

.site-header {
  position: sticky;
  top: 0;
  z-index: 50;
  background: color-mix(in srgb, var(--bg) 82%, transparent);
  backdrop-filter: blur(14px);
  border-bottom: 1px solid var(--border-soft);
}

.site-header__inner {
  display: flex;
  align-items: center;
  gap: 16px;
  height: 64px;
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 18px;
  letter-spacing: -0.03em;
  color: var(--text-primary);
  text-decoration: none;
  margin-right: auto;
}

.brand img {
  display: block;
}

.nav {
  display: none;
  align-items: center;
  gap: 4px;
}

.nav a {
  padding: 8px 12px;
  border-radius: var(--radius-sm);
  font-size: 14px;
  font-weight: 600;
  color: var(--text-secondary);
  text-decoration: none;
}

.nav a:hover {
  color: var(--text-primary);
  background: var(--accent-soft);
}

.nav a[aria-current='page'] {
  color: var(--accent);
}

.header-actions {
  display: flex;
  align-items: center;
  gap: 8px;
}

.theme-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: transparent;
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
}

.theme-toggle:hover {
  border-color: var(--accent);
}

/* The header's "Request access" pill is hidden on mobile (it moves into the
   menu panel — see menuLinks) and restored on desktop by the ≥940px block. */
.header-cta {
  display: none;
}

/* The CTA at the top of the mobile menu panel: the panel's one loud item. */
.menu__cta {
  background: var(--spice);
  color: var(--spice-ink) !important;
  text-align: center;
  font-weight: 700;
}

.menu__cta:hover {
  background: color-mix(in srgb, var(--spice) 88%, #fff);
  color: var(--spice-ink) !important;
}

/* Secondary to "Request access": an outlined "Sign in" for returning (approved)
   users, so both actions sit at the top of the mobile menu, not just the new one. */
.menu__login {
  border: 1px solid var(--border);
  text-align: center;
  font-weight: 600;
}

.menu__login:hover {
  border-color: var(--text-muted);
}

/* The mobile menu is a <details> element: it opens and closes with zero
   JavaScript, keeps working if the script fails, and is keyboard- and
   screen-reader-accessible for free. */
.menu {
  position: relative;
}

/* The menu button is the ONLY way to reach the other pages on mobile, so it
   must out-shout the theme toggle beside it — Alex's note: the light/dark
   button read as more important than "see the rest of the site". So it's a
   labelled accent pill ("☰ Menu"), 44px tall (touch-target floor too), not a
   quiet icon circle. Desktop is unaffected: the whole .menu is display:none
   at ≥940px, so this only ever renders on mobile. */
.menu-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  height: 44px;
  padding: 0 16px;
  border-radius: 999px;
  border: 1px solid var(--accent);
  background: var(--accent-soft);
  color: var(--accent);
  font-family: var(--font-body);
  font-size: 14px;
  font-weight: 700;
  cursor: pointer;
  list-style: none;
}

.menu-btn__bars {
  font-size: 16px;
  line-height: 1;
}

.menu-btn::-webkit-details-marker {
  display: none;
}

.menu[open] .menu-btn {
  border-color: var(--accent);
  background: var(--accent);
  color: var(--spice-ink);
}

.menu__panel {
  position: absolute;
  right: 0;
  top: calc(100% + 12px);
  width: min(84vw, 260px);
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 8px;
  /* Was a literal black shadow, same value on every theme -- --frame-shadow
     is the token every other floating/elevated surface on the site already
     uses (the mobile menu panel, cards), so this stops being the last
     hardcoded one. */
  box-shadow: var(--frame-shadow);
  display: flex;
  flex-direction: column;
  gap: 2px;
  max-height: min(70vh, 520px);
  overflow-y: auto;
}

.menu__panel a {
  padding: 10px 12px;
  border-radius: var(--radius-sm);
  color: var(--text-secondary);
  text-decoration: none;
  font-size: 15px;
  font-weight: 600;
}

.menu__panel a:hover {
  background: var(--accent-soft);
  color: var(--text-primary);
}

.menu__sep {
  height: 1px;
  background: var(--border-soft);
  margin: 6px 4px;
}

/* ⚠️ ORDER IS LOAD-BEARING — this block must stay BELOW the .nav/.menu-btn base
   rules above. It sat above them until 2026-07-17 and did nothing: same
   specificity (0,1,0) means the LAST matching rule wins regardless of the media
   query, so `.menu-btn { display: inline-flex }` beat it at every width and the
   hamburger showed on desktop next to the full nav. Media queries don't add
   specificity — a mobile-first override only works if it comes last. */
@media (min-width: 940px) {
  .nav,
  .header-cta {
    display: flex;
  }
  .menu {
    display: none;
  }
}

/* ── Footer ─────────────────────────────────────────────────────────────── */

.site-footer {
  border-top: 1px solid var(--border-soft);
  background: var(--bg-elevated);
  padding: 56px 0 40px;
  margin-top: 0;
}

.footer-grid {
  display: grid;
  gap: 32px;
  grid-template-columns: repeat(2, 1fr);
}

@media (min-width: 760px) {
  .footer-grid {
    grid-template-columns: 1.4fr repeat(4, 1fr);
    gap: 24px;
  }
}

.footer-brand p {
  color: var(--text-muted);
  font-size: 13px;
  margin: 12px 0 0;
  max-width: 30ch;
}

.footer-col h4 {
  font-family: var(--font-body);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin: 0 0 14px;
}

.footer-col ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 9px;
}

.footer-col a {
  color: var(--text-secondary);
  text-decoration: none;
  font-size: 14px;
}

.footer-col a:hover {
  color: var(--accent);
}

.footer-bottom {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-top: 48px;
  padding-top: 24px;
  border-top: 1px solid var(--border-soft);
  color: var(--text-muted);
  font-size: 12px;
}

/* Sign-in is the quiet secondary action in invite-only beta -- it lives here in
   the footer bottom, next to the slogan, rather than shouting from the header. */
.footer-bottom__right {
  display: inline-flex;
  align-items: center;
  gap: 16px;
}

.footer-bottom__right a {
  color: var(--text-secondary);
}

/* The sign-in screen's badge, now on every footer (Alex's call): same blue,
   same centring, same quiet confidence as where it started. */
.audhd-tag {
  display: block;
  text-align: center;
  /* var(--accent), not --accent-text: that token is app-only. The site's
     --accent is already the AA-safe ink (#4560a8 light / #7c9cff dark). */
  color: var(--accent);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  margin: 40px 0 0;
}

/* The corporate slogan the brief asked for. It's a joke that's also true,
   so it gets to be the last word on every page. */
.slogan {
  font-family: var(--font-display);
  font-weight: 700;
  letter-spacing: -0.02em;
  color: var(--text-secondary);
  font-size: 13px;
}

/* ── Prose (legal, blog, help) ──────────────────────────────────────────── */

.prose {
  max-width: var(--prose);
}

.prose h2 {
  font-size: 1.35rem;
  margin: 44px 0 12px;
}

.prose h3 {
  font-size: 1.05rem;
  margin: 28px 0 8px;
}

.prose p,
.prose li {
  color: var(--text-secondary);
}

.prose li {
  margin-bottom: 6px;
}

.prose ul,
.prose ol {
  padding-left: 20px;
}

.prose strong {
  color: var(--text-primary);
}

.page-head {
  padding: 56px 0 8px;
}

@media (min-width: 900px) {
  .page-head {
    padding: 88px 0 8px;
  }
}

.page-head h1 {
  font-size: clamp(2.1rem, 6vw, 3.2rem);
}

.updated {
  color: var(--text-muted);
  font-size: 13px;
  margin: 16px 0 0;
}

/* ── Forms ──────────────────────────────────────────────────────────────── */

.field {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-bottom: 14px;
}

.field label {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.field input,
.field select,
.field textarea {
  width: 100%;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 12px 14px;
  /* 16px floor: iOS Safari zooms the whole page on focus below it. */
  font-size: 16px;
  font-family: inherit;
  color: var(--text-primary);
  outline: none;
}

.field textarea {
  resize: vertical;
  min-height: 120px;
}

.field input:focus,
.field select:focus,
.field textarea:focus {
  border-color: var(--accent);
}

.form-status {
  font-size: 14px;
  margin: 12px 0 0;
  min-height: 1.2em;
}

.form-status.is-ok {
  color: var(--accent-energy);
}

.form-status.is-err {
  color: var(--danger);
}

/* ── FAQ ────────────────────────────────────────────────────────────────── */

.faq {
  max-width: var(--prose);
  margin: 0 auto;
}

.faq details {
  border-bottom: 1px solid var(--border-soft);
  padding: 4px 0;
}

.faq summary {
  cursor: pointer;
  list-style: none;
  padding: 18px 32px 18px 0;
  font-weight: 700;
  font-family: var(--font-display);
  letter-spacing: -0.02em;
  font-size: 1.05rem;
  position: relative;
}

.faq summary::-webkit-details-marker {
  display: none;
}

.faq summary::after {
  content: '+';
  position: absolute;
  right: 4px;
  top: 50%;
  transform: translateY(-50%);
  color: var(--text-muted);
  font-size: 20px;
  font-weight: 400;
}

.faq details[open] summary::after {
  content: '−';
  color: var(--accent);
}

.faq details p {
  color: var(--text-secondary);
  margin: 0 0 18px;
  padding-right: 24px;
}

/* ── The app illustration (NOT a screenshot) ────────────────────────────────
   The real dashboard sits behind Google auth, so a screenshot isn't reachable
   from a build script — and faking a pixel-accurate one would be a lie about a
   UI that then drifts. These are honest stylized diagrams of the real layout:
   recognisable, obviously drawn, impossible to mistake for a photo of the app.
   ------------------------------------------------------------------------ */

.frame {
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 14px;
  /* --frame-shadow, not a literal: a 55%-black drop shadow is right on ink and
     looks like soot on a light page. */
  box-shadow: var(--frame-shadow);
  font-size: 13px;
}

.frame__bar {
  display: flex;
  align-items: center;
  gap: 6px;
  padding-bottom: 12px;
  margin-bottom: 12px;
  border-bottom: 1px solid var(--border-soft);
}

.frame__dot {
  width: 9px;
  height: 9px;
  border-radius: 999px;
  background: var(--wire);
}

.frame__title {
  margin-left: 8px;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 12px;
  color: var(--text-muted);
  letter-spacing: -0.01em;
}

.row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-sm);
  margin-bottom: 8px;
  background: var(--bg-elevated);
}

.row:last-child {
  margin-bottom: 0;
}

/* A block of stacked lines (a brain-dump entry, one of Sara's replies) rather
   than a task row. Was three inline style="" attributes until 2026-07-17. */
.row--stack {
  flex-direction: column;
  align-items: stretch;
  gap: 10px;
  padding: 16px;
}

.row--sara {
  background: var(--accent-soft);
  border-color: var(--accent-soft);
}

.row__check {
  width: 15px;
  height: 15px;
  border-radius: 4px;
  border: 1.5px solid var(--wire);
  flex-shrink: 0;
}

.row__check--done {
  border-color: var(--accent-energy);
  background: var(--accent-energy);
}

/* Text-bars stand in for task text: a diagram of the layout that makes no claim
   about what any specific task says.

   These read as a LOADING SKELETON if you let them — Alex clocked it instantly,
   and he was right. The tell is uniformity: every skeleton loader on the web is
   identical grey pills at a single opacity. Three things break the illusion
   without inventing fake task text: bars sized like words rather than fields,
   varied weight (a "title" line is darker than a "detail" line), and never
   filling the row edge to edge. */
.bar {
  height: 9px;
  border-radius: 999px;
  background: var(--wire);
  flex: 1;
  max-width: 74%;
}

.bar--short {
  flex: 0 0 26%;
  opacity: 0.62;
}

.bar--mid {
  flex: 0 0 44%;
}

.bar--long {
  flex: 0 0 62%;
}

/* The leading bar in a row is the task's "title" — heavier, so the eye reads
   the row as a thing with a name rather than an empty input. */
.row .bar:first-of-type {
  background: var(--text-muted);
  opacity: 0.5;
}

.frame__focus .bar,
.row--stack .bar {
  max-width: 100%;
}

.chip {
  font-size: 10px;
  font-weight: 700;
  padding: 3px 8px;
  border-radius: 999px;
  background: var(--accent-soft);
  color: var(--accent);
  white-space: nowrap;
  letter-spacing: 0.03em;
}

.chip--spice {
  background: var(--spice-soft);
  color: var(--spice);
}

.frame__focus {
  border: 1px solid var(--accent-soft);
  background: var(--accent-soft);
  border-radius: var(--radius-sm);
  padding: 16px;
  margin-bottom: 12px;
  text-align: center;
}

.frame__timer {
  font-family: var(--font-display);
  font-size: 30px;
  font-weight: 800;
  letter-spacing: -0.04em;
  color: var(--accent);
  line-height: 1;
}

.frame__caption {
  display: block;
  margin-top: 14px;
  font-size: 11px;
  color: var(--text-muted);
  text-align: center;
  font-style: italic;
}

/* ── Split (text beside illustration) ───────────────────────────────────── */

.split {
  display: grid;
  gap: 40px;
  align-items: center;
  grid-template-columns: 1fr;
}

@media (min-width: 900px) {
  .split {
    grid-template-columns: 1fr 1fr;
    gap: 64px;
  }
  .split--flip .split__media {
    order: -1;
  }
}

/* ── Steps (how it works) ───────────────────────────────────────────────── */

.steps {
  display: grid;
  gap: 16px;
  counter-reset: step;
}

@media (min-width: 900px) {
  .steps--3 {
    grid-template-columns: repeat(3, 1fr);
  }
}

.step {
  position: relative;
  padding: 28px;
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-lg);
  background: var(--bg-card);
  box-shadow: var(--card-shadow);
}

.step::before {
  counter-increment: step;
  content: counter(step);
  display: block;
  font-family: var(--font-display);
  font-size: 44px;
  font-weight: 800;
  line-height: 1;
  color: var(--accent);
  opacity: 0.35;
  margin-bottom: 12px;
  letter-spacing: -0.05em;
}

/* ── Contrast list (the "not this / this" move) ─────────────────────────── */

.versus {
  display: grid;
  gap: 16px;
}

@media (min-width: 800px) {
  .versus {
    grid-template-columns: 1fr 1fr;
    gap: 20px;
  }
}

.versus__col {
  padding: 28px;
  border-radius: var(--radius-lg);
  border: 1px solid var(--border-soft);
}

.versus__col--bad {
  background: transparent;
}

.versus__col--good {
  background: var(--bg-card);
  border-color: var(--accent-soft);
  box-shadow: var(--card-shadow);
}

.versus ul {
  list-style: none;
  padding: 0;
  margin: 16px 0 0;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.versus li {
  display: flex;
  gap: 12px;
  align-items: flex-start;
  color: var(--text-secondary);
  line-height: 1.45;
}

.versus li::before {
  flex-shrink: 0;
  font-weight: 700;
}

.versus__col--bad li::before {
  content: '✕';
  color: var(--danger);
}

.versus__col--good li::before {
  content: '✓';
  color: var(--accent-energy);
}

/* ── Feature list rows ──────────────────────────────────────────────────── */

.feature-row {
  display: grid;
  gap: 20px;
  padding: 32px 0;
  border-bottom: 1px solid var(--border-soft);
  grid-template-columns: 1fr;
}

@media (min-width: 800px) {
  .feature-row {
    grid-template-columns: 200px 1fr;
    gap: 40px;
    padding: 40px 0;
  }
}

.feature-row:last-child {
  border-bottom: none;
}

.feature-row__label {
  display: flex;
  align-items: flex-start;
  gap: 12px;
}

/* h2 semantically (top-level page sections), sized like a card heading —
   heading LEVEL is structure, heading SIZE is design, and they don't have to
   agree. See the note on feature() in pages/features.ts. */
.feature-row__label h2 {
  font-size: 1.15rem;
  letter-spacing: -0.02em;
}

.feature-row p {
  margin: 0;
  color: var(--text-secondary);
  max-width: 60ch;
}

/* ── Pricing ────────────────────────────────────────────────────────────── */

.price-grid {
  display: grid;
  gap: 20px;
  grid-template-columns: 1fr;
  align-items: start;
}

@media (min-width: 900px) {
  .price-grid {
    grid-template-columns: repeat(3, 1fr);
  }
}

.plan {
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-lg);
  padding: 30px;
  background: var(--bg-card);
  box-shadow: var(--card-shadow);
  display: flex;
  flex-direction: column;
  height: 100%;
}

/* The only real plan today gets the visual weight, and the forecast tiers
   are deliberately dimmed — the hierarchy has to match the truth. */
.plan--now {
  border-color: var(--spice);
  box-shadow: 0 20px 60px -30px var(--spice);
}

/* Forecast tiers are deliberately flat and unelevated: they don't exist, so
   they mustn't look as solid as the plan that does.

   ⚠️ NO `opacity` HERE, and it must not come back. It used to carry 0.72, which
   composites the WHOLE card — text included — against the page: --text-secondary
   blended to 3.13:1 in light and 4.27:1 in dark, i.e. the tier text failed WCAG
   AA in both themes. The honesty mechanism was itself the accessibility bug, and
   it's an easy one to miss because measuring the token says 5.67:1 while the
   rendered pixels say 3.13:1 — opacity is applied after the colour resolves.
   The dashed --wire border and the literal "Planned · not yet real" badge
   already carry "this isn't real"; dimming the copy bought no extra meaning and
   cost legibility. Dim chrome, never body text. */
.plan--soon {
  border-style: dashed;
  border-color: var(--wire);
  background: transparent;
  box-shadow: none;
}

/* The dimming the opacity used to do, moved onto elements with contrast
   headroom: the price and name compute ~6.5:1, so they can afford to recede. */
.plan--soon .plan__price,
.plan--soon .plan__name {
  color: var(--text-muted);
}

.plan__name {
  font-family: var(--font-display);
  font-size: 1.15rem;
  font-weight: 700;
  letter-spacing: -0.02em;
  margin: 0;
}

.plan__price {
  font-family: var(--font-display);
  font-size: 3rem;
  font-weight: 800;
  letter-spacing: -0.05em;
  line-height: 1;
  margin: 18px 0 4px;
}

.plan__per {
  color: var(--text-muted);
  font-size: 13px;
  margin: 0 0 20px;
}

.plan__tagline {
  color: var(--text-secondary);
  font-size: 14px;
  margin: 8px 0 0;
  min-height: 2.6em;
}

.plan ul {
  list-style: none;
  padding: 0;
  margin: 20px 0 24px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  flex: 1;
}

.plan li {
  display: flex;
  gap: 10px;
  font-size: 14px;
  color: var(--text-secondary);
  line-height: 1.4;
}

.plan li::before {
  content: '✓';
  color: var(--accent-energy);
  font-weight: 700;
  flex-shrink: 0;
}

.plan--soon li::before {
  content: '·';
  color: var(--text-muted);
}

.plan .btn {
  width: 100%;
}

/* ── Callout ────────────────────────────────────────────────────────────── */

.callout {
  border: 1px solid var(--accent-soft);
  background: var(--accent-soft);
  border-radius: var(--radius);
  padding: 20px 24px;
  margin: 28px 0;
}

.callout p {
  margin: 0;
  color: var(--text-secondary);
}

.callout strong {
  color: var(--text-primary);
}

.callout--spice {
  border-color: var(--spice-soft);
  background: var(--spice-soft);
}

/* ── Big closing CTA ────────────────────────────────────────────────────── */

.cta-band {
  text-align: center;
  padding: 80px 0;
  border-top: 1px solid var(--border-soft);
}

@media (min-width: 900px) {
  .cta-band {
    padding: 120px 0;
  }
}

.cta-band h2 {
  font-size: clamp(2rem, 6vw, 3.4rem);
  max-width: 18ch;
  margin: 0 auto;
}

/* ── Newsletter ─────────────────────────────────────────────────────────── */

.newsletter {
  display: flex;
  flex-direction: column;
  gap: 10px;
  max-width: 460px;
  margin: 24px auto 0;
}

/* Visible label (not placeholder-only) for the email field -- better for
   cognitive accessibility, which is the whole audience here. */
.newsletter__label {
  font-size: 13px;
  color: var(--text-secondary);
  text-align: left;
}

@media (min-width: 600px) {
  .newsletter__row {
    display: flex;
    gap: 10px;
  }
  .newsletter__row input {
    flex: 1;
  }
}

.newsletter input {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 13px 20px;
  font-size: 16px;
  font-family: inherit;
  color: var(--text-primary);
  outline: none;
  width: 100%;
}

.newsletter input:focus {
  border-color: var(--accent);
}

.newsletter__row {
  margin-bottom: 4px;
}

@media (max-width: 599px) {
  .newsletter .btn {
    width: 100%;
    margin-top: 10px;
  }
}

/* ── Misc ───────────────────────────────────────────────────────────────── */

.tag-list {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 20px;
}

.hr {
  border: none;
  border-top: 1px solid var(--border-soft);
  margin: 48px 0;
}

/* ══════════════════════════════════════════════════════════════════════════
   MOTION — the animation pass (2026-07-17 evening)
   ══════════════════════════════════════════════════════════════════════════
   The rules, because motion on a site FOR overstimulated brains is a knife
   edge: everything here is slow, small, and optional. Nothing loops fast,
   nothing flashes, nothing moves more than a few px, and the global
   prefers-reduced-motion block above kills all of it dead. Playful is the
   seasoning, calm is the dish.

   Nothing here is gated on JavaScript: entrances are load-time CSS animations
   with `both` fill, so every element ends visible by construction. See the
   ARCHITECTURE NOTE below for why scroll-reveal was deliberately rejected. */

/* ARCHITECTURE NOTE — why there is no scroll-reveal here.
   The first draft hid content at opacity 0 until an IntersectionObserver
   revealed it. Verification found environments where the observer (and even
   timers) never fire — leaving the site INVISIBLE. A failure mode of "blank
   page" is never worth an entrance effect, so the model is inverted: nothing
   is ever hidden pending JS. Entrances are pure CSS animations that run on
   load, `both`-filled, and end visible by construction. If CSS animations
   don't run, the page simply shows — done. */

@media (prefers-reduced-motion: no-preference) {
  /* Load entrance for the hero/page-head — the strong opening on all 22
     pages. Staggered: eyebrow, then h1, then lead. */
  .page-head .eyebrow,
  .glow .eyebrow {
    animation: rise-in 0.5s cubic-bezier(0.22, 1, 0.36, 1) both;
  }
  .page-head h1,
  .glow h1 {
    animation: rise-in 0.6s cubic-bezier(0.22, 1, 0.36, 1) 0.08s both;
  }
  .page-head .lead,
  .glow .lead {
    animation: rise-in 0.6s cubic-bezier(0.22, 1, 0.36, 1) 0.18s both;
  }
  .glow .btn-row,
  .glow .tag-list {
    animation: rise-in 0.6s cubic-bezier(0.22, 1, 0.36, 1) 0.28s both;
  }
  .glow .split__media {
    animation: rise-in 0.7s cubic-bezier(0.22, 1, 0.36, 1) 0.3s both;
  }
}

/* Transform ONLY — opacity stays 1 throughout. Verification found renderers
   whose animation clocks freeze at the from-state; an entrance that fades
   from 0 makes the page invisible there, one that only rises costs a 14px
   offset. Legibility is never the price of an entrance. */
@keyframes rise-in {
  from { transform: translateY(14px); }
  to { transform: none; }
}

/* ── Hero atmosphere ────────────────────────────────────────────────────── */

/* Floating brand chips — 🌀🌶️⚡ drifting slowly behind the home hero. Low
   opacity, blurred a touch, purely decorative (aria-hidden in the markup). */
.float-chips {
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  overflow: hidden;
}

.float-chip {
  position: absolute;
  font-size: 28px;
  opacity: 0.16;
  filter: blur(0.5px);
  animation: drift 14s ease-in-out infinite;
}

/* Mobile: the hero text column spans the full width, so the two TOP chips sit
   directly behind the headline — measured overlapping the h1 at 375px. Visual
   noise behind words is exactly what this audience doesn't need, so phones
   keep only the two bottom chips, dimmer. Playful stays; clutter goes. */
@media (max-width: 640px) {
  .float-chip:nth-child(1),
  .float-chip:nth-child(3) {
    display: none;
  }
  .float-chip {
    opacity: 0.11;
  }
}

/* Chip 1 (top-left, left:6%) is positioned relative to the VIEWPORT, not the
   1120px .shell, so between 641px and the shell's max width it drifts over the
   left text column and grazes the H1. Hide it across that whole band, not just
   phones -- the 640px rule above only covered the low end. (audit, DESKTOP low) */
@media (max-width: 1120px) {
  .float-chip:nth-child(1) {
    display: none;
  }
}

.float-chip:nth-child(2) { animation-delay: -4s; animation-duration: 18s; font-size: 22px; }
.float-chip:nth-child(3) { animation-delay: -9s; animation-duration: 16s; font-size: 34px; }
.float-chip:nth-child(4) { animation-delay: -6s; animation-duration: 21s; font-size: 20px; }

@keyframes drift {
  0%, 100% { transform: translate(0, 0) rotate(-4deg); }
  33% { transform: translate(8px, -16px) rotate(3deg); }
  66% { transform: translate(-10px, -6px) rotate(-2deg); }
}

/* The coral punchline gets a highlighter sweep: a hand-drawn-feeling underline
   that draws itself in once the hero reveals. Gradient background, animated
   background-size — no extra elements, no SVG. */
.spice-text {
  background-image: linear-gradient(
    color-mix(in srgb, var(--spice) 32%, transparent),
    color-mix(in srgb, var(--spice) 32%, transparent)
  );
  background-repeat: no-repeat;
  background-position: 0 88%;
  background-size: 0% 0.18em;
}

@media (prefers-reduced-motion: no-preference) {
  .spice-text {
    animation: sweep 0.9s cubic-bezier(0.22, 1, 0.36, 1) 0.5s forwards;
  }
}

@media (prefers-reduced-motion: reduce) {
  .spice-text { background-size: 100% 0.18em; }
}

@keyframes sweep {
  to { background-size: 100% 0.18em; }
}

/* ── The principles ticker ──────────────────────────────────────────────────
   A slow, calm marquee of the product's convictions. Doubles the track for a
   seamless loop; pauses on hover; hidden from AT (a static sentence next to
   it carries the meaning for screen readers). 40s per loop — a drift, not a
   news crawl. */
.ticker {
  overflow: hidden;
  border-top: 1px solid var(--border-soft);
  border-bottom: 1px solid var(--border-soft);
  padding: 14px 0;
  background: var(--bg-elevated);
}

.ticker__track {
  display: flex;
  gap: 0;
  width: max-content;
  animation: ticker 40s linear infinite;
}

.ticker:hover .ticker__track { animation-play-state: paused; }

.ticker__item {
  white-space: nowrap;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 14px;
  letter-spacing: 0.02em;
  color: var(--text-muted);
  padding: 0 18px;
}

.ticker__item em {
  font-style: normal;
  /* 14px body text: --spice measured 3.45:1 on the white band in light.
     The ember token clears 4.92:1. */
  color: var(--spice-text);
}

@keyframes ticker {
  to { transform: translateX(-50%); }
}

@media (prefers-reduced-motion: reduce) {
  .ticker__track { animation: none; flex-wrap: wrap; width: auto; justify-content: center; }
  /* The track is doubled for the seamless loop; static, that prints every
     conviction twice. Hide the second copy (items 10-18 of 18). */
  .ticker__track .ticker__item:nth-child(n + 10) { display: none; }
}

/* ── Living illustrations ───────────────────────────────────────────────────
   The drawn app frames get a pulse of life once revealed — the product
   demonstrating itself. All slow, all subtle. */

@media (prefers-reduced-motion: no-preference) {
  /* The focus timer breathes like the real one's spiral. The glow itself is
     STATIC (painted once) and only opacity animates — opacity is applied at
     composite time, so an infinite loop costs no per-frame paint. The first
     version animated text-shadow directly, which re-rasterized the digits
     every frame, forever, on every visit. Audit finding; same class as the
     breathe spiral, which keeps its filter loop as the one accepted signature
     indulgence (tiny paint area, brand-defining). */
  .frame__timer {
    text-shadow: 0 0 18px var(--glow);
    animation: timer-fade 2.6s ease-in-out infinite;
  }

  /* Brain-dump lines "arrive" on load; `both` means they end (and rest)
     fully drawn, so a late scroller just sees the finished frame. */
  .row--stack .bar {
    transform-origin: left center;
    animation: bar-in 0.5s cubic-bezier(0.22, 1, 0.36, 1) 0.4s both;
  }
  .row--stack .bar:nth-child(2) { animation-delay: 0.55s; }
  .row--stack .bar:nth-child(3) { animation-delay: 0.7s; }
  .row--stack .bar:nth-child(4) { animation-delay: 0.85s; }

  /* Sara's receipt chips pop in — the trust story, animated. */
  .frame .chip {
    animation: chip-pop 0.4s cubic-bezier(0.34, 1.56, 0.64, 1) 0.8s both;
  }
  .frame .chip:nth-of-type(2) { animation-delay: 1s; }

  /* The done-checkbox ticks itself in. */
  .row__check--done {
    animation: chip-pop 0.35s cubic-bezier(0.34, 1.56, 0.64, 1) 1.1s both;
  }
}

@keyframes timer-fade {
  0%, 100% { opacity: 0.78; }
  50% { opacity: 1; }
}

@keyframes bar-in {
  from { transform: scaleX(0.35); }
  to { transform: scaleX(1); }
}

@keyframes chip-pop {
  from { transform: scale(0.6); }
  to { transform: scale(1); }
}

/* ── Playful edges ──────────────────────────────────────────────────────── */

/* Cards tilt a hair on hover — alternating direction so a grid feels like
   sticky notes shifting underhand, a quiet nod to the sticky-note idea
   without committing the whole layout to it. */
@media (prefers-reduced-motion: no-preference) {
  .card--link:hover { transform: translateY(-3px) rotate(-0.4deg); }
  .grid > .card--link:nth-child(even):hover { transform: translateY(-3px) rotate(0.4deg); }

  .btn--spice:hover { transform: translateY(-1px) rotate(-0.5deg); }

  /* The CTA band's spiral spins up gently on reveal — one turn, then back to
     breathing. */
  .cta-band .spiral-mark {
    animation: spin-in 1.1s cubic-bezier(0.22, 1, 0.36, 1), breathe 5.5s ease-in-out 1.1s infinite;
  }
}

@keyframes spin-in {
  from { transform: rotate(-160deg) scale(0.6); }
  to { transform: rotate(0) scale(1); }
}

/* Screen-reader-only: the static sentence that stands in for the ticker. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* ── Telegram chat illustration (Medulla) ───────────────────────────────────
   Medulla lives in Telegram, so its page needs to SHOW a conversation, not
   just describe one. Same honesty rule as .frame: this is a hand-built, clearly
   stylised diagram of a real exchange, captioned as such — never a screenshot
   and never mistakable for one. Uses the brand palette (not Telegram's blue) so
   it reads as ours. Real, meaningful text lives inside, so unlike .frame this
   is NOT role="img" — screen readers read the bubbles, with sr-only speaker
   labels for who's talking.
   ------------------------------------------------------------------------ */
.tg {
  max-width: 430px;
  margin: 0 auto;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--frame-shadow);
  overflow: hidden;
  font-size: 13px;
}
.tg__bar {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 14px;
  background: var(--bg-elevated);
  border-bottom: 1px solid var(--border-soft);
}
.tg__avatar {
  width: 30px;
  height: 30px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: var(--accent-soft);
  color: var(--accent);
  font-size: 15px;
  flex: none;
}
.tg__who { display: flex; flex-direction: column; line-height: 1.25; }
.tg__name {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 13px;
  color: var(--text-primary);
}
.tg__status { font-size: 10px; color: var(--accent-energy); }
.tg__app {
  margin-left: auto;
  font-size: 10px;
  color: var(--text-muted);
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.tg__body {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 16px 14px;
  background: var(--bg);
}
.tg__msg {
  max-width: 84%;
  padding: 9px 12px;
  border-radius: 16px;
  line-height: 1.45;
  color: var(--text-secondary);
}
.tg__msg strong { color: var(--text-primary); }
.tg__msg--in {
  align-self: flex-start;
  background: var(--bg-card);
  border: 1px solid var(--border-soft);
  border-bottom-left-radius: 5px;
}
.tg__msg--out {
  align-self: flex-end;
  background: var(--accent-soft);
  border: 1px solid var(--accent-soft);
  color: var(--text-primary);
  border-bottom-right-radius: 5px;
}
/* An approval card — the "it proposes, you tap" moment made visible. */
.tg__card {
  align-self: flex-start;
  max-width: 90%;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: 16px;
  border-bottom-left-radius: 5px;
  padding: 12px 13px;
}
.tg__card-title {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 12px;
  color: var(--text-primary);
  margin: 0 0 7px;
}
.tg__card-line { font-size: 12px; color: var(--text-secondary); margin: 3px 0; }
.tg__card-line strong { color: var(--text-primary); }
.tg__btns { display: flex; gap: 8px; margin-top: 11px; }
.tg__btn {
  flex: 1;
  text-align: center;
  font-size: 11px;
  font-weight: 700;
  padding: 8px;
  border-radius: 999px;
  letter-spacing: 0.02em;
}
.tg__btn--yes { background: var(--spice); color: var(--spice-ink); }
.tg__btn--no { background: transparent; color: var(--text-muted); border: 1px solid var(--border); }
.tg__btn--watch {
  flex: none;
  width: 100%;
  background: var(--wire);
  color: var(--text-muted);
}
/* A tool receipt — the audit chip, exactly as it appears after a real call. */
.tg__receipt {
  align-self: flex-start;
  font-size: 11px;
  color: var(--text-muted);
  padding: 1px 8px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
.tg__receipt--fail { color: var(--danger); }
.tg__caption {
  display: block;
  margin: 14px auto 0;
  max-width: 430px;
  font-size: 11px;
  color: var(--text-muted);
  text-align: center;
  font-style: italic;
}
