/* =========================================================================
   MY Currency Rate Tracker — Dashboard styles
   Design: a money-changer "rate board" reimagined — warm parchment ground,
   deep ink navy, a currency-gold accent, and monospaced tabular digits for
   every number so rates read like they're lit up on a physical board.
   ========================================================================= */

:root {
  /* --- palette (light, default) --- */
  --paper: #f6f1e4;
  --surface: #fffdf8;
  --surface-raised: #ffffff;
  --ink: #17233c;
  --ink-soft: #4d5872;
  --ink-faint: #8791a6;
  --line: #e6dcc3;
  --line-strong: #d8cba7;
  --accent: #b9822a;
  --accent-strong: #9c6c1f;
  --accent-ink: #2c1c05;
  --accent-tint: #f3e3c4;

  /* semantic status colors — separate from the gold accent */
  --status-live-bg: #e4f3ea;
  --status-live-fg: #1c7a4e;
  --status-waiting-bg: #fbf1d9;
  --status-waiting-fg: #93690b;
  --status-reached-bg: #fae4de;
  --status-reached-fg: #b23a20;
  --status-error-bg: #f1e6e0;
  --status-error-fg: #8a4a2a;
  --status-mock-bg: #eae6f5;
  --status-mock-fg: #5b4a9c;

  --up: #1c7a4e;
  --down: #b23a20;

  --radius-sm: 6px;
  --radius-md: 12px;
  --radius-lg: 18px;
  --shadow-card: 0 1px 2px rgba(23, 35, 60, 0.06), 0 8px 24px -12px rgba(23, 35, 60, 0.18);

  --font-display: "Fraunces", "Iowan Old Style", "Palatino Linotype", serif;
  --font-body: "IBM Plex Sans", -apple-system, "Segoe UI", sans-serif;
  --font-mono: "IBM Plex Mono", ui-monospace, "SFMono-Regular", Menlo, monospace;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --paper: #10182b;
    --surface: #16203a;
    --surface-raised: #1b2745;
    --ink: #eee7d6;
    --ink-soft: #b7bfd4;
    --ink-faint: #7c8499;
    --line: #2a3557;
    --line-strong: #3a4670;
    --accent: #d9a544;
    --accent-strong: #eab75c;
    --accent-ink: #21160a;
    --accent-tint: #33280f;

    --status-live-bg: #113224;
    --status-live-fg: #57d999;
    --status-waiting-bg: #362b0d;
    --status-waiting-fg: #e6b93d;
    --status-reached-bg: #3a1a12;
    --status-reached-fg: #ef8b6e;
    --status-error-bg: #33241a;
    --status-error-fg: #e2a37c;
    --status-mock-bg: #241d3d;
    --status-mock-fg: #b3a3ec;

    --up: #57d999;
    --down: #ef8b6e;
    --shadow-card: 0 1px 2px rgba(0, 0, 0, 0.3), 0 12px 28px -14px rgba(0, 0, 0, 0.6);
  }
}

:root[data-theme="dark"] {
  --paper: #10182b;
  --surface: #16203a;
  --surface-raised: #1b2745;
  --ink: #eee7d6;
  --ink-soft: #b7bfd4;
  --ink-faint: #7c8499;
  --line: #2a3557;
  --line-strong: #3a4670;
  --accent: #d9a544;
  --accent-strong: #eab75c;
  --accent-ink: #21160a;
  --accent-tint: #33280f;

  --status-live-bg: #113224;
  --status-live-fg: #57d999;
  --status-waiting-bg: #362b0d;
  --status-waiting-fg: #e6b93d;
  --status-reached-bg: #3a1a12;
  --status-reached-fg: #ef8b6e;
  --status-error-bg: #33241a;
  --status-error-fg: #e2a37c;
  --status-mock-bg: #241d3d;
  --status-mock-fg: #b3a3ec;

  --up: #57d999;
  --down: #ef8b6e;
  --shadow-card: 0 1px 2px rgba(0, 0, 0, 0.3), 0 12px 28px -14px rgba(0, 0, 0, 0.6);
}

* { box-sizing: border-box; }
html { color-scheme: light dark; }
/* Smooth-scrolls the "✏️ Edit alert" mobile jump link (see .mobile-edit-link)
   into a gentle scroll instead of a hard jump cut. Gated behind the same
   prefers-reduced-motion check already used elsewhere in this file. */
