/* alex-design.css — THE FOUNDATION. [M36-R28/R30] design phase 1.
 * Spec: DESIGN_BRIEFING_alex_app_aialex.md, extracted from the live aialex.io site.
 *
 * ★★★ THE GOAL IN ONE LINE (the briefing's own): the app must feel like the same product the
 *     website sells. A customer who clicks "Start building" should not feel a doorway between them.
 *
 * ★★★★★ ONE DEFINITION, TWELVE CONSUMERS. Every page in `public/` carried its OWN `:root` token
 *   block — twelve copies of one fact, which is how twelve pages drift apart. This file is the
 *   single source; each page links it and deletes its local block. That is the same
 *   derive-never-duplicate law the rest of this codebase runs on, applied to colour.
 *
 * ⚠ THE LEGACY ALIASES AT THE BOTTOM ARE LOAD-BEARING, NOT LAZINESS. The existing pages are written
 *   against `--bg / --muted / --accent-press / --chip`. Re-pointing those names at the new tokens
 *   moves every pixel to the new system WITHOUT a rewrite of twelve stylesheets in one commit —
 *   and a rewrite of that size is exactly where a design pass quietly breaks a working product.
 *   The aliases retire naturally as pages are touched in phase 2.
 */

/* ── §3 TYPE. Fraunces is Alex's voice, Inter is the human's. The single strongest brand
 *    signature, per the briefing, and the one thing it says to keep above all others. */
@import url('https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,300..600&family=Inter:wght@400..700&display=swap');

/* ── §2 COLOR TOKENS — LIGHT (default). Warm bone, never white.
 *    "Light mode's warmth is the brand's most distinctive quality." */
:root {
  --paper:      #F1ECE1;
  --paper-2:    #E8E1D2;
  --surface:    #FBF8F1;
  --ink:        #211E19;
  --ink-soft:   #5C5546;
  --ink-faint:  #7D7565;
  --line:       rgba(33, 30, 25, .13);
  --line-soft:  rgba(33, 30, 25, .08);
  --accent:       #1B2FD6;
  --accent-hover: #1526B0;
  --on-accent:    #FFFFFF;
  --good:       #2E9463;

  /* The accent at 22% is the selection colour (§2 theme mechanics). */
  --accent-22: rgba(27, 47, 214, .22);
  --accent-16: rgba(27, 47, 214, .16);

  --font-display: 'Fraunces', Georgia, 'Times New Roman', serif;
  --font-ui: 'Inter', -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;

  /* §5 THE HEARTBEAT RULE: every ambient loop shares one clock. In the app, unify on 2.6s. */
  --beat: 2.6s;
  --ease: cubic-bezier(.2, .75, .25, 1);
}

/* ── §2 DARK — cool slate, a sibling and not a twin. ⛔ "Do not warm the dark theme." */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --paper:      #0F1319;
    --paper-2:    #131820;
    --surface:    #161B23;
    --ink:        #E8ECF2;
    --ink-soft:   #A8B2C2;
    --ink-faint:  #7C8595;
    --line:       rgba(232, 236, 242, .13);
    --line-soft:  rgba(232, 236, 242, .07);
    --accent:       #7C8CFF;
    --accent-hover: #97A4FF;
    --on-accent:    #0A0D12;
    --good:       #5FBF8C;
    --accent-22: rgba(124, 140, 255, .22);
    --accent-16: rgba(124, 140, 255, .16);
  }
}
/* The explicit choice wins in BOTH directions, so the toggle beats the OS in either theme. */
:root[data-theme="dark"] {
  --paper:      #0F1319;
  --paper-2:    #131820;
  --surface:    #161B23;
  --ink:        #E8ECF2;
  --ink-soft:   #A8B2C2;
  --ink-faint:  #7C8595;
  --line:       rgba(232, 236, 242, .13);
  --line-soft:  rgba(232, 236, 242, .07);
  --accent:       #7C8CFF;
  --accent-hover: #97A4FF;
  --on-accent:    #0A0D12;
  --good:       #5FBF8C;
  --accent-22: rgba(124, 140, 255, .22);
  --accent-16: rgba(124, 140, 255, .16);
}

/* ── §2 THEME MECHANICS ─────────────────────────────────────────────────────────────────────── */
::selection { background: var(--accent-22); }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

