﻿
/* ── Design Tokens ─────────────────────────────────────────────── */
:root {
  /* Approved workspace tokens; opting in does not repaint existing pages. */
  --workspace-ink: #121b21;
  --workspace-muted: #4c5960;
  --workspace-paper: #eee8d8;
  --workspace-white: #f8f4e7;
  --workspace-field: #cbded7;
  --workspace-lilac: #c7c3ec;
  --workspace-remaining: #f8dcc6;
  --workspace-received: #e6eee6;
  --workspace-action: #e9e6ef;
  /* Selection controls also appear on public account pages without alpha-app. */
  --workspace-focus: #3e6ded;
  --workspace-coral: #ff9a83;
/* color-scheme tells the browser this UI is already dark. What it verifiably
   buys: native selects, date pickers, scrollbars and autofill backgrounds
   render dark instead of light against a dark page.

   It was originally added believing it explained a reported bug where pages
   went much darker until a refresh. That explanation did NOT hold up — force
   dark inverts lightness, so on a #0d0c0e page it would lighten, not darken,
   and Blink skips pages that are already dark. The line is worth keeping on
   its own merits; the cause of that bug is still open.

   ⚠️ NEVER PREPEND TO THIS FILE. It used to carry a UTF-8 BOM, and inserting
   text above it moved the BOM into the body, where U+FEFF is a valid CSS ident
   character — it fused with the next selector and silently killed that whole
   rule. On landing.css that was the entire design-token :root block. The BOMs
   have been removed; keep these files BOM-free. */
  color-scheme: dark;

  /* ── The ramp ────────────────────────────────────────────────────
     Eight steps from page-black to text-white. The old four-value
     palette (bg / surface / surface-2 / border) had no mid-tones, which
     is most of why the UI read as "bare bones" — every panel sat at the
     same depth. Legacy names are aliased at the bottom of this block so
     the 2,000 lines of CSS below keep working untouched. */
  --ink:      #0d0c0e;   /* page */
  --panel:    #131216;   /* cards */
  --panel-2:  #191720;   /* raised / notes inside cards */
  --line:     #232128;   /* hairlines */
  --line-2:   #2f2d36;   /* input borders, stronger dividers */
  --dim:      #6b6878;   /* de-emphasised numerals, disabled */
  --mute:     #8d8a9c;   /* secondary text */
  --text:     #f1f0f4;   /* primary text */

  /* ── Signal colours ──────────────────────────────────────────────
     Each has four levels so a state can be a background wash, a border,
     a text colour, or a solid fill without anyone inventing new hexes.
     wash = fill behind a block. line = its border. text = readable
     colour on dark. base = solid fills and buttons. */
  --g-wash: rgba(0, 200, 5, .065);
  --g-line: rgba(0, 200, 5, .26);
  --g-text: #5ce265;
  --g-base: #00C805;

  --a-wash: rgba(232, 163, 61, .075);
  --a-line: rgba(232, 163, 61, .30);
  --a-text: #f0b661;
  --a-base: #e8a33d;

  --r-wash: rgba(255, 68, 85, .07);
  --r-line: rgba(255, 68, 85, .28);
  --r-text: #ff8b96;
  --r-base: #ff4455;

  /* Category tints for charts and avatars. Deliberately desaturated so
     they never compete with the signal colours above — a category is
     not a state. */
  --cat-1: #7a72c4;
  --cat-2: #5c8fa8;
  --cat-3: #b07f9a;
  --cat-4: #7f9a6b;
  --cat-5: #a8865c;

  /* ── Type roles ──────────────────────────────────────────────────
     Three faces doing three jobs. One typeface doing all three jobs was
     the other half of why this looked like a draft.
       display — page titles, card headers, badges. Used sparingly.
       ui      — everything else.
       mono    — EVERY dollar amount, always, with tabular-nums so
                 columns of figures line up. */
  --font-display: 'Archivo', 'Inter', system-ui, sans-serif;
  --font-ui:      'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --font-mono:    'IBM Plex Mono', ui-monospace, SFMono-Regular, Menlo, monospace;

  /* ── Layout ──────────────────────────────────────────────────── */
  --sidebar-w: 220px;
  --rail-w:    290px;

  /* ── Legacy aliases ──────────────────────────────────────────────
     THE TRAP: do not delete these. Roughly 2,000 lines of rules below
     reference them by name. They now point at the ramp above, which is
     how the whole app picks up the new palette without those rules
     being edited. Shifts are 1-3 hex points and imperceptible. */
  --bg:           var(--ink);
  --surface:      var(--panel);
  --surface-2:    var(--panel-2);
  --border:       var(--line);
  --border-2:     var(--line-2);
  --accent:       var(--g-base);
  --accent-hover: #00a804;
  --green:        var(--g-base);
  --red:          var(--r-base);
  --text-2:       var(--mute);
  --glow:         var(--g-wash);
  --shadow-sm:    0 2px 8px rgba(0,0,0,0.4);
  --shadow-md:    0 4px 24px rgba(0,0,0,0.5), 0 1px 3px rgba(0,0,0,0.6);
  --shadow-lg:    0 8px 40px rgba(0,0,0,0.6), 0 2px 8px rgba(0,0,0,0.5);

  /* ── Invoice status badge colors ── */
  --status-draft-bg:      #1a1a1a;
  --status-draft-text:    #666666;
  --status-draft-border:  #333333;
  --status-sent-bg:       #0d1f3c;
  --status-sent-text:     #4d9eff;
  --status-sent-border:   #1a3a6a;
  --status-paid-bg:       #0a1f0a;
  --status-paid-text:     #00C805;
  --status-paid-border:   #1a4a1a;
  --status-overdue-bg:    #2a0a0a;
  --status-overdue-text:  #ff4444;
  --status-overdue-border:#5a1a1a;

  /* ── Category / avatar palette ── */
  --palette-purple: #7C3AED;
  --palette-blue:   #2563EB;
  --palette-teal:   #0891B2;
  --palette-green:  #059669;
  --palette-amber:  #D97706;
  --palette-red:    #DC2626;
  --palette-pink:   #DB2777;
  --palette-gray:   #6B7280;
  --palette-lime:   #65A30D;

  /* ── Feature / highlight colors ── */
  --color-blue:   #4d9eff;
  --color-purple: #a855f7;

  /* ── Chart / tooltip chrome ── */
  --chart-tooltip-bg:     #13131f;
  --chart-tooltip-border: #2a2a3a;

}

/* Rate Card banner photo position, in steps of 10 (PublicRateCard.banner_focus_*).
   Classes, not inline styles: the private preview forbids style attributes. */
.pc-fx-0 { --fx: 0%; } .pc-fy-0 { --fy: 0%; }
.pc-fx-10 { --fx: 10%; } .pc-fy-10 { --fy: 10%; }
.pc-fx-20 { --fx: 20%; } .pc-fy-20 { --fy: 20%; }
.pc-fx-30 { --fx: 30%; } .pc-fy-30 { --fy: 30%; }
.pc-fx-40 { --fx: 40%; } .pc-fy-40 { --fy: 40%; }
.pc-fx-50 { --fx: 50%; } .pc-fy-50 { --fy: 50%; }
.pc-fx-60 { --fx: 60%; } .pc-fy-60 { --fy: 60%; }
.pc-fx-70 { --fx: 70%; } .pc-fy-70 { --fy: 70%; }
.pc-fx-80 { --fx: 80%; } .pc-fy-80 { --fy: 80%; }
.pc-fx-90 { --fx: 90%; } .pc-fy-90 { --fy: 90%; }
.pc-fx-100 { --fx: 100%; } .pc-fy-100 { --fy: 100%; }

.billing-access-notice { position: fixed; z-index: 1200; bottom: 16px; left: max(16px, var(--sidebar-w)); right: 16px; padding: 16px; background: var(--workspace-paper); color: var(--workspace-ink); border: 1px solid var(--workspace-ink); box-shadow: 3px 3px 0 var(--workspace-muted); font-size: 14px; }
.billing-access-notice a { color: inherit; display: inline-block; text-decoration: underline; margin-left: 12px; padding: 6px; }
@media (max-width: 700px) { .billing-access-notice { left: 16px; } }

/* ── Reset ─────────────────────────────────────────────────────── */
* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

body {
  font-family: var(--font-ui);
  background: var(--bg);
  /* Two layered radial gradients — the top one is the brand-tied green
     signature (same on every page so the whole app feels like one
     universe), the bottom-right one is a faint cool ambient wash that
     prevents the dark area from reading as a flat void. Opacities kept
     low (0.06-0.08) so the page is "alive" but not gaudy. */
  background-image:
    radial-gradient(ellipse 90% 55% at 50% -8%,  rgba(0, 200, 5, 0.08)  0%, transparent 62%),
    radial-gradient(ellipse 70% 50% at 95% 105%, rgba(89, 140, 255, 0.05) 0%, transparent 60%);
  background-attachment: fixed;
  color: var(--text);
  min-height: 100vh;
  padding-left: var(--sidebar-w);
}

/* App theme belongs in the FIRST stylesheet. Waiting for workspace.css lets
   the legacy black body/sidebar paint while later assets are still loading.
   Page layout and navigation geometry remain in workspace.css. */
body.alpha-app {
  --workspace-blue: #a9c4ff;
  --workspace-focus: #3e6ded;
  --workspace-coral: #ff9a83;
  --workspace-rule: rgba(18, 27, 33, .24);
  --sidebar-w: 210px;
  --bg: var(--workspace-field);
  --surface: var(--workspace-white);
  --surface-2: var(--workspace-paper);
  --panel: var(--workspace-white);
  --panel-2: var(--workspace-paper);
  --text: var(--workspace-ink);
  --mute: var(--workspace-muted);
  --dim: var(--workspace-muted);
  --border: var(--workspace-rule);
  --border-2: var(--workspace-ink);
  --line: var(--workspace-rule);
  --line-2: var(--workspace-ink);
  --accent: var(--workspace-ink);
  --accent-hover: var(--workspace-muted);
  --g-base: var(--workspace-ink);
  --g-text: var(--workspace-ink);
  --g-wash: var(--workspace-blue);
  --g-line: var(--workspace-rule);
  --a-text: var(--workspace-ink);
  --r-text: #942c17;
  --r-base: #942c17;
  color-scheme: light;
  color: var(--workspace-ink);
  background: var(--foundation-canvas,var(--workspace-field));
  font-family: var(--font-ui);
  font-size: 14px;
  line-height: 1.55;
  transition: padding-left 220ms cubic-bezier(.4,0,.2,1);
}

/* Public booking and receipt pages share the light foundation even when the
   private Rate Card stylesheet is absent. Rebind aliases here: inherited
   aliases have already resolved against the old dark root palette. */
body.rate-public {
  --bg: var(--workspace-field);
  --surface: var(--workspace-white);
  --surface-2: var(--workspace-paper);
  --panel: var(--workspace-white);
  --panel-2: var(--workspace-paper);
  --text: var(--workspace-ink);
  --text-2: var(--workspace-muted);
  --text-3: var(--workspace-muted);
  --mute: var(--workspace-muted);
  --dim: var(--workspace-muted);
  --border: rgba(18, 27, 33, .24);
  --border-2: var(--workspace-ink);
  --accent: var(--workspace-ink);
  --accent-hover: var(--workspace-muted);
  --workspace-focus: #3e6ded;
  color-scheme: light;
  color: var(--workspace-ink);
  background: var(--workspace-field);
  font-family: var(--font-ui);
  margin: 0;
  padding: 0;
}
body.rate-public::before, body.rate-public::after { display: none; }
body.rate-public [hidden] { display: none !important; }