@media (prefers-reduced-motion: no-preference) {
  html { scroll-behavior: smooth; }
}

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  /* Safety net, not the real fix (see .hint/code below for that) — this
     just guarantees a single missed long, unbroken string (a URL, a file
     path) can never push the whole page into horizontal scroll on a
     narrow phone, only get clipped at worst. */
  overflow-x: hidden;
}

@media (prefers-reduced-motion: no-preference) {
  .status-pill, .card, .btn-primary { transition: background-color .2s ease, color .2s ease, transform .15s ease, box-shadow .2s ease; }
}
@media (prefers-reduced-motion: reduce) {
  * { animation-duration: 0.001ms !important; animation-iteration-count: 1 !important; transition-duration: 0.001ms !important; }
}

a { color: var(--accent-strong); }

h1, h2, h3 { font-family: var(--font-display); font-weight: 600; margin: 0; text-wrap: balance; color: var(--ink); }

.tabular { font-variant-numeric: tabular-nums; }
.mono { font-family: var(--font-mono); }

/* ---------- Mock-data banner (must always be visible while data is simulated) ---------- */
.mock-banner {
  background: var(--status-mock-bg);
  color: var(--status-mock-fg);
  font-family: var(--font-mono);
  font-size: 0.8rem;
  letter-spacing: 0.02em;
  text-align: center;
  padding: 8px 16px;
  border-bottom: 1px solid var(--line);
}
.mock-banner strong { font-weight: 600; }

/* ---------- Top bar ---------- */
.topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 20px clamp(16px, 4vw, 40px);
  border-bottom: 1px solid var(--line);
  flex-wrap: wrap;
}
.brand {
  display: flex;
  align-items: baseline;
  gap: 10px;
  flex-shrink: 0;
}
.brand-mark {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.5rem;
  letter-spacing: -0.01em;
  white-space: nowrap;
}
@media (max-width: 480px) {
  .brand-mark { font-size: 1.15rem; }
  .brand-sub { display: none; }
  /* Claw back padding on small phones (iPhone SE / older Android widths)
     so content gets more of the ~360-390px of actual screen width instead
     of losing a chunk of it to the same margins used on a tablet. */
  .card, .form-panel { padding: 16px; }
  .test-controls { padding: 12px 14px; }
}
.brand-mark em { color: var(--accent-strong); font-style: normal; }
.brand-sub {
  font-size: 0.78rem;
  color: var(--ink-faint);
  text-transform: uppercase;
  letter-spacing: 0.08em;
}
.phase-chip {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  color: var(--ink-soft);
  border: 1px solid var(--line-strong);
  border-radius: 999px;
  padding: 4px 10px;
  white-space: nowrap;
}
/* Placed AFTER the base .phase-chip rule above, deliberately — an earlier
   version of this override lived inside the max-width:480px block up near
   .brand-mark, but a media query doesn't win a cascade tie on its own;
   with equal specificity, whichever rule appears LATER in the file wins
   regardless of which one is wrapped in a media query. Sitting below the
   unconditional rule is what actually lets this take effect on a narrow
   phone, where the full "Phase 10 · email & Telegram alerts, rate
   history" text doesn't fit on one nowrap line without overflowing. */
@media (max-width: 480px) {
  .phase-chip { white-space: normal; text-align: center; }
}

/* ---------- Layout ---------- */
.layout {
  max-width: 1180px;
  margin: 0 auto;
  padding: clamp(16px, 3vw, 32px) clamp(16px, 4vw, 40px) 64px;
  display: grid;
  /* minmax(0, 1fr), not a bare 1fr — a grid track's implicit min-width is
     "auto" (i.e. its content's own intrinsic width), so a bare 1fr still
     refuses to shrink the column below whatever its widest child wants to
     be. On a real phone that silently forced the whole right-hand column
     out to ~560px width inside a 390px viewport — every card looked fine
     in isolation but the page as a whole had a permanent horizontal
     scrollbar. minmax(0, 1fr) is what actually makes 1fr mean "fill the
     available width," full stop. */
  grid-template-columns: 340px minmax(0, 1fr);
  gap: 24px;
  align-items: start;
}
@media (max-width: 880px) {
  .layout { grid-template-columns: minmax(0, 1fr); }
  .form-panel { order: 2; }
  .main-col { order: 1; }
}