/* ── §3 TYPE RULES ──────────────────────────────────────────────────────────────────────────── */
/* 🚨★★★★★ `html body`, NOT `body`, AND THE RENDER IS WHY.
 * Every page sets `body { font: 16px/1.55 -apple-system, ... }` as a SHORTHAND in its own inline
 * <style>, which comes after this sheet at equal specificity — so a plain `body { font-family }`
 * here LOST, silently. Measured in the browser: the tokens were all correct, the dot was correct,
 * and `body_font` still read `-apple-system, "Segoe UI", ...`. **144 green cells said phase 1 had
 * landed its type and the render said it had not.** `html body` (0,0,2) wins without `!important`.
 * ⚠ FAMILY ONLY, DELIBERATELY. The pages' shorthand also sets 16px/1.55; forcing the briefing's
 *   17px/1.65 across twelve working layouts in the same commit is how a design pass breaks a
 *   product. The size and leading move page by page in phase 2. */
html body { font-family: var(--font-ui); letter-spacing: -.006em; }
h1, h2, h3, .display {
  font-family: var(--font-display); font-weight: 430;
  letter-spacing: -.02em; line-height: 1.16; text-wrap: balance;
}
/* ★★★ §3, AND IT IS CALLED NON-NEGOTIABLE IN A PRODUCT THAT SHOWS USAGE NUMBERS.
 *   Anything that can change gets tabular figures so it does not jitter as it counts. */
.num, .tabular, [data-num],
.ucard .v, .usagechip .uc-n, .ubreak td.num, .kpi .v, .bars b, time, .timecode {
  font-variant-numeric: tabular-nums;
}
/* §3 eyebrow labels. */
.eyebrow { font-family: var(--font-ui); font-size: .73rem; font-weight: 580;
  letter-spacing: .13em; text-transform: uppercase; color: var(--ink-soft); }

/* ── §1.5 ALEX SPEAKS IN SERIF. The single strongest product-brand signature; keep it. ────────── */
.alex-voice, .msg.alex, .bubble.alex { font-family: var(--font-display); font-size: 1.05rem; }
.human-voice, .msg.user, .bubble.user { font-family: var(--font-ui); font-size: .95rem; }

/* ── §5.1 THE BRAND DOT: opacity only. ⛔ §8 hard never: no glow, no scale. ──────────────────── */
@keyframes alexDotBreathe { 0%, 100% { opacity: 1; } 50% { opacity: .45; } }
.wm .dot, .live-dot {
  animation: alexDotBreathe var(--beat) ease-in-out infinite !important;
  box-shadow: none !important;
  transform: none !important;
}

/* ── §5.1b THE MIND OF ALEX — the thinking state. [M36-R58], his own order from a screenshot ────
   *"while Alex is thinking in the chat, the loading state is A BIG BLUE PULSING DOT - the mind of
   Alex - right where the reply will appear."*

   ★★★ IT IS THE BRAND DOT GROWN UP, AND THAT IS THE WHOLE DESIGN. It reuses `alexDotBreathe` and
   `--beat` above rather than declaring a second keyframe and a second duration. Two declarations
   of one heartbeat is the drift this repo keeps paying for: the day somebody retunes the clock,
   a private copy keeps the old one and the page develops two pulses. Derive, never duplicate.

   ⛔ §8's HARD NEVERS HOLD AT ANY SIZE, and size is exactly when people start reaching for them:
   NO glow, NO box-shadow, NO scale, NO filter, NO second hue. The colour is the accent TOKEN, so
   it is ultramarine in light and periwinkle in dark with no literal anywhere near it.
   ★ Size is a token on purpose. His eyes are the acceptance for this component, so tuning it is
   one number in one place rather than a hunt through a page. */
/* ★★★★★ HIS OWN WORDS, 2026-08-17 from a screenshot of the live chat: *"MAKE IT PULSE A BIT MORE
   HARDER AND VIBRANT ... ITS SUPPOSED TO ENSENUATE CONSCIOUSNESS."*
   ⇒ SO THE MIND DOT GETS ITS OWN AMPLITUDE, ON THE SHARED CLOCK. It used to borrow
   `alexDotBreathe`, which is the 7px BRAND DOT's animation and is pinned by the briefing at
   *opacity 1 to .45 over 2s* (§5 row 1, §4 live indicator) and used on three pages. Deepening THAT
   would have retuned every dot on the site to satisfy one component, which is the opposite of
   derive-never-duplicate: the brand dot's spec is not this creature's to spend.
   ⇒ What stays SHARED is the CLOCK (`--beat`), because §5's heartbeat rule is that ambient loops
     share one clock family. What is its own is the SWING.
   ★ A DOUBLE BEAT, NOT A SINE, and that is the whole point of "consciousness": a systole and a
     lighter follow, so it reads as something alive rather than something fading in and out. The
     trough goes to .10 instead of .45, which is where the "harder and vibrant" actually comes from.
   ⛔ AND §8'S HARD NEVERS STILL HOLD, which is what makes this legitimate rather than a loophole:
     OPACITY ONLY. No glow, no box-shadow, no scale, no filter, no second hue, no private duration. */
