/* app.css — Claude Percent: mobile-first, single focused card.
   Base targets ~375px (iPhone SE / iPhone 14 portrait).
   Desktop breakpoint at 600px scales up type and spacing.
   Palette: near-black text on warm off-white; one accent hue (indigo).
   No external fonts; system font stack for zero network cost. */

/* -------------------------------------------------------------------------
   Custom properties
   ------------------------------------------------------------------------- */
/* Anthropic/Claude-inspired theming: warm neutrals + a coral-orange accent
   ("book cloth"). DARK is the default (tokens on :root below); LIGHT is an
   opt-in override under [data-theme="light"]. The theme is set on <html> by a
   tiny blocking script in the page <head> (no flash) and toggled at runtime by
   the corner button in app.js (persisted to localStorage). --radius is shared. */
:root {
  --radius: 1rem;

  /* ---- Dark (default) ---- */
  color-scheme: dark;
  --bg: #1f1e1d;            /* warm near-black */
  --surface: #2a2825;       /* card — a touch lighter than bg */
  --text-primary: #f2eee6;  /* warm ivory */
  --text-secondary: #b4afa3;
  --text-muted: #87827a;
  --accent: #d97757;        /* Anthropic coral-orange */
  --accent-hover: #e08a6e;
  --accent-light: #34271f;  /* dark warm tint (chart area / heatmap base) */
  --on-accent: #1f1410;     /* text/!icon on a solid accent fill */
  --danger: #e5675b;
  --danger-bg: #3a2422;
  --danger-border: #582f2b;
  --safe: #6cc285;
  --neutral-fill: #6e6a62;
  --border: #3a3733;
  --shadow: 0 1px 3px rgba(0, 0, 0, 0.45), 0 8px 24px rgba(0, 0, 0, 0.38);
}

[data-theme="light"] {
  /* ---- Light ---- */
  color-scheme: light;
  --bg: #f5f4ef;            /* Anthropic ivory */
  --surface: #ffffff;
  --text-primary: #1a1915;
  --text-secondary: #6b6962;
  --text-muted: #9b9890;
  --accent: #c2613f;        /* deeper coral for legibility on light */
  --accent-hover: #a94e30;
  --accent-light: #f6e7df;  /* soft peach (chart area / heatmap base) */
  --on-accent: #ffffff;
  --danger: #b91c1c;
  --danger-bg: #fef2f2;
  --danger-border: #fecaca;
  --safe: #15803d;
  --neutral-fill: #c4c0b6;
  --border: #e8e4dc;
  --shadow: 0 1px 3px rgba(0, 0, 0, 0.08), 0 4px 16px rgba(0, 0, 0, 0.06);
}

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

html {
  font-size: 16px;
  -webkit-text-size-adjust: 100%; /* prevent iOS font inflation */
}

body {
  background-color: var(--bg);
  color: var(--text-primary);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui,
               Helvetica, Arial, sans-serif;
  line-height: 1.5;
  min-height: 100dvh;
}

/* -------------------------------------------------------------------------
   Page layout — centers the card vertically on short screens too
   ------------------------------------------------------------------------- */
.page {
  display: flex;
  flex-direction: column;
  align-items: center;
  min-height: 100dvh;
  padding: 1.25rem 1rem 2rem;
  gap: 1rem;
}

/* -------------------------------------------------------------------------
   Site header (dashboard only)
   ------------------------------------------------------------------------- */