/* ---------- Form panel ---------- */
.form-panel {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  padding: 20px;
  position: sticky;
  top: 16px;
  box-shadow: var(--shadow-card);
  scroll-margin-top: 16px; /* breathing room when jumped to via .mobile-edit-link */
}
@media (max-width: 880px) { .form-panel { position: static; } }

/* Desktop-only "hidden until requested" mode (28-Aug-2026, requested):
   a signed-in returning desktop user gets My saved alerts as the first
   thing they see, with the Build Your Alert sidebar collapsed until
   they click "+ Build Your Alert" (auth.js adds/removes this class --
   see updateAuthUI() and wireForm() there for exactly when and why).
   Scoped to min-width so an accidentally-lingering class can never hide
   the form on a phone -- mobile's own reordering/stacking rules above
   are completely untouched by this. Never applied at all for a signed-
   out visitor (auth.js only ever adds it once signed in), since that is
   the only state with no "+ Build Your Alert" control to bring it back. */
@media (min-width: 881px) {
  .layout.form-panel-hidden { grid-template-columns: minmax(0, 1fr); }
  .layout.form-panel-hidden .form-panel { display: none; }
}

.form-panel h2 { font-size: 1.15rem; margin-bottom: 4px; }
.form-panel .lede { font-size: 0.85rem; color: var(--ink-soft); margin: 0 0 18px; }

.field { margin-bottom: 16px; }
.field label {
  display: block;
  font-size: 0.78rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--ink-soft);
  margin-bottom: 6px;
}
.field .hint {
  font-size: 0.78rem;
  color: var(--ink-faint);
  margin-top: 5px;
  line-height: 1.4;
}
/* Real fix for the overflow the body{overflow-x:hidden} rule above only
   papers over: hint text regularly contains file paths and URLs
   (frontend/supabaseConfig.js, SUPABASE_SETUP.md) with no spaces for the
   browser to wrap at, so on a narrow phone the text just kept running
   past the card's edge instead of wrapping to a new line. Applied to
   every .hint (not just inside .field) and to <code> specifically, since
   that's where the actual unbroken strings live. */
.hint, code { overflow-wrap: break-word; word-break: break-word; }
.field .hint.dynamic { color: var(--accent-strong); font-weight: 500; }

select, input[type="number"], input[type="text"], input[type="email"], input[type="password"] {
  width: 100%;
  font-family: var(--font-body);
  /* 16px, not 0.95rem (15.2px) — iOS Safari auto-zooms the page when a
     focused input's font-size is under 16px. That zoom-on-tap is a classic
     "this isn't really a mobile app" tell, so this is deliberately exact,
     not just a size preference. */
  font-size: 1rem;
  color: var(--ink);
  background: var(--surface-raised);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  padding: 9px 10px;
}
input[type="number"] { font-family: var(--font-mono); }
select:focus, input:focus {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
  border-color: var(--accent);
}

.segmented {
  display: flex;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  overflow: hidden;
}
.segmented button {
  flex: 1;
  border: none;
  background: var(--surface-raised);
  color: var(--ink-soft);
  font-family: var(--font-body);
  font-weight: 600;
  font-size: 0.85rem;
  padding: 9px 6px;
  cursor: pointer;
}
.segmented button + button { border-left: 1px solid var(--line-strong); }
.segmented button[aria-pressed="true"] { background: var(--accent); color: var(--accent-ink); }
.segmented button:focus-visible { outline: 2px solid var(--accent-strong); outline-offset: -2px; }

.checkbox-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 7px 0;
  font-size: 0.9rem;
}
.checkbox-row input { width: 16px; height: 16px; accent-color: var(--accent); }
.checkbox-row label { text-transform: none; font-weight: 500; letter-spacing: 0; color: var(--ink); font-size: 0.9rem; }

select:disabled, input:disabled, .checkbox-row.is-disabled { opacity: 0.45; }

.btn-primary {
  width: 100%;
  border: none;
  border-radius: var(--radius-sm);
  background: var(--accent);
  color: var(--accent-ink);
  font-family: var(--font-body);
  font-weight: 700;
  font-size: 0.95rem;
  letter-spacing: 0.02em;
  padding: 13px 16px;
  cursor: pointer;
  margin-top: 6px;
}
.btn-primary:hover { background: var(--accent-strong); }
.btn-primary:focus-visible { outline: 3px solid var(--accent-tint); outline-offset: 2px; }
.btn-primary[data-active="true"] { background: var(--status-live-fg); color: #fff; }

.btn-secondary {
  width: 100%;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--ink-soft);
  font-size: 0.85rem;
  font-weight: 600;
  padding: 9px 12px;
  cursor: pointer;
  margin-top: 8px;
}
.btn-secondary:hover { border-color: var(--accent); color: var(--accent-strong); }