@keyframes alexMindPulse {
  0%   { opacity: 1; }
  16%  { opacity: .94; }
  40%  { opacity: .10; }
  58%  { opacity: .90; }
  76%  { opacity: .34; }
  100% { opacity: 1; }
}

:root { --mind-size: 24px; }
.alex-mind {
  display: inline-block;
  width: var(--mind-size);
  height: var(--mind-size);
  border-radius: 50%;
  background: var(--accent);
  animation: alexMindPulse var(--beat) ease-in-out infinite;
  box-shadow: none;
  transform: none;
  filter: none;
  /* It stands where a line of his text would, so the reply lands where the dot was. */
  vertical-align: middle;
  margin: 6px 0;
}

/* ── §5.1c THE USAGE RING — his own order from a screenshot, 2026-08-18, pointing at the Claude
   composer: *"make the usage chip a ring, not a bar."* ─────────────────────────────────────────

   ★★★ ONE COMPONENT, BOTH SURFACES, AND THAT IS THE WHOLE REASON IT LIVES HERE RATHER THAN ON A
   PAGE. The usage chip exists TWICE — under the chat composer and in the my-alex header — and the
   two hand-written copies HAD ALREADY DRIFTED before anyone changed a shape: my-alex's bar
   hardcoded `#e0a33a`, which is the DARK amber, over a `rgba(255,255,255,.10)` rail, so in light
   theme it was the wrong amber on an invisible track. He pointed at one screen. A shape that
   changes on one screen and not the other is the same miss twice, so the shape is defined once.

   ⛔ AMBER, NEVER RED, ENFORCED BY NO RED EXISTING. There is deliberately no `.bad` variant of this
   component to reach for. Going over is a heavy month on a plan that caps it, not a fault, and a
   red ring renders a promise as an alarm. `--bad` is a token this very file defines, which is
   exactly why the ban has to be written down: it is one word away at all times.

   ⚠ ROTATED -90° BECAUSE AN SVG CIRCLE STARTS AT THREE O'CLOCK. Left alone the arc opens on the
   right edge and reads as a dial somebody forgot to zero.

   ★ NO CIRCUMFERENCE APPEARS ANYWHERE IN THIS FILE. The arc is set by the page from the circle's
   OWN `r`, so there is no second copy of the number to drift out of step with the markup. This is
   money on somebody's screen; a ring that is 4% short is a lie with a decimal point.

   ⛔ AND THE UNSET STATE IS EMPTY, NEVER FULL — see the dash below. That default is load-bearing. */
:root { --ring-size: 14px; }
.alex-ring {
  width: var(--ring-size); height: var(--ring-size); flex: 0 0 var(--ring-size);
  display: block; transform: rotate(-90deg);
}
.alex-ring circle { fill: none; stroke-width: 4; }
.alex-ring .ring-track { stroke: var(--line); }
.alex-ring .ring-fill {
  stroke: var(--muted);
  stroke-linecap: round;
  /* 🚨★★★★★ AN SVG CIRCLE WITH NO DASH RENDERS A COMPLETE RING. So a page whose script never ran —
     an error before the meter read, an old cached bundle — would show every owner a FINISHED
     allowance and no words to contradict it. The empty default makes the failure read as "nothing
     measured yet", which is the truth. Never delete this line to "clean up". */
  stroke-dasharray: 0 9999;
  transition: stroke-dashoffset .3s ease;
}
/* ★ THE TONE RIDES ON THE RING ITSELF, never on an ancestor. `.amber` is also a live class on
   roofing.html, so a descendant selector in a SHARED sheet would be a rule waiting to reach a page
   that never asked for it. */
.alex-ring.amber .ring-fill { stroke: var(--warn); }

/* ── §8 REDUCED MOTION: no animation without a fallback, no exceptions. ──────────────────────── */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
    scroll-behavior: auto !important;
  }
  /* ★ STATIC AND FULLY PRESENT, never faded out and never invisible. A thinking indicator that
     reduced motion turns into nothing is a chat that looks frozen to the person who needs the
     accommodation most. */
  .wm .dot, .live-dot, .alex-mind { opacity: 1 !important; }
}