/* ── Sidebar ───────────────────────────────────────────────────── */
.sidebar {
  position: fixed;
  left: 0;
  top: 0;
  bottom: 0;
  width: var(--sidebar-w);
  background: var(--surface);
  border-right: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  z-index: 100;
  overflow-y: auto;
  box-shadow: var(--shadow-md);
}

.sidebar-brand {
  font-size: 13px;
  font-weight: 800;
  letter-spacing: 6px;
  background: linear-gradient(135deg, #ffffff 0%, #a0ffa3 70%, #00C805 100%);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  background-clip: text;
  text-decoration: none;
  padding: 28px 20px 24px;
  border-bottom: 1px solid var(--border);
  display: block;
  flex-shrink: 0;
}

.sidebar-nav {
  flex: 1;
  padding: 10px 10px;
}

.sidebar-group {
  margin-bottom: 4px;
}

.sidebar-group-label {
  display: block;
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: #50506a;
  padding: 12px 12px 4px;
}

/* Category buttons inherit the existing heading metrics; only a small disclosure
   indicator and keyboard focus ring are added. No new group animation. */
.sidebar-group-toggle {
  appearance: none;
  width: 100%;
  border: 0;
  border-radius: 0;
  background: transparent;
  font-family: inherit;
  text-align: left;
  cursor: pointer;
  position: relative;
}
.sidebar-group-toggle::after {
  content: '';
  position: absolute;
  right: 12px;
  top: 50%;
  width: 4px;
  height: 4px;
  border-right: 1px solid currentColor;
  border-bottom: 1px solid currentColor;
  transform: translateY(-75%) rotate(45deg);
}
.sidebar-group-toggle[aria-expanded="false"]::after {
  transform: translateY(-50%) rotate(-45deg);
}
.sidebar-group-toggle:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: -2px;
}

.sidebar-link {
  display: flex;
  align-items: center;
  gap: 9px;
  text-decoration: none;
  color: var(--text-2);
  font-size: 13.5px;
  padding: 8px 12px;
  border-radius: 8px;
  transition: color 0.15s, background 0.15s;
  margin-bottom: 1px;
}

.sidebar-link i,
.sidebar-link svg {
  width: 15px;
  height: 15px;
  flex-shrink: 0;
  opacity: 0.7;
  transition: opacity 0.15s;
}

.sidebar-link:hover i,
.sidebar-link:hover svg,
.sidebar-link.active i,
.sidebar-link.active svg {
  opacity: 1;
}

.sidebar-link:hover {
  color: var(--text);
  background: var(--surface-2);
}

/* ── ROADMAP ITEMS IN THE SIDEBAR ────────────────────────────────────────
   Tools that do not exist yet, listed so the plan is visible daily rather
   than living in a doc nobody opens. Rendered as <span> by nav.js, so the
   only job here is to make them read as "coming" rather than "broken".

   Dimmed and cursor:default, NOT pointer — the single most common way a
   placeholder annoys someone is looking clickable and doing nothing. */
.sidebar-link--soon {
  color: var(--mute);
  cursor: default;
  opacity: 0.72;
}
.sidebar-link--soon:hover {
  background: transparent;
  color: var(--mute);
}
.sidebar-coming-soon {
  margin-left: auto;
  font-size: 10px;
  white-space: nowrap;
}
body.sidebar-collapsed .sidebar-coming-soon { display: none; }
.sidebar-link--soon i,
.sidebar-link--soon svg { opacity: 0.45; }

/* The marker sits hard right, so the dots form a readable column down the
   sidebar instead of trailing each label at a different x. */