.form-status {
  margin-top: 10px;
  font-size: 0.8rem;
  color: var(--ink-faint);
  text-align: center;
}

/* ---------- Main column ---------- */
.main-col { display: flex; flex-direction: column; gap: 20px; }

.card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  padding: 22px;
  box-shadow: var(--shadow-card);
}
/* Breathing room when scrollToAccountCard() (auth.js) jumps here after a
   save/update -- same pattern as .form-panel's own scroll-margin-top
   above, just on the one card that's actually a scroll target. */
#accountCard { scroll-margin-top: 16px; }
.card-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 8px 12px;
  margin-bottom: 6px;
}
/* Lets the title block actually shrink/wrap instead of forcing the card
   wider than the screen — flex items default to min-width:auto, which
   otherwise refuses to shrink below the text's natural width. */
.card-head > div:first-child { min-width: 0; }
.card-eyebrow {
  font-size: 0.74rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--ink-faint);
  font-weight: 600;
}
/* Phase 32 (26-Aug-2026) fix: requested — the Multi-source comparison
   card's headline ("Every selected money changer, side by side") should
   read in the same font as its own eyebrow label above it
   ("Multi-source comparison"), not the serif display font the global
   h1/h2/h3 rule applies everywhere else. Scoped to this one id (added on
   the <h3> in index.html) rather than changing the global h1,h2,h3 rule,
   which still governs every other heading on the page (form panel,
   account panel, chart, activity log). */
#compareHeading { font-family: var(--font-body); }

/* Phase 34 (26-Aug-2026) fix: requested — same treatment as
   #compareHeading/#heroRowsHeading above, this time for the History
   chart card's headline ("Selected Rate Over Time") and the Activity
   Log card's headline ("What The Monitor Has Done") — each should read
   in the same font as its own eyebrow label above it ("Rate History" /
   "Activity Log"), not the serif display font the global h1/h2/h3 rule
   applies everywhere else. Scoped to these two ids (added on the <h3>s
   in index.html) rather than changing the global h1,h2,h3 rule, which
   still governs every other heading on the page. */
#historyHeading,
#activityHeading { font-family: var(--font-body); }

/* Phase 36 (26-Aug-2026) fix: requested — same treatment again, this
   time for the Account card's headline ("Save Alerts To Your Account",
   should match its own eyebrow "Your account") and the form panel's
   headline ("Build Your Alert", should match the sans-serif font every
   field label — e.g. "Currency" — already uses via .field label's
   inherited font-body, since .form-panel h2 sets no font-family of its
   own and so falls through to the global h1/h2/h3 serif rule). Scoped to
   these two ids (added on the <h3>/<h2> in index.html) rather than
   changing the global h1,h2,h3 rule, which still governs every other
   heading on the page. */
#accountHeading,
#formHeading { font-family: var(--font-body); }

/* ---------- Hero rate card ---------- */
.hero-pair { font-family: var(--font-display); font-size: 1.3rem; font-weight: 600; }
/* Phase 32 (26-Aug-2026) fix: requested — "Your saved alerts" (shown here
   instead of the CNY/MYR pair once the user has saved alerts — see
   auth.js's renderHeroRows()) should read in the same font as its own
   eyebrow label above it ("Best available rate for your alert"), not the
   serif display font every other headline on this card uses. Scoped to
   this one id rather than changing .hero-pair itself, since that class is
   shared with #heroPair ("CNY / MYR"), which keeps the display font. */
#heroRowsHeading { font-family: var(--font-body); }
.hero-grid {
  display: grid;
  grid-template-columns: 1.3fr 1fr 1fr;
  gap: 20px;
  margin-top: 14px;
  align-items: end;
}
@media (max-width: 620px) { .hero-grid { grid-template-columns: 1fr; gap: 14px; } }