/* ═══ LEGACY ALIASES ═══════════════════════════════════════════════════════════════════════════
 * The twelve existing pages are written against these names. Pointing them at the tokens above
 * moves the whole surface onto the new system in one commit, without rewriting twelve stylesheets
 * at once. ⚠ These are ALIASES, never second definitions: change a colour above and every page
 * follows. Retire them page by page in phase 2.
 * ⛔ The retired blues (#1B5EF5, #2563EB, #1E65FF, #1E2BFF, #0000FF) and the retired near-black
 *   #0F0F12 appear NOWHERE in this file, which is the point of it.
 */
:root {
  --bg: var(--paper);
  --muted: var(--ink-soft);
  --accent-press: var(--accent-hover);
  --accent-soft: var(--accent-16);
  --chip: var(--paper-2);
  --userbubble: var(--paper-2);
  --warn: #B4661F;
  /* ★ --bad: THE ERROR RUNG THE SYSTEM WAS MISSING, added because the sweep needed it and the
   * alternative was every page inventing its own red again ([M36-R40] names live.html's literal
   * status ramp explicitly). ⚠ CHECKED FIRST: the site's own stylesheet ships --good and NOTHING
   * for error, so this is a genuine gap, not a duplicate of something upstream.
   * Values MEASURED, not picked, in the same family as --good and --warn:
   *   #B3261E on paper 5.55:1, on surface 6.16:1   ·   #E9756B on slate 6.40:1, on surface 5.94:1
   * Both clear the 4.5 body-text floor in their own theme, which --good and --warn do not, so this
   * one is safe for text as well as for borders. */
  --bad: #B3261E;
}
:root[data-theme="dark"], :root:not([data-theme="light"]) { --warn: #E0A33A; --bad: #E9756B; }

/* ═══ PHASE 2 COMPONENT: THE EYEBROW ═══════════════════════════════════════════════════════════
 * §3's eyebrow spec, and the values are TAKEN FROM THE SITE'S OWN STYLESHEET rather than
 * re-derived from the prose, so the two surfaces cannot drift by a rounding: the site ships
 * .75rem / 560 / .16em / uppercase / --ink-soft. If the site changes, this is the one line to move.
 * ⚠ NOT ACCENT-COLOURED. §1.2 gives the accent a job description and a section label is not on it.
 */
.eyebrow {
  display: block;
  font-family: var(--font-ui);
  font-size: .75rem;
  font-weight: 560;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--ink-soft);
}

/* ═══ PHASE 2 COMPONENT: THE THEME SWITCH ══════════════════════════════════════════════════════
 * The control for `alex-theme.js`. It lives HERE, not on the pages, for the same reason the tokens
 * do: a switch styled twelve times is a switch that looks like twelve different products.
 * §4 says a button is a full pill and an outline button is transparent with a hairline that
 * darkens to ink on hover. This is that button, at the smallest size the vocabulary allows.
 * ⚠ NOT AN ACCENT ELEMENT. §1 rule 2 gives the accent a job description (primary action, links,
 *   live indicators, the numbers that matter, the brand dot) and a theme switch is none of them.
 *   One saturated element per viewport, and on these pages that is the brand dot or the CTA.
 */
.alex-theme-toggle {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 7px 14px 7px 12px;
  border: 1px solid var(--line);
  border-radius: 999px;                 /* §4: pills belong to buttons */
  background: transparent;
  color: var(--ink-soft);
  font-family: var(--font-ui);
  font-size: .78rem; font-weight: 560; letter-spacing: .01em; line-height: 1;
  cursor: pointer;
  transition: color .28s var(--ease), border-color .28s var(--ease);
}
.alex-theme-toggle:hover { color: var(--ink); border-color: var(--ink-faint); }
.alex-theme-toggle svg { display: block; flex: 0 0 auto; }
/* The word is the affordance on a wide screen and pure noise on a narrow one. */
@media (max-width: 560px) { .alex-theme-label { display: none; } }

/* ★ FOR PAGES WITH NO <header> TO MOUNT IT IN. [M36-R40] orders a control he can SEE on EVERY
 * owner-facing page, so a page without a header bar is not an exemption; it gets a fixed one.
 * ⚠ It carries a --surface fill rather than `transparent` because it floats over page content,
 *   and a hairline pill over running text is not a control, it is a smudge. */
.alex-theme-toggle.alex-theme-floating {
  position: fixed; top: 14px; right: 14px; z-index: 50;
  background: var(--surface);
  box-shadow: 0 1px 2px rgba(0, 0, 0, .04), 0 8px 24px rgba(0, 0, 0, .06);
}