.sidebar-state {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  flex: 0 0 7px;
  margin-left: auto;
}
/* Hollow amber ring: nothing exists yet. */
.sidebar-link--soon[data-state="todo"] .sidebar-state {
  border: 1.5px solid var(--palette-amber, #D97706);
  background: transparent;
}
/* Solid violet: the page is BUILT and works — only the way in is missing.
   A different state from "todo" on purpose: one is a promise, the other is
   a decision owed. */
.sidebar-link--soon[data-state="unlinked"] .sidebar-state {
  background: #7a72c4;
  border: 1.5px solid #7a72c4;
}

.sidebar-legend {
  margin-top: 14px;
  padding: 12px 12px 4px;
  border-top: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.sidebar-legend div {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 10.5px;
  color: var(--mute);
  line-height: 1.3;
}
.sidebar-legend i {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  flex: 0 0 7px;
  display: block;
}
.sidebar-legend .lg-live     { background: var(--accent); }
.sidebar-legend .lg-todo     { border: 1.5px solid var(--palette-amber, #D97706); }
.sidebar-legend .lg-unlinked { background: #7a72c4; }

.nav-pro-badge {
  margin-left: auto;
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.5px;
  color: var(--accent);
  border: 1px solid var(--accent);
  border-radius: 4px;
  padding: 1px 5px;
  opacity: 0.7;
  flex-shrink: 0;
}

.plaid-badge {
  display: inline-block;
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  color: var(--palette-blue);
  border: 1px solid var(--palette-blue);
  border-radius: 4px;
  padding: 1px 5px;
  margin-left: 7px;
  opacity: 0.75;
  vertical-align: middle;
}

.sidebar-link.active {
  color: var(--accent);
  font-weight: 600;
  background: rgba(0, 200, 5, 0.12);
  border: 1px solid rgba(0, 200, 5, 0.2);
}

.sidebar-user {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 8px 12px 4px;
  padding: 10px 12px;
  border-radius: 10px;
  text-decoration: none;
  border: 1px solid var(--border);
  background: var(--surface-2);
  transition: border-color 0.15s, background 0.15s;
  cursor: pointer;
}

.sidebar-user:hover {
  border-color: var(--border-2);
  background: #222228;
}

.sidebar-user.active {
  border-color: rgba(0,200,5,0.25);
  background: rgba(0,200,5,0.07);
}

.sidebar-avatar {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: linear-gradient(135deg, #7C3AED, #4f46e5);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 12px;
  font-weight: 700;
  color: #fff;
  flex-shrink: 0;
}

.sidebar-user-info {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
}

.sidebar-username {
  font-size: 13px;
  font-weight: 600;
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.sidebar-user-label {
  font-size: 11px;
  color: var(--text-2);
}

/* ── Bell button ── */
.sidebar-bell {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 0 10px 4px;
  padding: 10px 12px;
  background: none;
  border: 1px solid transparent;
  border-radius: 8px;
  color: var(--text-2);
  font-size: 13px;
  font-weight: 500;
  font-family: inherit;
  cursor: pointer;
  transition: background 0.15s, color 0.15s, border-color 0.15s;
  position: relative;
  text-align: left;
  flex-shrink: 0;
}

.sidebar-bell:hover {
  background: var(--surface-2);
  color: var(--text);
  border-color: var(--border);
}

.sidebar-bell svg {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
}

.bell-label {
  flex: 1;
}

.bell-badge {
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  background: #f59e0b;
  color: #000;
  font-size: 10px;
  font-weight: 800;
  border-radius: 9px;
  line-height: 1;
}

/* ── Sidebar brand row + collapse toggle ───────────────────────────────────
   The brand link and the collapse button sit in a single row. The button
   is small and quiet by default — visible on hover. */
.sidebar-brand-row {
  display: flex;
  align-items: center;
  border-bottom: 1px solid var(--border);
}
.sidebar-brand-row .sidebar-brand {
  flex: 1;
  border-bottom: none;
}
.sidebar-collapse {
  background: none;
  border: 1px solid transparent;
  border-radius: 6px;
  width: 26px;
  height: 26px;
  display: flex;
  align-items: center;
  justify-content: center;
  margin-right: 14px;
  color: var(--text-2);
  cursor: pointer;
  transition: background 0.15s, color 0.15s, border-color 0.15s;
  flex-shrink: 0;
}
.sidebar-collapse:hover {
  background: var(--surface-2);
  color: var(--text);
  border-color: var(--border);
}
.sidebar-collapse svg { width: 14px; height: 14px; }

/* User-card chevron — hint that the row is tappable, leads to Settings. */
.sidebar-user { position: relative; }
.sidebar-user-chev {
  margin-left: auto;
  width: 14px;
  height: 14px;
  color: var(--text-2);
  flex-shrink: 0;
  opacity: 0.5;
  transition: opacity 0.15s, transform 0.15s;
}
.sidebar-user:hover .sidebar-user-chev {
  opacity: 1;
  transform: translateX(2px);
}

/* Logout button: just the icon sizing — main styling stays in the existing
   .sidebar-logout rule farther down (which gets display:flex added). */
.sidebar-logout-icon { width: 16px; height: 16px; flex-shrink: 0; }

/* ── Collapsible sidebar ───────────────────────────────────────────────────
   Toggle via `body.sidebar-collapsed`. Swapping `--sidebar-w` cascades to
   the container's left padding automatically (see line 68: container uses
   `padding-left: var(--sidebar-w)`). Adds a width transition for smoothness. */
body.sidebar-collapsed { --sidebar-w: 60px; }

.sidebar,
.container {
  transition: width 220ms cubic-bezier(0.4, 0, 0.2, 1),
              padding-left 220ms cubic-bezier(0.4, 0, 0.2, 1);
}

/* In collapsed mode, hide text labels everywhere; let icons carry meaning.
   Hover tooltips (title attrs in nav.js) cover discoverability. */
body.sidebar-collapsed .sidebar-brand,
body.sidebar-collapsed .sidebar-group-label,
body.sidebar-collapsed .sidebar-link-label,
body.sidebar-collapsed .bell-label,
body.sidebar-collapsed .sidebar-user-info,
body.sidebar-collapsed .sidebar-logout-label,
body.sidebar-collapsed .sidebar-user-chev,
/* Center icons + buttons when collapsed; ditch most padding. */
body.sidebar-collapsed .sidebar-link,
body.sidebar-collapsed .sidebar-bell,
body.sidebar-collapsed .sidebar-logout {
  justify-content: center;
  padding: 10px 0;
  margin-left: 8px;
  margin-right: 8px;
}
body.sidebar-collapsed .sidebar-link {
  gap: 0;
}
body.sidebar-collapsed .sidebar-user {
  justify-content: center;
  padding: 8px;
  margin: 8px;
  gap: 0;
}

/* Brand row collapses to just the toggle button, centered. */
body.sidebar-collapsed .sidebar-brand-row {
  justify-content: center;
  padding: 18px 0 14px;
}
body.sidebar-collapsed .sidebar-collapse {
  margin: 0;
}

/* Preserve badge counts on icons by anchoring them top-right of the icon.
   The bell-badge is already absolute-positioned via the bell button being
   `position: relative` (we add it for safety). */
body.sidebar-collapsed .sidebar-bell { position: relative; }
body.sidebar-collapsed .bell-badge {
  position: absolute;
  top: 4px;
  right: 8px;
  transform: scale(0.85);
}

/* Group label gets a thin divider line in collapsed mode (since the text is hidden). */
body.sidebar-collapsed .sidebar-group {
  margin-bottom: 6px;
  position: relative;
}
body.sidebar-collapsed .sidebar-group + .sidebar-group::before {
  content: '';
  display: block;
  height: 1px;
  background: var(--border);
  margin: 2px 14px 6px;
}

/* (PRO chip hide rolled into the main collapsed-state hide list above.) */

/* ── Notification panel ── */
.notif-overlay {
  display: none;
  position: fixed;
  inset: 0;
  z-index: 199;
}

/* Inline form-validation feedback. Use with showFieldError() in auth.js
   to highlight the offending input and surface a short error directly
   under it. !important on border-color so we beat per-page focus styles
   that would otherwise reassert the accent border on focus. */
input.input-error,
textarea.input-error,
select.input-error {
  border-color: #ff5d5d !important;
  background: rgba(255, 93, 93, 0.05);
}
.field-error {
  display: block;
  color: #ff5d5d;
  font-size: 12px;
  margin-top: 4px;
  line-height: 1.4;
}

.notif-panel {
  position: fixed;
  bottom: 0;
  left: 220px;
  width: 340px;
  max-height: 480px;
  background: var(--surface);
  border: 1px solid var(--border-2);
  border-bottom: none;
  border-radius: 16px 16px 0 0;
  z-index: 200;
  display: flex;
  flex-direction: column;
  margin: 0;
  padding: 0;
  color: var(--text);
  transition: transform 0.25s cubic-bezier(0.4, 0, 0.2, 1);
  box-shadow: 0 -8px 32px rgba(0,0,0,0.4);
}

.notif-panel:not([open]) { display: none; }
.notif-view-all { display: block; padding: 14px 18px; border-top: 1px solid var(--border); color: var(--text); font-size: 12px; }
.notif-status { margin: 0; padding: 12px 18px; color: var(--text); background: var(--surface-2); font-size: 12px; }
.notif-confirm { margin: auto; width: min(520px,calc(100vw - 32px)); max-height: calc(100dvh - 32px); padding: 24px; color: var(--text); background: var(--surface); border: 1px solid var(--border-2); border-radius: 0; box-shadow: 6px 6px 0 #121b2133; }
.notif-confirm::backdrop { background: #121b2180; }
.notif-confirm h2 { font: 600 26px/1.15 var(--font-display); margin: 0 0 18px; }
.notif-confirm p { line-height: 1.6; font-size: 14px; margin: 0 0 22px; }
.notif-confirm-actions { display: flex; flex-wrap: wrap; gap: 10px; justify-content: flex-end; }
.notif-confirm-actions button { min-height: 42px; padding: 10px 14px; background: var(--surface); color: var(--text); border: 1px solid var(--border-2); border-radius: 0; cursor: pointer; }
.notif-confirm-actions button:last-child { background: var(--text); color: var(--surface); }
.notif-confirm [role=alert] { background: #ff9a83; color: #121b21; padding: 10px; }
.notif-confirm button:disabled { opacity: .55; cursor: wait; }
.notif-panel::backdrop { background: rgba(18,27,33,.22); }
.notif-close { background: none; border: 0; color: inherit; padding: 4px 8px; font-size: 24px; cursor: pointer; }
.notif-actions { padding: 12px 18px; border-bottom: 1px solid var(--border); }
.notif-item { width: 100%; text-align: left; color: inherit; background: none; border: 0; font: inherit; }
.notif-body > span { display: block; }

.notif-panel--open {
  transform: translateY(0);
}

.notif-panel-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 16px 18px 12px;
  border-bottom: 1px solid var(--border);
  flex-shrink: 0;
}

.notif-panel-title {
  margin: 0;
  font-size: 14px;
  font-weight: 700;
  color: var(--text);
}

.notif-mark-all {
  background: none;
  border: none;
  font-size: 12px;
  color: var(--text-2);
  cursor: pointer;
  font-family: inherit;
  padding: 0;
  transition: color 0.15s;
}

.notif-mark-all:hover { color: var(--text); }

/* Group the header action buttons so the panel stays title-left / actions-right. */
.notif-actions { display: flex; align-items: center; gap: 14px; }
/* Clear all is destructive — hint with red on hover. */
.notif-clear-all:hover { color: var(--red); }

.notif-list {
  overflow-y: auto;
  flex: 1;
}

.notif-empty {
  padding: 32px 18px;
  text-align: center;
  font-size: 13px;
  color: var(--text-2);
}

.notif-item {
  display: flex;
  gap: 12px;
  padding: 14px 18px;
  border-bottom: 1px solid var(--border);
  cursor: pointer;
  transition: background 0.15s;
  position: relative;
}

.notif-item:last-child { border-bottom: none; }
.notif-item:hover      { background: var(--surface-2); }

.notif-item.notif-unread {
  background: rgba(245, 158, 11, 0.04);
}

.notif-icon {
  font-size: 18px;
  flex-shrink: 0;
  width: 24px;
  text-align: center;
  margin-top: 1px;
}

.notif-body {
  flex: 1;
  min-width: 0;
}

.notif-title {
  font-size: 13px;
  font-weight: 600;
  color: var(--text);
  margin-bottom: 3px;
  line-height: 1.3;
}

.notif-text {
  font-size: 12px;
  color: var(--text-2);
  line-height: 1.4;
  margin-bottom: 5px;
}

.notif-time {
  font-size: 11px;
  color: var(--text-2);
  opacity: 0.7;
}

.notif-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: #f59e0b;
  flex-shrink: 0;
  margin-top: 5px;
}

@media (max-width: 768px) {
  .notif-panel {
    left: 0;
    width: 100%;
    border-radius: 16px 16px 0 0;
  }
}

.sidebar-logout {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 16px;
  padding: 9px 16px;
  background: none;
  border: 1px solid var(--border-2);
  border-radius: 8px;
  color: var(--text-2);
  font-size: 13px;
  cursor: pointer;
  transition: color 0.15s, border-color 0.15s;
  flex-shrink: 0;
  text-align: left;
  width: calc(100% - 32px);
  font-family: inherit;
}

.sidebar-logout:hover {
  color: var(--red);
  border-color: var(--red);
}

/* ── Layout ────────────────────────────────────────────────────── */
/* 1280px is the productivity-app convention (Linear, Stripe Dashboard,
   roughly Notion). Wider feels marketing-y and forces eyes to track too
   far horizontally. On screens narrower than 1280px the layout adapts
   naturally — this only changes things for big-monitor users. */
.container {
  max-width: 1280px;
  margin: 0 auto;
  padding: 40px 56px 80px;
  display: flex;
  flex-direction: column;
  gap: 20px;
}

/* ── Page header ───────────────────────────────────────────────── */
.page-header {
  padding-bottom: 4px;
}

.page-header h1 {
  background: linear-gradient(135deg, #ffffff 0%, #ccffcc 100%);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  background-clip: text;
}

.page-sub {
  color: var(--text-2);
  font-size: 14px;
  margin-top: 6px;
}

/* ── 2-column tool layout ──────────────────────────────────────── */
.page-layout {
  display: grid;
  grid-template-columns: 1fr 400px;
  gap: 24px;
  align-items: start;
}

.page-main {
  display: flex;
  flex-direction: column;
  gap: 20px;
}

/* Sticky right panel */
.page-panel {
  position: sticky;
  top: 28px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 14px;
  overflow: hidden;
  max-height: calc(100vh - 80px);
  display: flex;
  flex-direction: column;
  box-shadow: var(--shadow-md);
}

.panel-header {
  padding: 20px 20px 16px;
  border-bottom: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  gap: 12px;
  flex-shrink: 0;
}

.panel-header h2 {
  margin-bottom: 0;
}

.panel-filters {
  display: flex;
  gap: 8px;
}

.panel-filters select {
  flex: 1;
  padding: 8px 12px;
  font-size: 13px;
}

.panel-body {
  overflow-y: auto;
  flex: 1;
}

.panel-empty {
  padding: 40px 20px;
  text-align: center;
  font-size: 14px;
  color: var(--text-2);
}

/* Full-width history card header (replaces sticky panel) */
.history-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 20px;
  gap: 16px;
  flex-wrap: wrap;
}

.history-header h2 { margin-bottom: 0; }

.history-filters {
  display: flex;
  gap: 8px;
  align-items: center;
  flex-wrap: wrap;
}

/* Filter dropdowns ("All time", "All statuses", etc.) — without these,
   the native select renders with a too-pale border + no padding and
   reads as unstyled. Custom caret keeps the dark-theme look. */
.history-filters select {
  /* width:auto is load-bearing. The global `input, select, textarea` rule
     below sets width:100%, which is right in a stacked form column and wrong
     in a flex row of filters — each select took the full width of the row and
     they stacked one per line, with the card's heading stranded beside a
     column of dropdowns. Same rule, same trap, as the one that put a green
     Save bar across the Settings cards. */
  width: auto;
  appearance: none;
  -webkit-appearance: none;
  background: var(--surface-2);
  border: 1px solid var(--border-2);
  border-radius: 8px;
  color: var(--text);
  font-size: 13px;
  font-weight: 500;
  font-family: inherit;
  padding: 8px 32px 8px 12px;
  cursor: pointer;
  outline: none;
  transition: border-color 0.15s, background 0.15s;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%238888aa' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 10px center;
}
.history-filters select:hover {
  border-color: var(--text-2);
  background-color: rgba(255, 255, 255, 0.04);
}
.history-filters select:focus {
  border-color: rgba(0, 200, 5, 0.4);
}

/* The popup that appears when a native <select> is opened can't be fully
   styled (the OS owns it), but Chromium honors background/color on
   <option> — so at least the popup is dark instead of the bright system
   white. Applied globally so every select in the app gets the same
   treatment, not just history filters. Safari ignores this; Firefox is
   partial. Acceptable trade for not building a custom dropdown component. */
select option {
  background: var(--surface);
  color: var(--text);
}
select option:checked,
select option:hover {
  background: var(--surface-2);
  color: var(--text);
}

/* ── Typography ────────────────────────────────────────────────── */
h1 {
  font-size: 28px;
  font-weight: 700;
  color: var(--text);
  letter-spacing: -0.6px;
}

h2 {
  font-size: 14px;
  font-weight: 600;
  margin-bottom: 18px;
  color: var(--text);
  letter-spacing: -0.2px;
}

/* ── Cards ─────────────────────────────────────────────────────── */
.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 24px;
  box-shadow: var(--shadow-md);
}

.summary-row {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 12px;
}

.summary-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 22px 24px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  box-shadow: var(--shadow-md);
  position: relative;
  overflow: hidden;
}

.summary-card::before {
  content: '';
  position: absolute;
  inset: 0;
  background: radial-gradient(ellipse 100% 80% at 50% -30%, rgba(0, 200, 5, 0.04) 0%, transparent 70%);
  pointer-events: none;
}

.summary-label {
  font-size: 11px;
  color: var(--text-2);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.1em;
}

.summary-amount {
  font-size: 26px;
  font-weight: 700;
  background: linear-gradient(135deg, #00C805 0%, #a0ffa3 100%);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  background-clip: text;
  letter-spacing: -0.5px;
}

/* ── Forms ─────────────────────────────────────────────────────── */
.field-row {
  display: flex;
  gap: 12px;
  margin-bottom: 12px;
}

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

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

/* Field labels. Sentence case, quiet — every August 2026 mockup writes them
   this way ("Display name", not "DISPLAY NAME").
   CHANGED HERE ON PURPOSE RATHER THAN PER PAGE. This rule was
   `text-transform: uppercase; font-weight: 700`, and because it is global,
   three of the pages repainted on 2026-08-13 still rendered shouting caps
   while their own mockups showed sentence case — invoices/style.css had
   already grown a `text-transform: none` override to escape it, and a second
   page was about to. One root fix beats five page-scoped undos.
   Small caps still exist as a deliberate device: the page eyebrow and the
   in-card section labels, which set their own text-transform. */
label {
  font-size: 11.5px;
  font-weight: 500;
  color: var(--mute);
  letter-spacing: normal;
}

input, select, textarea {
  padding: 11px 14px;
  background: var(--surface-2);
  border: 1px solid var(--border-2);
  border-radius: 10px;
  font-size: 14px;
  color: var(--text);
  outline: none;
  transition: border-color 0.15s ease, box-shadow 0.2s ease;
  width: 100%;
  -webkit-appearance: none;
  appearance: none;
  font-family: inherit;
}

input::placeholder, textarea::placeholder {
  color: #55556a;
}

input:focus, select:focus, textarea:focus {
  border-color: rgba(0, 200, 5, 0.4);
  box-shadow: 0 0 0 3px rgba(0, 200, 5, 0.08);
}

/* ── Buttons ───────────────────────────────────────────────────── */
.btn-primary {
  background: linear-gradient(135deg, #00C805 0%, #00a004 100%);
  color: #000;
  border: none;
  border-radius: 10px;
  padding: 12px 20px;
  font-size: 14px;
  font-weight: 700;
  cursor: pointer;
  width: 100%;
  transition: opacity 0.15s, transform 0.1s;
  letter-spacing: 0.1px;
  box-shadow: 0 2px 16px rgba(0, 200, 5, 0.25);
  font-family: inherit;
}

.btn-primary:hover  { opacity: 0.88; transform: translateY(-1px); box-shadow: 0 6px 28px rgba(0, 200, 5, 0.35); }
.btn-primary:active { transform: scale(0.98) translateY(0); }

/* ══════════════════════════════════════════════════════════════════════════
   THE PRESS — every control answers your finger, not just the green one.

   Before this, exactly TWO rules in the entire app had an :active state:
   .btn-primary above and .app-fab. Every other button had a hover style and
   nothing at all on press, so on a touchscreen — where there IS no hover —
   most of ALPHA felt dead under the finger until the work finished. That is
   the layer behind "HoneyBook feels smooth and this doesn't". It is not
   animation; it is acknowledgement.

   Written against the ELEMENT, not a list of classes. There are 20 button
   classes across 10 files and counting; enumerating them means the next one
   somebody adds is silently dead again. `button` catches them all, including
   the ones not written yet.

   transform only — scale reflows nothing, so no neighbour shifts and no
   layout is recalculated. :not(:disabled) because a control that cannot be
   used must not pretend to respond.
   ══════════════════════════════════════════════════════════════════════════ */
button:not(:disabled):not([aria-disabled="true"]):active,
a.btn-primary:active,
.pressable:not([aria-disabled="true"]):active {
  transform: scale(0.97);
}

/* ══════════════════════════════════════════════════════════════════════════
   THE WAIT — a spinner primitive, because there was not one anywhere.

   46 places in the app swap a button's text to "Syncing…" / "Saving…" and
   then nothing moves until the work finishes. A static word does not read as
   WORKING, it reads as STUCK — and bank sync now asks Plaid for two years,
   so that wait is regularly 10-40 seconds.

   currentColor, so one primitive works on the green button, the blue sync
   button and a ghost button without a variant per colour.
   ══════════════════════════════════════════════════════════════════════════ */
@keyframes alpha-spin { to { transform: rotate(360deg); } }

.spinner {
  width: 13px;
  height: 13px;
  border-radius: 50%;
  border: 2px solid currentColor;
  border-right-color: transparent;
  animation: alpha-spin 0.7s linear infinite;
  flex-shrink: 0;
  opacity: 0.9;
  display: inline-block;
  vertical-align: -2px;
}
@media (prefers-reduced-motion: reduce) {
  /* Same treatment .skeleton already uses: stop the motion, keep the shape,
     so the busy state is still legible without spinning. */
  .spinner { animation: none; opacity: 0.5; border-right-color: currentColor; }
}

/* A busy button keeps FULL opacity. The usual `disabled` dimming reads as
   "this is broken", when the truth is "this is working" — busy is a state,
   not a disabled state. It is still non-interactive; setBusy() sets the
   disabled attribute for that. */
button.is-busy,
button.is-busy:disabled {
  opacity: 1;
  cursor: progress;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
}
/* Holds the width it had before the label changed, so a row cannot jiggle
   when "Sync All" becomes "Syncing". setBusy() measures and sets this. */
button.is-busy { min-width: var(--busy-w, auto); }

/* The five classes defined in THIS file already declare a transition, so the
   press can ease rather than snap. Classes defined in a tool's own stylesheet
   (settings/style.css and friends) load after this and keep their own
   transition — those press instantly, which is crisp rather than wrong.
   Re-declared in full because `transition` is a shorthand and cannot be
   appended to. */
.btn-secondary { transition: border-color 0.15s, color 0.15s, transform 0.1s; }
.btn-delete,
.btn-edit     { transition: color 0.15s, border-color 0.15s, transform 0.1s; }
.btn-load-more{ transition: background 0.15s, color 0.15s, border-color 0.15s, transform 0.1s; }

/* Rows are the most-pressed surfaces in the app — a ledger line, a pending
   import, an invoice. They are divs, so they need the opt-in class. A smaller
   dip than a button on purpose: a whole row scaling 3% reads as the page
   lurching. */
.pressable { transition: transform 0.1s; cursor: pointer; }
.pressable:active { transform: scale(0.995); }

@media (prefers-reduced-motion: reduce) {
  /* Someone who has asked for less motion still needs to know the tap
     landed, so the press is not removed — it is made instant. */
  button:active,
  .pressable:active { transition: none; }
}

.btn-secondary {
  background: none;
  border: 1px solid var(--border-2);
  border-radius: 10px;
  padding: 10px 16px;
  font-size: 13px;
  color: var(--text-2);
  cursor: pointer;
  transition: border-color 0.15s, color 0.15s;
  font-family: inherit;
}

.btn-secondary:hover {
  border-color: #55556a;
  color: var(--text);
}

/* A row that FOLLOWS something else — an invoice, a split, a royalty — has
   no × and no Edit, because it is a copy and changing the copy would leave
   the original saying something different. This label sits where the ×
   would be, so the row does not just look mysteriously incomplete. */
.entry-derived {
  font-size: 11px;
  color: var(--dim);
  white-space: nowrap;
  cursor: default;
}

.btn-delete {
  background: none;
  border: 1px solid var(--border-2);
  border-radius: 6px;
  color: var(--text-2);
  font-size: 13px;
  cursor: pointer;
  padding: 4px 9px;
  transition: color 0.15s, border-color 0.15s;
  flex-shrink: 0;
  font-family: inherit;
}

.btn-delete:hover {
  color: var(--red);
  border-color: var(--red);
}

.btn-edit {
  background: none;
  border: 1px solid var(--border-2);
  border-radius: 6px;
  color: var(--text-2);
  font-size: 13px;
  cursor: pointer;
  padding: 4px 9px;
  transition: color 0.15s, border-color 0.15s;
  flex-shrink: 0;
  font-family: inherit;
}

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

.btn-load-more {
  display: block;
  width: 100%;
  padding: 12px;
  margin-top: 8px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 8px;
  color: var(--text-2);
  font-size: 13px;
  font-weight: 500;
  cursor: pointer;
  transition: background 0.15s, color 0.15s, border-color 0.15s;
  font-family: inherit;
  text-align: center;
}
.btn-load-more:hover { background: var(--surface-3, var(--border)); color: var(--text); border-color: var(--text-2); }

/* ── History / Entry rows ──────────────────────────────────────── */
.filter-row {
  display: flex;
  gap: 10px;
  margin-bottom: 20px;
}

.filter-row select { flex: 1; }

.entry-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 13px 20px;
  border-bottom: 1px solid var(--border);
  gap: 12px;
}

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

.entry-left {
  display: flex;
  flex-direction: column;
  gap: 3px;
}

.entry-meta {
  font-size: 12px;
  color: var(--text-2);
}

.entry-amount {
  font-weight: 700;
  font-size: 15px;
  color: var(--accent);
}

.error {
  color: var(--red);
  font-size: 13px;
  margin-top: 4px;
}

/* ── Skeleton loading ── */
@keyframes shimmer {
  0%   { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}

.skeleton {
  background: linear-gradient(90deg, var(--surface-2) 25%, var(--border-2) 50%, var(--surface-2) 75%);
  background-size: 200% 100%;
  animation: shimmer 1.4s ease infinite;
  border-radius: 6px;
  display: inline-block;
}

/* Placeholder rows shown in a list container before its data loads, so a
   panel shimmers instead of flashing blank then popping. The list's first
   real render (or empty state) replaces them. Pair with .skeleton. */
.list-skeleton-row {
  display: block;
  height: 46px;
  width: 100%;
  border-radius: 10px;
  margin-bottom: 8px;
}
.list-skeleton-row:last-child { margin-bottom: 0; }
@media (prefers-reduced-motion: reduce) {
  .skeleton { animation: none; }
}

/* ── Toast notifications ── */
#_toast-container {
  position: fixed;
  bottom: 28px;
  right: 28px;
  z-index: 9999;
  display: flex;
  flex-direction: column;
  gap: 10px;
  pointer-events: none;
}

.toast {
  padding: 13px 18px;
  border-radius: 10px;
  font-size: 13px;
  font-weight: 500;
  color: var(--text);
  background: var(--surface-2);
  border: 1px solid var(--border-2);
  border-left-width: 3px;
  box-shadow: var(--shadow-lg);
  opacity: 0;
  transform: translateY(6px);
  transition: opacity 0.22s ease, transform 0.22s ease;
  pointer-events: auto;
  max-width: 300px;
  font-family: inherit;
}

.toast.toast-visible    { opacity: 1; transform: none; }
.toast.toast-success    { border-left-color: var(--accent); }
.toast.toast-error      { border-left-color: var(--red); }
.toast.toast-info       { border-left-color: #3b82f6; }
.toast.toast-warning    { border-left-color: #f59e0b; }

.toast-action {
  display: block;
  margin-top: 6px;
  font-size: 12px;
  font-weight: 600;
  color: var(--accent);
  text-decoration: none;
  opacity: 0.9;
}
.toast-action:hover { opacity: 1; text-decoration: underline; }

/* ── Designed empty states ── */
.empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 48px 24px;
  text-align: center;
  gap: 8px;
  color: var(--text-2);
}

.empty-state-icon {
  width: 40px;
  height: 40px;
  border-radius: 10px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  display: flex;
  align-items: center;
  justify-content: center;
  margin-bottom: 4px;
  color: var(--text-2);
}

.empty-state-icon svg { width: 18px; height: 18px; }

.empty-state-title {
  font-size: 13px;
  font-weight: 600;
  color: var(--text);
}

.empty-state-sub {
  font-size: 12px;
  color: var(--text-2);
  line-height: 1.5;
  max-width: 260px;
}

/* ── iOS-style toggle switch ──────────────────────────────────────
   Use anywhere an on/off boolean needs a clear UI. Wrap markup:
     <span class="switch">
       <input type="checkbox" id="myToggle">
       <span class="switch-slider"></span>
     </span>
   The input is visually hidden but kept in the DOM so .checked works
   as normal — no extra JS to read state.                          */
.switch {
  position: relative;
  display: inline-block;
  width: 38px;
  height: 22px;
  flex-shrink: 0;
  cursor: pointer;
  vertical-align: middle;
}

.switch input[type="checkbox"] {
  opacity: 0;
  width: 0;
  height: 0;
  position: absolute;
  margin: 0;
  padding: 0;
}

.switch-slider {
  position: absolute;
  inset: 0;
  background: var(--surface-2);
  border: 1px solid var(--border-2);
  border-radius: 22px;
  transition: background 0.2s ease, border-color 0.2s ease;
}

.switch-slider::before {
  content: '';
  position: absolute;
  top: 2px;
  left: 2px;
  width: 16px;
  height: 16px;
  background: var(--text-2);
  border-radius: 50%;
  transition: transform 0.22s ease, background 0.22s ease;
}

.switch input:checked + .switch-slider {
  background: rgba(0, 200, 5, 0.18);
  border-color: rgba(0, 200, 5, 0.5);
}

.switch input:checked + .switch-slider::before {
  transform: translateX(16px);
  background: var(--accent);
}

.switch input:focus-visible + .switch-slider {
  box-shadow: 0 0 0 3px rgba(0, 200, 5, 0.15);
}

/* Compact variant — use inline next to label text in dense modals/lists. */
.switch.switch--sm { width: 32px; height: 18px; }
.switch.switch--sm .switch-slider { border-radius: 18px; }
.switch.switch--sm .switch-slider::before { width: 13px; height: 13px; top: 2px; left: 2px; }
.switch.switch--sm input:checked + .switch-slider::before { transform: translateX(14px); }

/* ── Tooltips (hover for hint) ────────────────────────────────── */
.tip {
  position: relative;
  cursor: help;
  border-bottom: 1px dotted var(--text-2);
}

.tip::after {
  content: attr(data-tip);
  position: absolute;
  bottom: calc(100% + 6px);
  left: 50%;
  transform: translateX(-50%);
  padding: 7px 10px;
  background: var(--surface-2);
  border: 1px solid var(--border-2);
  border-radius: 6px;
  font-size: 12px;
  font-weight: 400;
  color: var(--text);
  line-height: 1.4;
  width: max-content;
  max-width: 260px;
  text-align: center;
  pointer-events: none;
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.15s, visibility 0.15s;
  z-index: 100;
  box-shadow: var(--shadow-lg);
  white-space: normal;
}

.tip:hover::after,
.tip:focus::after {
  opacity: 1;
  visibility: visible;
}

.list-count {
  text-align: center;
  font-size: 12px;
  color: var(--text-2);
  padding: 14px 0 4px;
  margin: 0;
  border-top: 1px solid var(--border);
}

.empty-state-cta {
  margin-top: 12px;
  padding: 8px 16px;
  font-size: 13px;
  font-weight: 600;
  color: var(--accent);
  background: none;
  border: 1px solid var(--border-2);
  border-radius: 8px;
  cursor: pointer;
  font-family: inherit;
  text-decoration: none;
  transition: border-color 0.15s, color 0.15s, background 0.15s;
}

.empty-state-cta:hover {
  border-color: var(--accent);
  background: rgba(0, 200, 5, 0.06);
}

/* ── Modal ─────────────────────────────────────────────────────── */
.modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.65);
  backdrop-filter: blur(4px);
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
}

.modal {
  background: var(--surface);
  border: 1px solid var(--border-2);
  border-radius: 16px;
  width: 100%;
  max-width: 500px;
  box-shadow: var(--shadow-lg);
}

.modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 20px 24px 16px;
  border-bottom: 1px solid var(--border);
}

.modal-header h2 { margin: 0; }

.modal-close {
  background: none;
  border: none;
  color: var(--text-2);
  font-size: 22px;
  cursor: pointer;
  line-height: 1;
  padding: 2px 6px;
  border-radius: 4px;
  transition: color 0.15s;
  font-family: inherit;
}

.modal-close:hover { color: var(--text); }

.modal-body { padding: 20px 24px; }

.modal-footer {
  display: flex;
  gap: 10px;
  justify-content: flex-end;
  padding: 16px 24px 20px;
  border-top: 1px solid var(--border);
}

.modal-footer .btn-primary { width: auto; }

/* ── Mobile header ─────────────────────────────────────────────── */
.mobile-header {
  display: none;
}

/* ── Nav overlay ───────────────────────────────────────────────── */
.nav-overlay {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.6);
  z-index: 150;
  backdrop-filter: blur(2px);
}

/* Navigation follows the available window width, including tablet and split
   windows. Keep this breakpoint aligned with nav.js and workspace.css. */
@media (max-width: 1024px) {

  /* Body: remove sidebar offset, add top bar height */
  body {
    padding-left: 0;
    padding-top: 56px;
  }

  /* Sidebar: off-canvas by default */
  .sidebar {
    transform: translateX(-100%);
    transition: transform 0.26s cubic-bezier(0.4, 0, 0.2, 1);
    z-index: 200;
    top: 0;
  }

  /* Sidebar open state */
  body.nav-open .sidebar {
    transform: translateX(0);
    box-shadow: 8px 0 40px rgba(0,0,0,0.7);
  }

  body.nav-open .nav-overlay {
    display: block;
  }

  body.nav-open {
    overflow: hidden;
  }

  /* Mobile top bar */
  .mobile-header {
    display: flex;
    align-items: center;
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    height: 56px;
    background: var(--surface);
    border-bottom: 1px solid var(--border);
    z-index: 99;
    padding: 0 16px;
    gap: 16px;
  }

  .mobile-brand {
    font-size: 12px;
    font-weight: 800;
    letter-spacing: 6px;
    background: linear-gradient(135deg, #ffffff 0%, #a0ffa3 70%, #00C805 100%);
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
    background-clip: text;
    text-decoration: none;
  }

  /* Hamburger button */
  .hamburger {
    background: none;
    border: none;
    cursor: pointer;
    padding: 8px;
    display: flex;
    flex-direction: column;
    gap: 5px;
    flex-shrink: 0;
  }

  .hamburger span {
    display: block;
    width: 20px;
    height: 2px;
    background: var(--text);
    border-radius: 2px;
    transition: transform 0.22s ease, opacity 0.22s ease, width 0.22s ease;
    transform-origin: center;
  }

  body.nav-open .hamburger span:nth-child(1) {
    transform: translateY(7px) rotate(45deg);
  }

  body.nav-open .hamburger span:nth-child(2) {
    opacity: 0;
    width: 0;
  }

  body.nav-open .hamburger span:nth-child(3) {
    transform: translateY(-7px) rotate(-45deg);
  }

}

@media (max-width: 768px) {
  /* Container: reduce padding */
  .container {
    padding: 24px 16px 60px;
    gap: 16px;
  }

  /* Summary row: 2 cols on tablet, 1 on phone */
  .summary-row {
    grid-template-columns: repeat(2, 1fr);
  }

  /* Field rows: stack vertically */
  .field-row {
    flex-direction: column;
  }

  /* History header: already wraps, just tighten */
  .history-header {
    margin-bottom: 16px;
  }

  /* Page header */
  .page-header h1 {
    font-size: 22px;
  }

  h1 { font-size: 22px; }

  /* Cards: tighter padding */
  .card {
    padding: 18px 16px;
  }
}

@media (max-width: 480px) {

  .summary-row {
    grid-template-columns: 1fr;
  }

  .summary-amount {
    font-size: 22px;
  }

  /* Toasts: full width on small phones */
  #_toast-container {
    bottom: 16px;
    right: 16px;
    left: 16px;
  }

  .toast {
    max-width: 100%;
  }
}

/* ════════════════════════════════════════════════════════════════════════
   APPLE-STYLE DESIGN SYSTEM PRIMITIVES
   Shared classes the tool redesigns lean on. Adding here so every tool
   page can opt into the pattern without duplicating CSS.
   ──────────────────────────────────────────────────────────────────────── */

/* ── Hero number block ─────────────────────────────────────────────────────
   One big primary metric at 56px with a row of smaller sub-metrics beneath.
   Replaces 3-card equal-weight summary rows on tool pages.
   Usage:
     <div class="app-hero">
       <div class="app-hero-label">Net balance this month</div>
       <div class="app-hero-value">$17,551<span class="app-hero-cents">.06</span></div>
       <div class="app-hero-row">
         <div class="app-hero-col"><span class="l">Income</span><span class="v inc">$18,775</span></div>
         ...
       </div>
     </div>
*/
.app-hero { margin-bottom: 28px; }
.app-hero-label {
  font-size: 11px;
  color: var(--text-2);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  font-weight: 600;
  margin-bottom: 6px;
}
.app-hero-value {
  font-size: 48px;
  color: var(--text);
  font-weight: 700;
  letter-spacing: -0.025em;
  line-height: 1;
  margin-bottom: 14px;
}
.app-hero-value .app-hero-cents {
  color: var(--text-2);
  font-size: 28px;
  font-weight: 600;
}
.app-hero-value .app-hero-delta {
  color: var(--accent);
  font-size: 13px;
  font-weight: 500;
  margin-left: 12px;
  vertical-align: middle;
}
.app-hero-value .app-hero-delta.down { color: var(--red); }
.app-hero-row { display: flex; gap: 28px; flex-wrap: wrap; }
.app-hero-col .l {
  display: block;
  font-size: 10px;
  color: var(--text-2);
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.app-hero-col .v {
  display: block;
  color: var(--text);
  font-size: 16px;
  font-weight: 600;
  margin-top: 4px;
}
.app-hero-col .v.inc  { color: var(--accent); }
.app-hero-col .v.exp  { color: var(--red); }
.app-hero-col .v.warn { color: #f7c97c; }
.app-hero-col .d {
  display: block;
  color: var(--text-2);
  font-size: 11px;
  margin-top: 2px;
}

/* ── Filter chips ──────────────────────────────────────────────────────────
   Pill-shaped buttons that replace native <select> dropdowns for period /
   category / status filtering. The cross-cutting visual win — flagged in
   the audit on every tool page that uses native selects.
   Usage:
     <div class="app-chip-row">
       <button class="app-chip">3M</button>
       <button class="app-chip app-chip--on">6M</button>
       ...
     </div>
*/
.app-chip-row {
  display: flex;
  gap: 4px;
  flex-wrap: wrap;
  align-items: center;
}
.app-chip {
  background: transparent;
  border: none;
  font-family: inherit;
  font-size: 11.5px;
  color: var(--text-2);
  padding: 6px 13px;
  border-radius: 99px;
  cursor: pointer;
  transition: background 0.15s, color 0.15s;
}
.app-chip:hover { background: var(--surface-2); color: var(--text); }
.app-chip--on  {
  background: var(--surface-2);
  color: var(--text);
}
.app-chip--accent {
  background: rgba(0, 200, 5, 0.15);
  color: var(--accent);
}
.app-chip-divider {
  width: 1px;
  height: 14px;
  background: var(--border);
  margin: 0 4px;
}

/* ── Circular avatar ───────────────────────────────────────────────────────
   Sized circle with initial or icon inside. Used for platforms (Royalty
   Logger), clients (Invoices), collaborators (Splits), categories
   (Deductions), and sources (Ledger).
   Color is set inline since each context picks its own palette. */
.app-avatar {
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  color: #fff;
  flex-shrink: 0;
}
.app-avatar--sm { width: 24px; height: 24px; font-size: 11px; }
.app-avatar--md { width: 32px; height: 32px; font-size: 13px; }
.app-avatar--lg { width: 40px; height: 40px; font-size: 14px; }

/* ── Floating Action Button (FAB) ──────────────────────────────────────────
   Primary green "+ Action" button. Used for opening modal sheets like
   "+ New entry", "+ Log royalty", "+ New invoice".
   Sticky-pinnable on mobile via .app-fab--mobile-sticky modifier. */
.app-fab {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  background: var(--accent);
  color: #000;
  font-family: inherit;
  font-size: 13.5px;
  font-weight: 600;
  padding: 12px 18px;
  border: none;
  border-radius: 10px;
  cursor: pointer;
  transition: opacity 0.15s, transform 0.1s;
  white-space: nowrap;
}
.app-fab:hover  { opacity: 0.9; }
.app-fab:active { transform: scale(0.97); }
.app-fab .app-fab-plus { font-size: 17px; line-height: 1; }
.app-fab--block { display: flex; width: 100%; justify-content: center; }

/* ── Modal sheet ───────────────────────────────────────────────────────────
   Centered overlay modal for actions. Reusable shell — content is custom.
   Pairs with the existing `onb-modal*` styles which use a near-identical
   pattern (keeping both for now; the onb-* variant has demo-specific extras). */
.app-modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.7);
  backdrop-filter: blur(4px);
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
}
.app-modal-sheet {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 16px;
  width: 100%;
  max-width: 460px;
  padding: 26px 28px 22px;
  position: relative;
  box-shadow: 0 24px 64px rgba(0, 0, 0, 0.55);
}
.app-modal-sheet--wide { max-width: 600px; }
.app-modal-sheet .app-modal-close {
  position: absolute;
  top: 12px;
  right: 14px;
  background: none;
  border: none;
  color: var(--text-2);
  font-size: 22px;
  cursor: pointer;
  padding: 4px 10px;
  line-height: 1;
}
.app-modal-sheet .app-modal-close:hover { color: var(--text); }
.app-modal-sheet .app-modal-title {
  font-size: 18px;
  font-weight: 700;
  color: var(--text);
  margin: 0 0 4px;
}
.app-modal-sheet .app-modal-sub {
  font-size: 13px;
  color: var(--text-2);
  margin: 0 0 18px;
  line-height: 1.5;
}
.app-modal-sheet .app-modal-foot {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  margin-top: 18px;
}

/* ── Action bar ────────────────────────────────────────────────────────────
   Colored callout at the top of a page when something needs attention.
   Red = urgent (overdue), yellow = warning (deadline), blue = informational.
   Usage:
     <div class="app-action-bar app-action-bar--urgent">
       <span class="app-action-ic">⚠</span>
       <span class="app-action-text"><strong>INV-008</strong> is 12 days overdue.</span>
       <button class="app-action-btn">Send reminder</button>
     </div>
   The action may also be a link (<a class="app-action-btn" href="...">) when
   it navigates rather than acts — see the Royalty Logger import notice.
*/
.app-action-bar {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 12px 16px;
  border-radius: 12px;
  margin-bottom: 22px;
  font-size: 13px;
  color: var(--text);
}
.app-action-bar--urgent {
  background: linear-gradient(180deg, rgba(255, 138, 120, 0.10), rgba(255, 138, 120, 0.04));
  border: 1px solid rgba(255, 138, 120, 0.30);
}
.app-action-bar--urgent .app-action-ic  { color: #ff8a78; }
.app-action-bar--urgent .app-action-btn { background: #ff8a78; color: #000; }
.app-action-bar--warn {
  background: linear-gradient(180deg, rgba(247, 201, 124, 0.10), rgba(247, 201, 124, 0.04));
  border: 1px solid rgba(247, 201, 124, 0.30);
}
.app-action-bar--warn .app-action-ic  { color: #f7c97c; }
.app-action-bar--warn .app-action-btn { background: #f7c97c; color: #000; }
.app-action-bar--info {
  background: linear-gradient(180deg, rgba(126, 182, 255, 0.10), rgba(126, 182, 255, 0.04));
  border: 1px solid rgba(126, 182, 255, 0.30);
}
.app-action-bar--info .app-action-ic  { color: #7eb6ff; }
.app-action-bar--info .app-action-btn { background: #7eb6ff; color: #000; }
.app-action-text { flex: 1; }
.app-action-text strong { color: var(--text); }
.app-action-btn {
  font-family: inherit;
  font-size: 11.5px;
  font-weight: 600;
  padding: 7px 13px;
  border-radius: 6px;
  border: none;
  cursor: pointer;
  white-space: nowrap;
}
.app-action-btn:hover { opacity: 0.9; }
/* Link-shaped action (navigates instead of acting). Anchors don't inherit
   button reset, so pin the bits that would otherwise differ. */
a.app-action-btn { display: inline-block; text-decoration: none; }

/* ── Row pattern (Apple Card-style transaction row) ────────────────────────
   Used in: Money In/Out history, Royalty Logger payouts, Splits people,
   Invoices list, Deductions list, Ledger.
   Usage:
     <div class="app-row">
       <div class="app-avatar app-avatar--md" style="background:#6c5ce7;">D</div>
       <div class="app-row-info">
         <div class="app-row-name">DistroKid · Streaming</div>
         <div class="app-row-meta">May 17 · Royalty</div>
       </div>
       <div class="app-row-amount inc">+$247.50</div>
     </div>
*/
.app-row {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 12px 0;
  border-bottom: 1px solid var(--border);
}
.app-row:last-child { border-bottom: none; }
.app-row-info  { flex: 1; min-width: 0; }
.app-row-name  {
  color: var(--text);
  font-size: 13.5px;
  font-weight: 500;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.app-row-meta  {
  color: var(--text-2);
  font-size: 11.5px;
  margin-top: 2px;
}
.app-row-amount {
  color: var(--text);
  font-size: 14px;
  font-weight: 600;
  font-feature-settings: "tnum";
  white-space: nowrap;
}
.app-row-amount.inc { color: var(--accent); }
.app-row-amount.exp { color: var(--red); }

/* ── Section + sub-card patterns ────────────────────────────────────────── */
.app-section {
  margin-bottom: 32px;
  padding-bottom: 24px;
  border-bottom: 1px solid var(--border);
}
.app-section:last-child { border-bottom: none; padding-bottom: 0; }
.app-section-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  margin-bottom: 12px;
}
.app-section-head h3 {
  color: var(--text);
  font-size: 14px;
  font-weight: 600;
  margin: 0;
}
.app-section-head .app-section-meta {
  color: var(--text-2);
  font-size: 11.5px;
}

/* ════════════════════════════════════════════════════════════════════════
   MOTION SYSTEM — universal micro-interactions
   One easing + timing language across the app. Any tool page that uses
   these classes inherits the same feel for "you did something."
   ──────────────────────────────────────────────────────────────────────── */

:root {
  --ease-out-soft: cubic-bezier(0.2, 0.7, 0.2, 1);
  --ease-out-pop:  cubic-bezier(0.34, 1.5, 0.64, 1);
  --t-quick:  140ms;
  --t-base:   240ms;
  --t-slow:   520ms;
}

/* Sparkle E — ring + check burst on save success */
@keyframes alpha-ring {
  0%   { transform: translate(-50%, -50%) scale(0.4); opacity: 0; }
  35%  { opacity: 1; }
  100% { transform: translate(-50%, -50%) scale(1.7); opacity: 0; }
}
@keyframes alpha-check-pop {
  0%   { transform: translate(-50%, -50%) scale(0);    opacity: 0; }
  60%  { transform: translate(-50%, -50%) scale(1.15); opacity: 1; }
  100% { transform: translate(-50%, -50%) scale(1);    opacity: 1; }
}
@keyframes alpha-burst-fade {
  0%, 80% { opacity: 1; }
  100%    { opacity: 0; }
}
.alpha-burst-host { position: relative; }
.alpha-burst {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 5;
  animation: alpha-burst-fade 1.1s linear forwards;
}
.alpha-burst::before,
.alpha-burst::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  width: 56px;
  height: 56px;
  border-radius: 50%;
  border: 2px solid rgba(0, 200, 5, 0.55);
  transform: translate(-50%, -50%) scale(0.4);
  animation: alpha-ring 760ms var(--ease-out-soft) forwards;
}
.alpha-burst::after {
  border-color: rgba(0, 200, 5, 0.30);
  animation-delay: 140ms;
}
.alpha-burst-check {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: 1;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: var(--accent);
  display: flex;
  align-items: center;
  justify-content: center;
  color: #000;
  font-weight: 800;
  font-size: 14px;
  line-height: 1;
  box-shadow: 0 0 0 4px rgba(0, 200, 5, 0.18);
  animation: alpha-check-pop 340ms var(--ease-out-pop) forwards;
}

/* Sparkle B — 4-point star, lingers next to fresh rows */
@keyframes alpha-star-twinkle {
  0%, 100% { opacity: 0.35; transform: scale(0.85) rotate(0deg); }
  50%      { opacity: 1;    transform: scale(1)    rotate(45deg); }
}
.alpha-star {
  display: inline-block;
  width: 12px; height: 12px;
  margin-left: 8px;
  flex-shrink: 0;
  vertical-align: middle;
  background: var(--accent);
  clip-path: polygon(50% 0%, 60% 40%, 100% 50%, 60% 60%, 50% 100%, 40% 60%, 0% 50%, 40% 40%);
  animation: alpha-star-twinkle 1.6s var(--ease-out-soft) infinite;
  filter: drop-shadow(0 0 4px rgba(0, 200, 5, 0.55));
}

/* Row drop-in — newly added list rows slide + fade */
@keyframes alpha-row-in {
  0%   { opacity: 0; transform: translateY(-6px); }
  100% { opacity: 1; transform: translateY(0); }
}
.alpha-row-in { animation: alpha-row-in 380ms var(--ease-out-soft) backwards; }

/* Floater — "+$X" rises and fades next to the totals */
@keyframes alpha-floater {
  0%   { opacity: 0; transform: translate(-50%, 4px); }
  18%  { opacity: 1; }
  100% { opacity: 0; transform: translate(-50%, -36px); }
}
.alpha-floater {
  position: absolute;
  top: 0;
  left: 50%;
  transform: translate(-50%, 0);
  font-weight: 700;
  font-size: 15px;
  color: var(--accent);
  pointer-events: none;
  z-index: 30;
  white-space: nowrap;
  animation: alpha-floater 1.5s var(--ease-out-soft) forwards;
  text-shadow: 0 2px 10px rgba(0, 200, 5, 0.45);
}
.alpha-floater--exp {
  color: var(--red);
  text-shadow: 0 2px 10px rgba(255, 68, 85, 0.45);
}

/* Value tick — small bump when a number re-renders */
@keyframes alpha-tick {
  0%   { transform: translateY(4px); opacity: 0.55; }
  100% { transform: translateY(0);   opacity: 1; }
}
.alpha-tick { animation: alpha-tick 380ms var(--ease-out-soft); }

/* Hover glow — opt-in on any tile/card */
.alpha-tile-hover {
  transition:
    transform 200ms var(--ease-out-soft),
    box-shadow 200ms var(--ease-out-soft),
    border-color 200ms var(--ease-out-soft);
}
.alpha-tile-hover:hover {
  transform: translateY(-1px);
  border-color: rgba(0, 200, 5, 0.26);
  box-shadow: 0 6px 22px rgba(0, 200, 5, 0.10), var(--shadow-md);
}

/* Respect reduced-motion */
@media (prefers-reduced-motion: reduce) {
  .alpha-star,
  .alpha-burst,
  .alpha-burst::before,
  .alpha-burst::after,
  .alpha-burst-check,
  .alpha-row-in,
  .alpha-floater,
  .alpha-tick {
    animation: none !important;
  }
  .alpha-tile-hover:hover { transform: none; }
}

/* ── Ghost confirmation card ───────────────────────────────────────────────
   Inline preview that appears right where the user clicked Save. Mirrors
   the row they just created so feedback lands in their gaze instead of
   below the fold. Auto-removes after ~3.4s. */
@keyframes alpha-ghost-out {
  to { opacity: 0; transform: translateY(-4px); }
}
.alpha-ghost-confirm {
  margin-top: 14px;
  border-radius: 12px;
  background: linear-gradient(180deg, rgba(0, 200, 5, 0.10), rgba(0, 200, 5, 0.02));
  border: 1px solid rgba(0, 200, 5, 0.30);
  padding: 12px 16px;
  display: flex;
  align-items: center;
  gap: 14px;
  animation: alpha-row-in 380ms var(--ease-out-soft) backwards;
  position: relative;
  overflow: hidden;
}
.alpha-ghost-confirm::before {
  /* faint glow wash that reinforces the "just happened" feeling */
  content: '';
  position: absolute;
  inset: 0;
  background: radial-gradient(ellipse 80% 100% at 0% 50%, rgba(0, 200, 5, 0.12), transparent 70%);
  pointer-events: none;
}
.alpha-ghost-confirm__check {
  width: 22px; height: 22px;
  border-radius: 50%;
  background: var(--accent);
  color: #000;
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 800;
  font-size: 12px;
  flex-shrink: 0;
  box-shadow: 0 0 0 3px rgba(0, 200, 5, 0.18);
  position: relative;
  z-index: 1;
}
.alpha-ghost-confirm__body {
  flex: 1;
  min-width: 0;
  position: relative;
  z-index: 1;
}
.alpha-ghost-confirm__label {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-weight: 700;
  color: var(--accent);
  margin-bottom: 2px;
}
.alpha-ghost-confirm__row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 12px;
}
.alpha-ghost-confirm__name {
  font-size: 13.5px;
  color: var(--text);
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.alpha-ghost-confirm__meta {
  font-size: 11.5px;
  color: var(--text-2);
  margin-top: 2px;
}
.alpha-ghost-confirm__amount {
  font-size: 15px;
  font-weight: 700;
  color: var(--accent);
  font-feature-settings: "tnum";
  white-space: nowrap;
  flex-shrink: 0;
}
.alpha-ghost-confirm__amount.exp { color: var(--red); }
.alpha-ghost-confirm--out {
  animation: alpha-ghost-out 380ms var(--ease-out-soft) forwards;
}
@media (prefers-reduced-motion: reduce) {
  .alpha-ghost-confirm,
  .alpha-ghost-confirm--out {
    animation: none !important;
  }
}

/* ══ The right rail ══════════════════════════════════════════════════
   Opt-in. A page gets a rail by putting `has-rail` on <body>, adding
   <aside class="rail" id="rail" aria-label="Waiting for you"></aside> (near
   the end of <body>, not before the main content — see dashboard.html for
   why), loading rail.js before the page's own script, and calling
   buildRail() guarded with `if (typeof buildRail === 'function')` (see
   rail.js's docblock). Pages that do none of that are completely
   unaffected by this section; buildRail() also console.warns if it detects
   the has-rail class and the #rail element disagree about whether a page
   opted in.

   The rail is fixed like the sidebar, and `body.has-rail` reserves space
   for it the same way body already reserves space for the sidebar.

   Below ~1150px (1150.98px exactly — see the media query below for why the
   odd number) it stops being fixed and stacks as a normal full-width block
   at the bottom of the page instead of disappearing. That used to be the
   rule — "context below the fold is clutter" — and it was wrong: below the
   fold beats absent. Nine of the ten rail pages had NO other surface for
   "3 splits unpaid" / "1 invoice overdue" below that width; only the
   Dashboard papered over it with its own #actionsRow twin (dashboard.js),
   which is why that page could ship broken for months while looking fine
   in a full-width browser window. This works cheaply because `<aside
   class="rail">` is already the LAST element in the DOM on every page (see
   the placement note above) — dropping position:fixed lets it fall to the
   bottom of the page exactly where it already sits in source order.     */

body.has-rail {
  padding-right: var(--rail-w);
}

.rail {
  position: fixed;
  right: 0;
  top: 0;
  bottom: 0;
  width: var(--rail-w);
  padding: 24px 20px;
  background: var(--ink);
  border-left: 1px solid var(--line);
  overflow-y: auto;
  z-index: 90;
}

/* A rail with nothing in it should not look like a panel that failed to load.
   Two states, both real:
     :empty      — before the first buildRail() call. The <aside> ships empty
                   in all ten pages' markup, so this is every page load, and on
                   Settings it lasts until four separate fetches resolve.
     .rail-blank — after a render that produced no blocks, i.e. every source
                   came back empty or failed. Set by buildRail() in rail.js.
   Both are needed: :empty is defeated by a single newline inside the <aside>,
   and the class cannot cover the window before any JS has run.

   The column itself stays. body.has-rail's padding-right is untouched and
   this is not display:none, because the rail populates asynchronously —
   collapsing it would shift the entire page sideways when the data lands.
   Only the chrome goes. pointer-events:none so an invisible 290px strip
   cannot swallow clicks meant for the page beneath it. */
.rail:empty,
.rail.rail-blank {
  border-left: none;
  background: none;
  padding: 0;
  pointer-events: none;
}

.rail-block { margin-bottom: 24px; }

.rail-label {
  font-family: var(--font-display);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--mute);
  margin-bottom: 10px;
}

.rail-item {
  border: 1px solid var(--line);
  background: var(--panel);
  border-radius: 10px;
  padding: 13px 14px;
  margin-bottom: 9px;
}
.rail-item.rail-warn { border-color: var(--a-line); background: var(--a-wash); }
.rail-item.rail-bad  { border-color: var(--r-line); background: var(--r-wash); }
.rail-item.rail-ok   { border-color: var(--g-line); background: var(--g-wash); }

.rail-item-title {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 8px;
  font-size: 13px;
  font-weight: 600;
  margin-bottom: 3px;
  overflow-wrap: anywhere;
}
.rail-item-amount {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-weight: 500;
  flex-shrink: 0;
}
/* A pasted URL or long line in a description must wrap inside the 290px
   column rather than force a horizontal scrollbar on the whole rail. */
.rail-item-detail { font-size: 11.5px; color: var(--mute); line-height: 1.55; overflow-wrap: anywhere; }

/* Outline only. Solid green belongs to the page, never to the rail. */
.rail-btn {
  display: block;
  width: 100%;
  margin-top: 11px;
  padding: 9px;
  border: 1px solid var(--line-2);
  border-radius: 8px;
  background: transparent;
  color: var(--text);
  font-family: inherit;
  font-size: 12.5px;
  font-weight: 600;
  text-align: center;
  text-decoration: none;
  cursor: pointer;
  transition: border-color .13s, background .13s;
}
.rail-btn:hover { border-color: var(--g-line); background: var(--g-wash); }
.rail-btn:focus-visible { outline: 2px solid var(--g-base); outline-offset: 2px; }

/* --dim is a below-AA-contrast ~3.5:1 against the rail background at this
   size; --mute is the readable secondary-text tone used everywhere else
   in the rail. */
.rail-more { font-size: 11.5px; color: var(--mute); padding-left: 2px; }

.rail-empty {
  display: flex;
  align-items: center;
  gap: 9px;
  font-size: 12.5px;
  color: var(--mute);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 14px;
}
.rail-empty-mark { color: var(--g-text); font-size: 14px; }

.rail-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  padding: 9px 0;
  border-bottom: 1px solid var(--line);
  font-size: 12.5px;
  color: var(--mute);
}
.rail-row:last-child { border-bottom: none; }
.rail-row b {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-weight: 500;
  color: var(--text);
}
.rail-row-hero b { font-size: 15px; }
.rail-row b.rail-ok   { color: var(--g-text); }
.rail-row b.rail-warn { color: var(--a-text); }
.rail-row b.rail-bad  { color: var(--r-text); }

/* Every save-confirmation toast is position:fixed in the same bottom-right
   corner the rail occupies (#_toast-container, below). Without this it
   lands underneath/behind the rail on every migrated page instead of over
   the content it refers to. Reverted to the plain 28px corner inside the
   narrow-viewport media query below, where the rail itself is hidden —
   MUST stay textually after this rule: both selectors have identical
   specificity (one class + one id), so with a tie the media-query override
   only wins if it is later in source order. */
body.has-rail #_toast-container { right: calc(var(--rail-w) + 28px); }

/* THE ODD NUMBER IS DELIBERATE — do not "tidy" this to 1150px.
   dashboard.js's (retired) #actionsRow twin used to hide at exactly
   `min-width: 1151px`; the 0.02px shave kept that boundary and this one
   from leaving a fractional gap under browser zoom / Windows display
   scaling, which routinely produce non-integer CSS pixel viewport widths
   (e.g. 1150.5px). #actionsRow is gone (see renderActionItems()'s docblock
   in dashboard.js), but the two-column layouts below this width across the
   app (.iv-side, .bt-side, .dd-side, .rt-side, .cb-side — see their own
   style.css files) collapse at their own bounds using the same .98 trick
   for the same reason, so this number stays put rather than rounding to a
   value nothing else in the app shares.

   BELOW THIS WIDTH THE RAIL STACKS, IT DOES NOT DISAPPEAR. `.rail` drops
   position:fixed and becomes a normal full-width block; because the
   <aside> is already the LAST element in the DOM on every rail-bearing
   page (tab-order — see the placement note in the section header above),
   it falls to the bottom of the page with no markup changes required.

   Its blocks (.rail-block, built by rail.js) are sized for a 290px column,
   so simply letting `.rail` go block-level would stretch a single tall
   card across 1000+px of a laptop viewport — exactly the "single tall
   stack looks broken" failure mode. Laying the blocks out as grid columns
   instead keeps each one near its designed width. `auto-fill` (NOT
   `auto-fit`) is what makes that hold at every width: it always creates as
   many 240px-minimum column tracks as fit the container, and when there
   are fewer real blocks than tracks the extra tracks stay empty and simply
   absorb the leftover space — `auto-fit` would collapse those empty tracks
   and let the real ones stretch to fill the gap instead, reintroducing the
   same problem one level down. `align-items: start` stops a short block
   (e.g. a 2-row summary) from being stretched to match a taller sibling
   (e.g. three "Waiting for you" cards) in the same row — grid's default
   `stretch` would otherwise inflate it to match. */
@media (max-width: 1150.98px) {
  body.has-rail { padding-right: 0; }

  .rail {
    position: static;
    width: auto;
    max-width: 1280px;         /* matches .container's own max-width */
    margin: 40px auto 0;
    padding: 28px 56px 80px;   /* matches .container's L/R inset */
    border-left: none;
    border-top: 1px solid var(--line);
    overflow-y: visible;
    z-index: auto;
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
    align-items: start;
    gap: 24px 28px;
  }
  .rail-block { margin-bottom: 0; }  /* the grid gap replaces this */

  /* Toast container reverts to the plain corner position once the rail
     stops being a fixed 290px column — see the unconditional rule above;
     this must come after it in source order to win the specificity tie. */
  body.has-rail #_toast-container { right: 28px; }
}

/* Mirrors .container's own mobile inset (see the 768px query further down
   this file) so the stacked rail's edges keep lining up with the page
   content sitting above it. No column-count override needed here: at a
   375-414px phone width, .rail's own padding leaves less room than two
   240px columns plus their gap require, so the auto-fill grid above
   already lands on one column without a second rule saying so. */
@media (max-width: 768px) {
  .rail { padding: 24px 16px 60px; margin-top: 28px; }
}

/* Display face on page titles and card headers. Applied here rather than to
   h1 globally so unmigrated pages keep their current look until their own
   migration lands.
   The Dashboard's title lives in `.dash-header h1`, not `.page-header h1` —
   dashboard.html predates the shared `.page-header` pattern the other tool
   pages use (ledger, invoices, BudgetTracker, etc.) and has its own
   dash-header/dash-sub styling in dashboard.css. Both selectors are listed
   so this rule reaches the Dashboard now and every other page as it
   migrates, without requiring a rename of dashboard.html's markup. */
body.has-rail .page-header h1,
body.has-rail .dash-header h1 {
  font-family: var(--font-display);
  font-weight: 800;
  letter-spacing: -.03em;
}
body.has-rail .app-hero-value,
body.has-rail .app-hero-col .v {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  letter-spacing: -.02em;
}
/* #deltaNet (".app-hero-delta") sits INSIDE .app-hero-value, so without this
   it inherits the mono rule above. The per-metric deltas beside Income/
   Expenses/Royalties (".app-hero-col .d") were never part of that rule and
   stay Inter. Both render the same kind of string — renderDelta() in
   dashboard.js builds one text node like "↑ +$120 vs last mo." mixing an
   arrow, a dollar figure, and words; there's no sub-span around just the
   digits to mono selectively. Mono-ing the whole sentence (words and arrow
   included) reads worse than the one-figure/one-face rule is worth, so this
   excludes deltaNet from the mono rule instead of adding the .d spans to
   it — both delta kinds now match each other in Inter, consistent with
   every other compound description in the UI. Pure figures (the hero value
   itself, the four .v stat values) stay mono above. */
body.has-rail .app-hero-value .app-hero-delta {
  font-family: var(--font-ui);
}

/* ── The entry bar (shared) ───────────────────────────────────────────────────
   Replaces the old right-column "Add Entry" card. One line: amount,
   direction, category, what it was. Date and account live behind More,
   because on nearly every entry they are today and cash.

   flex-wrap AND .eb-what's min-width are both load-bearing, and for the
   reason SplitCalc's history row taught us: in a nowrap flex row whose
   other children are rigid, the one flex:1 child absorbs the entire
   shortfall and resolves to 0px — the text does not truncate, it
   disappears with no ellipsis. Wrapping gives the overflow somewhere to
   go; the min-width stops the field collapsing before the row wraps.
   Change either and re-measure at 375px before believing it. */
.entry-bar {
  background: var(--panel);
  border: 1px solid var(--line-2);
  border-radius: 12px;
  padding: 10px;
  display: flex;
  gap: 8px;
  align-items: center;
  flex-wrap: wrap;
  margin-bottom: 7px;
}
.entry-bar .eb-amt {
  flex: 0 0 120px;
  width: 120px;
  font-family: "IBM Plex Mono", monospace;
  font-size: 16px;
  font-weight: 600;
}
.entry-bar .eb-what {
  flex: 1;
  min-width: 130px;
  font-size: 13px;
}
/* width:auto is the load-bearing line here. shared.css:968 sets
   `input, select, textarea { width: 100% }` and shared.css:993 does the same
   for .btn-primary — sensible for the stacked field columns everywhere else,
   fatal in a flex row, where each control claims the full line and the bar
   stacks vertically instead of forming one line. The mockup this came from is
   a standalone file that never loaded shared.css, so it looked correct there
   and broke on the real page. Expect the same trap in any other mockup that
   lays fields out horizontally. */
.entry-bar input,
.entry-bar select {
  width: auto;
  background: var(--ink);
  border: 1px solid var(--line-2);
  border-radius: 8px;
  padding: 10px 11px;
  color: var(--text);
  font-family: inherit;
}
.entry-bar select {
  padding: 10px 9px;
  font-size: 12.5px;
}
.entry-bar input:focus,
.entry-bar select:focus {
  outline: none;
  border-color: var(--accent);
}
/* The date chip. Reads "Today" on the default and the real date otherwise,
   so a misdated entry is visible at a glance rather than hidden behind
   More. .is-past uses the amber signal, not red: a back-dated entry is
   normal and often correct — it just has to be legible, not alarming. */
.entry-bar .eb-date {
  flex: 0 0 auto;
  width: auto;
  background: transparent;
  border: 1px dashed var(--line-2);
  border-radius: 8px;
  padding: 9px 11px;
  color: var(--mute);
  font-family: inherit;
  font-size: 12px;
  cursor: pointer;
  white-space: nowrap;
}
.entry-bar .eb-date:hover { color: var(--text); border-color: var(--dim); }
.entry-bar .eb-date.is-past {
  border-style: solid;
  border-color: var(--a-line);
  background: var(--a-wash);
  color: var(--a-text);
  font-weight: 600;
}

/* Text button, not a .btn-* — it discloses fields rather than performing
   an action, and giving it button chrome would compete with Add. */
.entry-bar .eb-more {
  background: transparent;
  border: none;
  color: var(--mute);
  font-size: 12px;
  cursor: pointer;
  padding: 0 5px;
}
.entry-bar .eb-more:hover { color: var(--text); }
/* Same shared.css width:100% override, and no full-bleed glow: the button
   sits inline in the row rather than terminating a stacked form. */
.entry-bar .eb-add {
  flex: 0 0 auto;
  width: auto;
  white-space: nowrap;
  padding: 10px 18px;
  box-shadow: none;
}

.entry-more {
  display: flex;
  gap: 12px;
  flex-wrap: wrap;
  padding: 12px 11px 2px;
}
.entry-more .field { flex: 0 0 200px; }
.entry-more[hidden] { display: none; }

.entry-hint {
  font-size: 11.5px;
  color: var(--dim);
  margin: 0 0 16px 11px;
}
.entry-hint b { color: var(--mute); }
.entry-error { margin: 0 0 6px 11px; }

/* The write-off switch sits under the bar, not inside it: it applies to
   what you just typed and only appears for money out. */
.entry-bar ~ .deductible-row { margin: 0 0 10px 11px; }
.entry-bar ~ .bt-cat-help { margin: 0 0 12px 11px; }

@media (max-width: 600px) {
  .entry-bar .eb-amt { flex: 1 0 100%; width: auto; }
  .entry-bar .eb-add { flex: 1 0 100%; }
  .entry-more .field { flex: 1 0 100%; }
}


/* Royalties keeps its reporting period on the bar rather than behind More.
   Month inputs size to their own content and ignore flex-basis in some
   engines, so it gets an explicit floor. */
.entry-bar .eb-month {
  flex: 0 0 auto;
  width: auto;
  min-width: 150px;
  font-size: 12.5px;
}

/* "Who paid you" on the royalty bar — rigid like the selects, not flex:1,
   so the song field stays the one child that absorbs shortfall (see the
   load-bearing note at the top of this block). */
.entry-bar .eb-payer {
  flex: 0 0 auto;
  width: auto;
  min-width: 150px;
  font-size: 13px;
}

/* The date fields that ask WHEN THE MONEY LANDED / when a job is due
   (docs/NOW.md §1.5 item 7i). Shared because two pages ask a date on an
   entry bar now — Catalog's payout day and Work's start/due pair — and a
   third would otherwise copy whichever one it found first.

   ⚠️ `.eb-when`, NOT `.eb-date` — that name is already the Money In/Out date
   CHIP above, a bare dashed input with no caption. Two different controls
   under one class name is a change to one page silently redrawing another.

   The label WRAPS its input so the small caption and the control move as one
   unit when the bar wraps to a second line; a caption stranded above a
   different field is the failure this shape avoids. */
.entry-bar .eb-when {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  flex: 0 0 auto;
  font-size: 10px;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--dim);
  white-space: nowrap;
  margin: 0;
}
.entry-bar .eb-when input {
  flex: 0 0 auto;
  width: auto;
  min-width: 132px;
  font-family: "IBM Plex Mono", monospace;
  font-size: 12px;
}
/* "Don't know" beside a required date. It is a real answer, not a way out,
   so it sits ON the bar at the same weight as the field rather than being
   tucked under it — see RoyaltyEntry.PAYOUT_DATE_CHOICES for why the two
   kinds of blank are different facts. */
.entry-bar .eb-unknown {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  flex: 0 0 auto;
  font-size: 11.5px;
  color: var(--mute);
  white-space: nowrap;
  cursor: pointer;
  margin: 0;
}
.entry-bar .eb-when input:disabled {
  opacity: .5;
  cursor: not-allowed;
}
/* ⚠️ THE LABEL WRAPS ITS INPUT, so showFieldError (auth.js) injects the
   validation message INSIDE this label — inheriting its uppercase, its
   letter-spacing and its nowrap, and landing to the right of the box rather
   than under it because the label is a row-direction flex container. A
   132-character sentence rendered that way pushed the whole entry bar off
   the page on the first unanswered submit. Reset here rather than in
   .field-error, which is shared with every stacked form in the app. */
.entry-bar .eb-when {
  flex-wrap: wrap;
}
.entry-bar .eb-when .field-error {
  flex: 1 0 100%;
  text-transform: none;
  letter-spacing: normal;
  white-space: normal;
  font-size: 11px;
  line-height: 1.4;
  margin-top: 4px;
}

/* ── Page eyebrow ───────────────────────────────────────────────────────
   The small-caps line above a page title ("YOUR ACCOUNT" over "Settings").
   Shared because more than one page uses it; SplitCalc and Invoices still
   carry private copies (.sc-eyebrow, .iv-eyebrow) that should fold into this
   one on the next cosmetic pass.
   Deliberately still uppercase: the global `label` rule above dropped its
   caps, and this is the device that keeps small caps meaningful by being one
   of the few places they survive. */
.app-eyebrow {
  font-family: var(--font-display);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--mute);
  margin-bottom: 5px;
}

/* ── Compact totals line ────────────────────────────────────────────────
   Used by Money In/Out and Royalties in place of a 48px hero number.
   Shared because both pages need it and the August mockups have no hero
   on either. Lives here rather than in one page's stylesheet — the first
   version shipped in BudgetTracker/style.css and rendered as run-on text
   the moment Royalties used the same markup. */
.bt-totals {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 6px 22px;
  margin: 0 0 16px;
}
.bt-total { display: flex; align-items: baseline; gap: 7px; }
.bt-total .k {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--mute);
}
.bt-total .v {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: 16px;
  font-weight: 600;
  letter-spacing: -0.01em;
}
.bt-totals-note { font-size: 11.5px; color: var(--dim, var(--mute)); }
@media (max-width: 620px) { .bt-totals-note { display: none; } }
.alpha-session-notice {
  position: fixed;
  right: 20px;
  bottom: 20px;
  z-index: 11000;
  box-sizing: border-box;
  width: min(420px, calc(100vw - 40px));
  padding: 20px;
  background: var(--workspace-white, #f8f4e7);
  color: var(--workspace-ink, #121b21);
  border: 1px solid currentColor;
  box-shadow: 5px 5px 0 var(--workspace-ink, #121b21);
}
.alpha-session-notice p { margin: 0 0 12px; }
.alpha-session-notice small { display: block; margin-top: 12px; }

/* Figma ALPHA / Payment success mark, 346:2516. One exact glyph for M1d/C3/C4. */
.alpha-payment-success{display:inline-flex;flex:0 0 36px;width:36px;height:36px;border-radius:50%;padding:8px;background:var(--workspace-field);vertical-align:middle}
.alpha-payment-success::before{content:"";display:block;width:20px;height:20px;background:url('/assets/controls/payment-confirmed.svg') center/20px 20px no-repeat}

/* Stable content only: Import's fast 5px settle, with no hidden/loading baseline. */
@keyframes alpha-content-enter {
  from { opacity:.7; transform:translateY(5px); }
  to { opacity:1; transform:none; }
}
.alpha-content-enter,
body.alpha-app :is(.iw-dialog,.cw-dialog,.rc-dialog,.settings-dialog,.support-dialog,.tp-dialog)[open],
body.alpha-app .help-workspace .support-sheet,
.settings-page #settingsOverview:not([hidden]) {
  animation:alpha-content-enter 180ms ease-out;
}
@media(prefers-reduced-motion:reduce) {
  .alpha-content-enter,
  body.alpha-app :is(.iw-dialog,.cw-dialog,.rc-dialog,.settings-dialog,.support-dialog,.tp-dialog)[open],
  body.alpha-app .help-workspace .support-sheet,
  .settings-page #settingsOverview:not([hidden]) { animation:none!important; }
}
.alpha-reduce-motion .alpha-content-enter,
body.alpha-reduce-motion :is(.iw-dialog,.cw-dialog,.rc-dialog,.settings-dialog,.support-dialog,.tp-dialog)[open],
body.alpha-reduce-motion .help-workspace .support-sheet,
.settings-page.alpha-reduce-motion #settingsOverview:not([hidden]) { animation:none!important; }

/* Navigation uses direction; local panels retain the smaller vertical settle. */
@keyframes alpha-page-enter {
  from { opacity:.7; transform:translateX(-12px); }
  to { opacity:1; transform:none; }
}
.alpha-page-enter { animation:alpha-page-enter 200ms ease-out; }
@media(prefers-reduced-motion:reduce) { .alpha-page-enter { animation:none!important; } }
.alpha-reduce-motion .alpha-page-enter { animation:none!important; }