.hero-rate-label { font-size: 0.78rem; color: var(--ink-soft); text-transform: uppercase; letter-spacing: 0.06em; margin-bottom: 4px; }
.hero-rate-value { font-family: var(--font-mono); font-size: clamp(2.4rem, 7vw, 3.4rem); font-weight: 600; line-height: 1; }
.hero-rate-value .decimal { color: var(--ink-soft); }
.hero-sub { display: flex; flex-direction: column; gap: 10px; }
.hero-metric-label { font-size: 0.72rem; color: var(--ink-faint); text-transform: uppercase; letter-spacing: 0.06em; }
.hero-metric-value { font-family: var(--font-mono); font-size: 1.25rem; font-weight: 600; }
.hero-metric-value.diff-up { color: var(--up); }
.hero-metric-value.diff-down { color: var(--down); }

.status-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 0.82rem;
  font-weight: 600;
  padding: 5px 12px;
  border-radius: 999px;
}
.status-pill.live { background: var(--status-live-bg); color: var(--status-live-fg); }
.status-pill.waiting { background: var(--status-waiting-bg); color: var(--status-waiting-fg); }
.status-pill.reached { background: var(--status-reached-bg); color: var(--status-reached-fg); }
.status-pill.error { background: var(--status-error-bg); color: var(--status-error-fg); }
.status-pill.mock { background: var(--status-mock-bg); color: var(--status-mock-fg); }

.origin-tag {
  display: inline-block;
  margin-top: 4px;
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  padding: 2px 7px;
  border-radius: 999px;
  text-transform: uppercase;
}
.origin-tag.origin-live { background: var(--status-live-bg); color: var(--status-live-fg); }
.origin-tag.origin-sim { background: var(--status-mock-bg); color: var(--status-mock-fg); }

.hero-footer {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: center;
  gap: 8px 12px;
  margin-top: 18px;
  padding-top: 14px;
  border-top: 1px dashed var(--line-strong);
  font-size: 0.82rem;
  color: var(--ink-faint);
}
.hero-footer .last-checked { font-family: var(--font-mono); }

/* Phone-only shortcut to the "Build your alert" form. On mobile the form
   panel is reordered to appear BELOW this hero card (see the max-width:
   880px rule under "Layout"), which is the right priority — the live rate
   is what you came for — but it also means editing your alert now means
   scrolling down past the whole dashboard. This link jumps straight there.
   Hidden entirely above 880px, where the form is already right beside the
   hero card and a jump link would be redundant clutter. */
.mobile-edit-link {
  display: none;
  align-items: center;
  gap: 4px;
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--accent-strong);
  text-decoration: none;
  border: 1px solid var(--line-strong);
  border-radius: 999px;
  padding: 4px 12px;
}
.mobile-edit-link:hover { border-color: var(--accent); }
@media (max-width: 880px) {
  .mobile-edit-link { display: inline-flex; }
}