.site-header {
  width: 100%;
  max-width: 28rem;
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.site-name {
  font-size: 0.8125rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.logout-link {
  font-size: 0.8125rem;
  color: var(--text-secondary);
  text-decoration: none;
  padding: 0.25rem 0;
}

.logout-link:hover {
  color: var(--text-primary);
  text-decoration: underline;
}

/* -------------------------------------------------------------------------
   Phase 3: view-controls — wrapper for view-nav (toggle + range switcher)
   ------------------------------------------------------------------------- */
#view-controls {
  width: 100%;
  max-width: 28rem;
}

/* view-nav: stacks toggle + range switcher vertically on mobile,
   rows on wider screens */
.view-nav {
  display: flex;
  flex-direction: column;
  gap: 0.625rem;
  width: 100%;
}

/* ---- View toggle (Trend / Heatmap) ---- */
.view-toggle {
  display: flex;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 0.625rem;
  padding: 0.1875rem;
  gap: 0.1875rem;
}

.view-toggle-btn {
  flex: 1;
  padding: 0.5rem 0.75rem;
  font-size: 0.875rem;
  font-weight: 600;
  font-family: inherit;
  color: var(--text-secondary);
  background: transparent;
  border: none;
  border-radius: 0.4375rem;
  cursor: pointer;
  transition: background-color 0.15s ease, color 0.15s ease;
  -webkit-tap-highlight-color: transparent;
  outline: none;
}

.view-toggle-btn:focus-visible {
  box-shadow: 0 0 0 2px var(--accent);
}

.view-toggle-btn.is-active,
.view-toggle-btn[aria-pressed="true"] {
  background: var(--accent);
  color: var(--on-accent);
}

/* ---- Range switcher (24h / 7d / 30d) — segmented control ---- */
.range-switcher {
  display: flex;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 0.625rem;
  padding: 0.1875rem;
  gap: 0.1875rem;
}

.range-btn {
  flex: 1;
  padding: 0.375rem 0.5rem;
  font-size: 0.8125rem;
  font-weight: 600;
  font-family: inherit;
  color: var(--text-secondary);
  background: transparent;
  border: none;
  border-radius: 0.4375rem;
  cursor: pointer;
  transition: background-color 0.15s ease, color 0.15s ease;
  -webkit-tap-highlight-color: transparent;
  outline: none;
}

.range-btn:focus-visible {
  box-shadow: 0 0 0 2px var(--accent);
}

.range-btn.is-active,
.range-btn[aria-pressed="true"] {
  background: var(--accent-light);
  color: var(--accent);
}

/* ---- View containers ---- */
.view-container {
  width: 100%;
  max-width: 28rem;
}

/* ---- Trend chart: inline SVG area/line curve ---- */
.trend-chart {
  width: 100%;
  display: block;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  overflow: hidden;
}

/* SVG fills the card; fluid scaling via viewBox */
.trend-chart svg {
  width: 100%;
  height: auto;
  display: block;
}

/* Chart background rect — theme-aware so the plot matches the card surface
   in both light and dark (replaces a hard-coded white fill). */
.trend-bg {
  fill: var(--surface);
}

/* Area fill under the line — uses accent at low opacity */
.trend-area {
  fill: var(--accent-light);
  opacity: 0.7;
}

/* The line itself */
.trend-line {
  fill: none;
  stroke: var(--accent);
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}

/* Axis lines */
.trend-axis {
  stroke: var(--border);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}

/* Y-axis labels (0%, 50%, 100%) */
.trend-label {
  font-size: 9px;
  fill: var(--text-muted);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui,
               Helvetica, Arial, sans-serif;
}

/* X-axis time labels */
.trend-xlabel {
  font-size: 8px;
  fill: var(--text-muted);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui,
               Helvetica, Arial, sans-serif;
}

/* MARK-01: dotted vertical reset markers — ground-truth session-reset
   boundaries from session_reset_at. Drawn in red (--danger) on top of the
   trend line so they stay visible even behind a vertical 100→0 drop, and
   overshoot above the 100% line (see RESET_OVERSHOOT in app.js). */
.trend-reset-line {
  stroke: var(--danger);
  stroke-width: 1.5;
  stroke-dasharray: 1.5 3;
  opacity: 0.9;
  vector-effect: non-scaling-stroke;
}

/* One-line legend under the trend chart describing the reset marker. */
.trend-legend {
  padding: 0.5rem 0.75rem 0.625rem;
  font-size: 0.6875rem;
  color: var(--text-muted);
  border-top: 1px solid var(--border);
}

/* ---- Heatmap: GitHub-style hour x day intensity grid (DASH-04) ---- */

/* Outer card — same card treatment as trend chart for cohesion */
.heatmap-card {
  width: 100%;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 1rem;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

/* Section heading */
.heatmap-title {
  font-size: 0.8125rem;
  font-weight: 600;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: var(--text-muted);
}

/* Scroll wrapper — lets the grid overflow horizontally on narrow screens
   without breaking the page layout */
.heatmap-scroll {
  width: 100%;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}

/* Grid: CSS grid with 25 equal-width columns (day-label + 24 hours).
   min() keeps cells from shrinking too small on very small viewports. */
.heatmap-grid {
  display: grid;
  grid-template-columns: 2rem repeat(24, minmax(0.875rem, 1fr));
  gap: 2px;
  min-width: 18rem; /* ensures cells stay readable; triggers scroll below */
  width: 100%;
}

/* Shared cell base */
.heatmap-cell {
  aspect-ratio: 1 / 1;
  border-radius: 2px;
  background: var(--border);
}

/* Header and label cells are not square */
.heatmap-hour-label,
.heatmap-day-label,
.heatmap-corner {
  aspect-ratio: unset;
  background: transparent;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.625rem;
  color: var(--text-muted);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui,
               Helvetica, Arial, sans-serif;
  white-space: nowrap;
}

.heatmap-day-label {
  justify-content: flex-end;
  padding-right: 0.25rem;
}

/* Header row — short fixed height */
.heatmap-header-row .heatmap-cell {
  aspect-ratio: unset;
  height: 1.25rem;
}

/* Data rows */
.heatmap-row {
  display: contents; /* each row's children slot into the parent grid */
}

/* --- Empty cell: no sample for that hour/day bucket ---
   Must be VISUALLY DISTINCT from a 0% cell (DASH-04 requirement).
   Uses a dashed border and the muted background so it reads as
   "absent" rather than "present but zero". */
.cell-empty {
  background: transparent;
  border: 1px dashed var(--border);
}

/* --- Idle (confirmed 0%) ---
   A sample exists for this hour but usage was 0% — the user was idle, not
   absent. Transparent fill with a faint SOLID border keeps it clearly "not
   usage" (matching the trend's floor line) while staying distinct from the
   DASHED no-data cell above. The first colored fill is cell-i1 (1-25%). */
.cell-idle {
  background: transparent;
  border: 1px solid var(--border);
}

/* --- Intensity steps (1-4): accent with stepped opacity ---
   cell-i1 = 1-25% usage ... cell-i4 = 76-100% usage */
.cell-i1 {
  background: color-mix(in srgb, var(--accent) 30%, var(--accent-light));
}

.cell-i2 {
  background: color-mix(in srgb, var(--accent) 55%, var(--accent-light));
}

.cell-i3 {
  background: color-mix(in srgb, var(--accent) 78%, var(--accent-light));
}

.cell-i4 {
  background: var(--accent);
}

/* Fallback for browsers without color-mix (pre-2023 Safari / Firefox):
   use hard-coded indigo tints that match the accent palette */
@supports not (color: color-mix(in srgb, red 50%, blue)) {
  .cell-i1 { background: #efc5ad; }
  .cell-i2 { background: #e6a283; }
  .cell-i3 { background: #df8a64; }
  .cell-i4 { background: var(--accent); }
}

/* --- MARK-02: ground-truth reset marker on an hour-cell ---
   A small red (--danger) dot floated just above the cell via ::after, matching
   the red reset markers on the trend chart. The dot sits ABOVE the square so it
   never obscures the cell's own intensity fill. position:relative anchors it. */
.cell-reset {
  position: relative;
  overflow: visible;
}

/* Red dot floated just ABOVE the square (not over it) so the cell's own fill
   — purple intensity, or white for an unused hour — stays fully visible. */
.cell-reset::after {
  content: "";
  position: absolute;
  bottom: calc(100% + 1px);
  left: 50%;
  width: 0.25rem;
  height: 0.25rem;
  margin-left: -0.125rem;
  border-radius: 50%;
  background: var(--danger);
}

/* --- Legend --- */
.heatmap-legend {
  display: flex;
  align-items: center;
  gap: 0.25rem;
  justify-content: flex-end;
}

.heatmap-legend-label {
  font-size: 0.625rem;
  color: var(--text-muted);
  padding: 0 0.125rem;
}

/* Legend swatches reuse the cell classes; override aspect-ratio for
   fixed square size */
.heatmap-legend-swatch {
  width: 0.875rem;
  height: 0.875rem;
  aspect-ratio: 1 / 1;
  border-radius: 2px;
  flex-shrink: 0;
}

/* Desktop: slightly larger cells via the auto-fitting grid */
@media (min-width: 600px) {
  .heatmap-card {
    padding: 1.25rem;
  }

  .heatmap-grid {
    grid-template-columns: 2.5rem repeat(24, 1fr);
    gap: 3px;
  }
}

/* ---- Sparse / no-data message ---- */
.view-empty-msg {
  width: 100%;
  max-width: 28rem;
  text-align: center;
  padding: 2.5rem 1rem;
  color: var(--text-secondary);
  font-size: 0.9375rem;
  line-height: 1.6;
}

/* ---- Desktop breakpoint additions for view controls ---- */
@media (min-width: 600px) {
  .view-nav {
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
  }

  .view-toggle {
    flex: 0 0 auto;
  }

  .range-switcher {
    flex: 0 0 auto;
  }

  .view-toggle-btn {
    padding: 0.5rem 1.125rem;
  }

  .range-btn {
    padding: 0.375rem 0.75rem;
  }
}

/* -------------------------------------------------------------------------
   Card — the single focused surface
   ------------------------------------------------------------------------- */
.card {
  width: 100%;
  max-width: 28rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 2rem 1.75rem;
}

/* -------------------------------------------------------------------------
   Hero percentage — the biggest element on the page
   ------------------------------------------------------------------------- */
.usage-block {
  text-align: center;
  padding-bottom: 1rem;
}

.hero-pct {
  font-size: clamp(3rem, 15vw, 4.5rem);
  font-weight: 700;
  line-height: 1;
  letter-spacing: -0.03em;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
}

/* -------------------------------------------------------------------------
   Pace bars — usage fill vs. time-elapsed fill in the 5h session window.
   Grouped with the hero number (above the meta-row). The usage bar carries
   the semantic colour (green = ahead of the clock, indigo = on pace, red =
   burning fast); the time bar is a neutral grey reference. A thin tick on the
   usage track marks the clock position, so the gap to the usage fill = runway.
   ------------------------------------------------------------------------- */
.pace {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  padding-bottom: 1.5rem;
}

.pace-row {
  display: flex;
  align-items: center;
  gap: 0.625rem;
}

.pace-key {
  width: 2.75rem;
  flex-shrink: 0;
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.pace-track {
  position: relative;
  flex: 1;
  height: 0.625rem;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 999px;
  overflow: hidden;
}

.pace-fill {
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 0; /* JS sets the real width → animates the grow-in + live creep */
  border-radius: 999px;
  transition: width 0.7s cubic-bezier(0.4, 0, 0.2, 1),
              background-color 0.3s ease;
}

.pace-fill--time {
  background: var(--neutral-fill);
}

/* Usage fill colour is driven by the pace state class on #pace */
.pace-fill--usage { background: var(--accent); }
.pace.is-ahead  .pace-fill--usage { background: var(--safe); }
.pace.is-behind .pace-fill--usage { background: var(--danger); }

/* Clock-position tick on the usage track (left set by JS to the time %) */
.pace-tick {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 2px;
  margin-left: -1px;
  background: var(--text-primary);
  opacity: 0.5;
  transition: left 0.7s cubic-bezier(0.4, 0, 0.2, 1);
}

.pace-num {
  width: 2.75rem;
  flex-shrink: 0;
  text-align: right;
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--text-primary);
  font-variant-numeric: tabular-nums;
}

.pace-verdict {
  margin-top: 0.125rem;
  text-align: center;
  font-size: 0.8125rem;
  font-weight: 600;
  letter-spacing: 0.01em;
  color: var(--text-secondary);
}

.pace.is-ahead  .pace-verdict { color: var(--safe); }
.pace.is-behind .pace-verdict { color: var(--danger); }
.pace.is-onpace .pace-verdict { color: var(--accent); }

.usage-label {
  margin-top: 0.375rem;
  font-size: 0.875rem;
  color: var(--text-secondary);
  letter-spacing: 0.01em;
}

/* -------------------------------------------------------------------------
   Meta row (Plan tier + Reset countdown)
   ------------------------------------------------------------------------- */
.meta-row {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0;
  border-top: 1px solid var(--border);
  padding-top: 1.5rem;
}

.meta-item {
  flex: 1;
  text-align: center;
}

.meta-divider {
  width: 1px;
  height: 2.5rem;
  background: var(--border);
  flex-shrink: 0;
}

.meta-label {
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: 0.25rem;
}

.meta-value {
  font-size: 1rem;
  font-weight: 600;
  color: var(--text-primary);
}

/* TZ-01: the reset line renders as two deliberate lines — countdown on top,
   absolute local clock time below — via a "\n" in the text and `pre-line`
   here, so it always breaks between the two rather than wrapping at an
   arbitrary width. The single "—" / raw-fallback string stays one line. */
#reset-countdown {
  font-size: 0.9375rem;
  line-height: 1.3;
  color: var(--text-secondary);
  white-space: pre-line;
}

/* -------------------------------------------------------------------------
   Empty state card
   ------------------------------------------------------------------------- */
.empty-card {
  text-align: center;
  padding: 3rem 2rem;
}

.empty-icon {
  font-size: 2rem;
  color: var(--text-muted);
  margin-bottom: 1rem;
}

.empty-text {
  font-size: 0.9375rem;
  color: var(--text-secondary);
  line-height: 1.6;
}

/* -------------------------------------------------------------------------
   Login page layout variant
   ------------------------------------------------------------------------- */
.page--login {
  justify-content: center;
}

.login-card {
  padding: 2.25rem 2rem;
}

.login-header {
  text-align: center;
  margin-bottom: 1.75rem;
}

.login-title {
  font-size: 1.375rem;
  font-weight: 700;
  color: var(--text-primary);
  letter-spacing: -0.01em;
}

.login-subtitle {
  margin-top: 0.375rem;
  font-size: 0.875rem;
  color: var(--text-secondary);
}

.login-error {
  background: var(--danger-bg);
  color: var(--danger);
  border: 1px solid var(--danger-border);
  border-radius: 0.5rem;
  padding: 0.625rem 0.875rem;
  font-size: 0.875rem;
  margin-bottom: 1.25rem;
}

.login-form {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.login-label {
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--text-secondary);
  letter-spacing: 0.01em;
}

.login-input {
  width: 100%;
  padding: 0.625rem 0.875rem;
  font-size: 1rem;
  font-family: inherit;
  color: var(--text-primary);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 0.5rem;
  outline: none;
  transition: border-color 0.15s ease;
  -webkit-appearance: none;
}

.login-input:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-light);
}

.login-btn {
  width: 100%;
  padding: 0.6875rem 1rem;
  font-size: 0.9375rem;
  font-weight: 600;
  font-family: inherit;
  color: var(--on-accent);
  background: var(--accent);
  border: none;
  border-radius: 0.5rem;
  cursor: pointer;
  transition: background-color 0.15s ease, transform 0.1s ease;
  margin-top: 0.25rem;
}

.login-btn:hover {
  background: #4338ca;
}

.login-btn:active {
  transform: scale(0.98);
}

/* -------------------------------------------------------------------------
   Desktop breakpoint — scales up type and breathing room
   ------------------------------------------------------------------------- */
@media (min-width: 600px) {
  .page {
    padding: 2.5rem 1.5rem 3rem;
    gap: 1.25rem;
  }

  .hero-pct {
    font-size: clamp(3.75rem, 9vw, 5.5rem);
  }

  .card {
    padding: 2.5rem 2.25rem;
  }

  .meta-label {
    font-size: 0.75rem;
  }

  .meta-value {
    font-size: 1.0625rem;
  }

  .login-card {
    padding: 2.75rem 2.5rem;
  }

  .login-title {
    font-size: 1.5rem;
  }
}

/* -------------------------------------------------------------------------
   Live refresh — header force-refresh button, mobile pull-to-refresh, and a
   "freshness" pulse on the hero. Auto-polling keeps the data current silently;
   these give an explicit, animated way to force an update on demand.
   ------------------------------------------------------------------------- */

/* Header right-side cluster: refresh button + sign-out */
.header-actions {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

/* ---- Round icon buttons (theme toggle + force-refresh), shared base ---- */
.refresh-btn,
.theme-toggle {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.125rem;
  height: 2.125rem;
  padding: 0;
  color: var(--text-secondary);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 50%;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: color 0.15s ease, border-color 0.15s ease, transform 0.1s ease;
}

.refresh-btn:hover,
.theme-toggle:hover  { color: var(--accent); border-color: var(--accent); }
.refresh-btn:active,
.theme-toggle:active { transform: scale(0.92); }
.refresh-btn:focus-visible,
.theme-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ---- Theme toggle icon swap (sun in dark, moon in light) ---- */
.theme-icon {
  width: 1.0625rem;
  height: 1.0625rem;
  display: block;
}
.theme-icon-moon { display: none; }
[data-theme="light"] .theme-icon-sun  { display: none; }
[data-theme="light"] .theme-icon-moon { display: block; }

.refresh-icon {
  width: 1.0625rem;
  height: 1.0625rem;
  display: block;
  transform-origin: 50% 50%;
}

/* In flight: tint accent + spin the glyph */
.refresh-btn.is-spinning { color: var(--accent); }
.refresh-btn.is-spinning .refresh-icon { animation: cp-spin 0.7s linear infinite; }

/* Completed: brief green state + an expanding success ring */
.refresh-btn.is-done { color: var(--safe); border-color: var(--safe); }
.refresh-ring {
  position: absolute;
  inset: -1px;
  border-radius: 50%;
  border: 2px solid var(--accent);
  opacity: 0;
  pointer-events: none;
}
.refresh-btn.is-done .refresh-ring { animation: cp-ring 0.6s ease-out; }

@keyframes cp-spin { to { transform: rotate(360deg); } }
@keyframes cp-ring {
  0%   { opacity: 0.85; transform: scale(0.8); }
  100% { opacity: 0;    transform: scale(1.9); }
}

/* ---- Mobile pull-to-refresh indicator ---- */
.ptr {
  position: fixed;
  top: 0;
  left: 50%;
  z-index: 50;
  display: flex;
  align-items: center;
  justify-content: center;
  /* Parked above the viewport; app.js translates it down with the pull. */
  transform: translate(-50%, -44px);
  opacity: 0;
  pointer-events: none;
  will-change: transform, opacity;
}
/* During raw dragging the transform is set inline with NO transition (1:1
   finger tracking); .is-animating adds an eased transition for the snap-back
   and the settle to the loading position. */
.ptr.is-animating {
  transition: transform 0.28s cubic-bezier(0.22, 1, 0.36, 1), opacity 0.28s ease;
}

.ptr-circle {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 2.25rem;
  height: 2.25rem;
  color: var(--accent);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 50%;
  box-shadow: var(--shadow);
  transition: transform 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}

.ptr-icon {
  width: 1.125rem;
  height: 1.125rem;
  transform-origin: 50% 50%;
}

/* Armed (pulled past the trigger): cue that releasing will refresh */
.ptr.is-armed .ptr-circle { transform: scale(1.08); border-color: var(--accent); }

/* Refreshing: pinned + spinning */
.ptr.is-refreshing .ptr-icon { animation: cp-spin 0.7s linear infinite; }

/* Done: brief green confirmation before it retracts */
.ptr.is-done .ptr-circle { color: var(--safe); border-color: var(--safe); }

/* ---- Freshness pulse on the hero card after a forced refresh, so the update
   is perceptible even when the numbers are unchanged. ---- */
.card.just-refreshed { animation: cp-fresh 0.7s ease-out; }
@keyframes cp-fresh {
  0%   { box-shadow: 0 0 0 0   color-mix(in srgb, var(--accent) 0%,  transparent), var(--shadow); }
  30%  { box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 40%, transparent), var(--shadow); }
  100% { box-shadow: 0 0 0 0   color-mix(in srgb, var(--accent) 0%,  transparent), var(--shadow); }
}

/* Accessibility: honour reduced-motion. No spins/pulses/sweeps — busy/done
   state is still conveyed by colour (accent while busy, green on success). */
@media (prefers-reduced-motion: reduce) {
  .refresh-btn.is-spinning .refresh-icon,
  .ptr.is-refreshing .ptr-icon { animation: none; }
  .refresh-btn.is-done .refresh-ring,
  .card.just-refreshed { animation: none; }
}