/* ---------- Comparison table ---------- */
.table-scroll { overflow-x: auto; }
table.compare {
  width: 100%;
  border-collapse: collapse;
  min-width: 480px;
  margin-top: 12px;
}
table.compare th, table.compare td {
  text-align: left;
  padding: 10px 12px;
  border-bottom: 1px solid var(--line);
  font-size: 0.88rem;
}
table.compare th {
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--ink-faint);
  font-weight: 600;
}
table.compare td.num { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
table.compare tr.is-best td { background: var(--accent-tint); }
table.compare tr.is-best td:first-child { border-left: 3px solid var(--accent); }
.best-badge {
  font-size: 0.68rem;
  font-weight: 700;
  color: var(--accent-strong);
  margin-left: 6px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

/* ---------- Saved-currency chips (Phase 18) ----------
   Quick-switch row sourced from the signed-in user's own saved alerts.
   Deliberately lighter/smaller than .chart-range so it reads as a
   secondary, data-driven shortcut rather than competing with the
   heavier BUY/SELL segmented control or the 1h/6h/24h/7d range picker.
   :empty collapses the row to nothing (no stray gap) when signed out or
   with zero saved alerts — see renderSavedCurrencyChips() in auth.js. */
.saved-currency-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 10px; }
.saved-currency-chips:empty { display: none; margin: 0; }
.chip-currency {
  border: 1px solid var(--line-strong);
  background: var(--surface-raised);
  color: var(--ink-soft);
  font-family: 'IBM Plex Mono', monospace;
  font-size: 0.76rem;
  font-weight: 700;
  letter-spacing: 0.03em;
  border-radius: 999px;
  padding: 5px 14px;
  cursor: pointer;
}
.chip-currency:hover { border-color: var(--accent); color: var(--accent-strong); }
.chip-currency:focus-visible { outline: 2px solid var(--accent-strong); outline-offset: 1px; }
.chip-currency[aria-pressed="true"] { background: var(--accent); color: var(--accent-ink); border-color: var(--accent); }

/* ---------- History chart ---------- */
.chart-wrap { margin-top: 12px; }
.chart-range { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 10px; }
.chart-range button {
  border: 1px solid var(--line-strong);
  background: var(--surface-raised);
  color: var(--ink-soft);
  font-size: 0.76rem;
  font-weight: 600;
  border-radius: 999px;
  padding: 4px 12px;
  cursor: pointer;
}
.chart-range button[aria-pressed="true"] { background: var(--ink); color: var(--paper); border-color: var(--ink); }
canvas#historyChart { width: 100%; height: 180px; display: block; }

/* ---------- Activity log ---------- */
.log-list { list-style: none; margin: 12px 0 0; padding: 0; display: flex; flex-direction: column; gap: 8px; max-height: 220px; overflow-y: auto; }
.log-list li {
  display: flex;
  gap: 10px;
  align-items: baseline;
  font-size: 0.82rem;
  padding: 6px 0;
  border-bottom: 1px solid var(--line);
}
.log-list li:last-child { border-bottom: none; }
.log-time { font-family: var(--font-mono); color: var(--ink-faint); flex-shrink: 0; }
.log-msg { color: var(--ink-soft); }
.log-list li.log-empty { display: block; color: var(--ink-faint); border-bottom: none; padding: 6px 0; }

/* ---------- Test controls (Phase 1 demo only) ---------- */
.test-controls {
  border: 1px dashed var(--line-strong);
  border-radius: var(--radius-md);
  padding: 14px 16px;
}
.test-controls .card-eyebrow { color: var(--status-mock-fg); }
.test-controls p { font-size: 0.82rem; color: var(--ink-soft); margin: 4px 0 10px; }
.test-controls .btn-row { display: flex; flex-wrap: wrap; gap: 8px; }
.test-controls .btn-row button {
  border: 1px solid var(--line-strong);
  background: var(--surface-raised);
  color: var(--ink);
  font-size: 0.8rem;
  font-weight: 600;
  border-radius: var(--radius-sm);
  padding: 7px 12px;
  cursor: pointer;
}
.test-controls .btn-row button:hover { border-color: var(--accent); color: var(--accent-strong); }

/* ---------- Account card (Phase 7 — Supabase, optional) ---------- */
.account-form .field:last-of-type { margin-bottom: 8px; }
.account-divider { border-top: 1px dashed var(--line-strong); margin: 16px 0; }

.auth-tabs {
  display: flex;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  overflow: hidden;
  margin-bottom: 14px;
}
.auth-tab {
  flex: 1;
  border: none;
  background: var(--surface-raised);
  color: var(--ink-soft);
  font-family: var(--font-body);
  font-weight: 600;
  font-size: 0.85rem;
  padding: 9px 6px;
  cursor: pointer;
}
.auth-tab + .auth-tab { border-left: 1px solid var(--line-strong); }
.auth-tab[aria-pressed="true"] { background: var(--accent); color: var(--accent-ink); }
.auth-tab:focus-visible { outline: 2px solid var(--accent-strong); outline-offset: -2px; }

#forgotPasswordLink { color: var(--accent-strong); font-size: 0.8rem; }
.account-subhead {
  font-family: var(--font-body);
  font-size: 0.78rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--ink-soft);
  margin: 18px 0 8px;
}
/* Phase (28-Aug-2026) — quick jump back to the alert form from "My saved
   alerts", requested after Android install testing: once someone has a
   few saved alerts, this list can sit well below the form, so a plain
   anchor link (no JS needed — jumps to #formHeading) saves the scroll. */
.account-subhead-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin: 18px 0 8px;
}
.account-subhead-row .account-subhead { margin: 0; }
.build-alert-shortcut {
  font-family: var(--font-body);
  font-weight: 600;
  font-size: 0.72rem;
  color: var(--accent-ink);
  background: var(--accent);
  border: none;
  border-radius: 999px;
  padding: 4px 10px;
  white-space: nowrap;
  text-decoration: none;
  flex-shrink: 0;
}
.build-alert-shortcut:hover { background: var(--accent-strong); }
.build-alert-shortcut:focus-visible { outline: 3px solid var(--accent-tint); outline-offset: 2px; }
.alert-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.alert-empty { font-size: 0.82rem; color: var(--ink-faint); padding: 6px 0; }
.alert-item {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: 8px 10px;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface-raised);
}
.alert-item-title { font-family: var(--font-mono); font-weight: 600; font-size: 0.92rem; overflow-wrap: break-word; }
.alert-item-sub { font-size: 0.76rem; color: var(--ink-faint); margin-top: 2px; }
/* Phase 16 (23-Aug-2026) — each saved alert's own live/simulated rate line,
   so a user with several alerts isn't limited to seeing only one of them
   live at a time (see window.CKM.computeAlertReading()'s comment in app.js
   and updateAlertLiveRates() in auth.js). */
.alert-item-live { font-family: var(--font-mono); font-size: 0.78rem; margin-top: 6px; color: var(--ink-soft); }
.alert-item-live-real { color: var(--status-live-fg); }
.alert-item-live-sim { color: var(--status-mock-fg); }
.alert-item-live-error { color: var(--down); }
.alert-item-side { display: flex; flex-direction: column; align-items: flex-end; gap: 6px; flex-shrink: 0; }
.alert-item-actions { display: flex; gap: 6px; }
.alert-action {
  border: 1px solid var(--line-strong);
  background: transparent;
  color: var(--ink-soft);
  font-size: 0.72rem;
  font-weight: 600;
  border-radius: var(--radius-sm);
  padding: 4px 8px;
  cursor: pointer;
}
.alert-action:hover { border-color: var(--accent); color: var(--accent-strong); }
.alert-action-danger:hover { border-color: var(--down); color: var(--down); }

/* Phase 13 — shown above "Save current alert" while an existing saved
   alert's settings are loaded into the form for editing (see auth.js's
   startEditingAlert()/stopEditingAlert() and app.js's loadAlertIntoForm()). */
.editing-banner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 10px;
  background: var(--accent-tint);
  color: var(--accent-ink);
  border: 1px solid var(--accent);
  border-radius: var(--radius-sm);
  padding: 8px 10px;
  font-size: 0.82rem;
  margin-bottom: 10px;
}
.editing-banner #cancelEditBtn { background: var(--surface-raised); margin-left: auto; }

/* ---------- Toast ---------- */
/* ============================================================
   Install App button + iOS "Add to Home Screen" banner
   (mobile-app phase, 28-Aug-2026) — reuses the existing pill/
   button tokens rather than inventing a new visual language.
   ============================================================ */
.install-btn {
  font-family: var(--font-body);
  font-weight: 600;
  font-size: 0.78rem;
  color: var(--accent-ink);
  background: var(--accent);
  border: none;
  border-radius: 999px;
  padding: 6px 14px;
  cursor: pointer;
  white-space: nowrap;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  text-decoration: none;
}
.install-btn:hover { background: var(--accent-strong); }
.install-btn:focus-visible { outline: 3px solid var(--accent-tint); outline-offset: 2px; }
.install-btn:disabled { opacity: 0.6; cursor: default; }

.install-banner {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  background: var(--accent-tint);
  color: var(--accent-ink);
  border-bottom: 1px solid var(--line-strong);
  font-family: var(--font-body);
  font-size: 0.85rem;
  padding: 10px 16px;
  text-align: center;
}
.install-banner-dismiss {
  border: none;
  background: transparent;
  color: var(--accent-ink);
  font-size: 1.2rem;
  line-height: 1;
  cursor: pointer;
  padding: 0 4px;
}
.install-banner-dismiss:hover { opacity: 0.7; }

@media (max-width: 480px) {
  .install-banner { font-size: 0.78rem; padding: 8px 12px; }
}

#toast {
  position: fixed;
  left: 50%;
  bottom: 22px;
  transform: translate(-50%, 12px);
  background: var(--ink);
  color: var(--paper);
  font-size: 0.88rem;
  padding: 12px 18px;
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-card);
  opacity: 0;
  pointer-events: none;
  transition: opacity .25s ease, transform .25s ease;
  max-width: min(420px, calc(100vw - 32px));
  z-index: 40;
}
#toast.show { opacity: 1; transform: translate(-50%, 0); }

footer.site-footer {
  text-align: center;
  font-size: 0.78rem;
  color: var(--ink-faint);
  padding: 24px 16px 40px;
}
footer.site-footer a { color: var(--ink-soft); }
