/* ==========================================================================
   Trader Journal — Institutional Terminal tokens (FINAL)
   Dark is default (:root, no attribute). Light via <html data-theme="light">.
   Charts read tokens via getComputedStyle(document.documentElement);
   re-read on 'themechange'. Legacy aliases keep existing rules resolving
   during the literal sweep — delete the alias block when the sweep is done.
   CONTRACT NOTES:
   - Accent (--accent) means interaction only: active nav, focus, primary CTA,
     chart line. Nothing else.
   - --pnl-* means money only. Never decoration.
   - Text-entry surfaces always use --surface-inset (opaque). Never
     translucency behind text being typed.
   ========================================================================== */

:root {
  color-scheme: dark;

  /* Surfaces — soft clay ramp. Two rules from the clay recipe are structural,
     not taste: the page is never pure black, and every surface is LIGHTER than
     the page (depth reads as lift, not as an outline). The ramp carries a
     faint 245deg cast so the hue-matched clay shadows below have something to
     match. */
  --surface-0: #0b0c11;        /* page */
  --surface-1: #171a23;        /* panels, cards — +5% L over the page */
  --surface-2: #1d202b;        /* nav, modal, raised */
  --surface-3: #21242f;        /* hover / active fill */
  --surface-inset: #101219;    /* inputs, table heads, wells — ALWAYS OPAQUE */

  /* Hairlines — kept in the vocabulary (tables and grids still need a rule),
     but softened: on clay surfaces depth is carried by shadow, not by edges. */
  --line: #262a36;
  --line-strong: #343a4a;
  --line-accent: rgba(91, 141, 239, 0.45);

  /* Text */
  --text: #e9ecf2;
  --text-soft: #9aa2b4;
  --text-faint: #969eb2;       /* labels only, never below 11px / weight 600; AA 4.5:1 on all surfaces */
  --text-inverse: #0b0d10;

  /* Accent — one hue, interaction only */
  --accent: #5b8def;
  --accent-strong: #7da5f5;
  --accent-muted: rgba(91, 141, 239, 0.12);

  /* P&L — money colors, used for nothing else */
  --pnl-pos: #2fd18c;
  --pnl-pos-soft: rgba(47, 209, 140, 0.12);
  --pnl-pos-line: rgba(47, 209, 140, 0.38);
  --pnl-neg: #f5537a;
  --pnl-neg-soft: rgba(245, 83, 122, 0.12);
  --pnl-neg-line: rgba(245, 83, 122, 0.38);
  --pnl-flat: var(--text-soft);

  /* P&L intensity — JS sets clamp(|dayPnl| / dailyLossLimit, 0, 1) on the
     balance card; drives signed-hairline width/opacity, never a glow */
  --pl-intensity: 0;

  /* Status */
  --warn: #f0b34e;
  --warn-soft: rgba(240, 179, 78, 0.12);
  --info: #4cc3e8;
  --info-soft: rgba(76, 195, 232, 0.12);

  /* Charts — canvas reads these; nothing hard-coded in charts.js */
  --chart-grid: #22262f;
  --chart-axis: #969eb2;
  --chart-line: var(--accent);
  --chart-pos: var(--pnl-pos);
  --chart-neg: var(--pnl-neg);
  --chart-radar: var(--info);
  --chart-font-size: 11;       /* px, unitless for canvas */
  --chart-font-family: "JetBrains Mono", ui-monospace, monospace;

  /* Charts — depth pass. Every gradient stop, glow and extruded face the
     canvas renderer uses is a token, so both themes stay authored in CSS.
     *-fade pairs exist because a canvas gradient interpolates RGBA channels
     literally: fading to `transparent` (rgba(0,0,0,0)) greys the ramp, so
     every fade stop repeats its own RGB at alpha 0. */
  --chart-line-deep: #2f4f8f;               /* extruded side face, accent bars */
  --chart-glow: rgba(91, 141, 239, 0.55);
  --chart-halo: rgba(91, 141, 239, 0.26);
  --chart-halo-fade: rgba(91, 141, 239, 0);
  --chart-area-top: rgba(91, 141, 239, 0.42);
  --chart-area-mid: rgba(91, 141, 239, 0.13);
  --chart-area-bottom: rgba(91, 141, 239, 0);
  --chart-pos-deep: #17805a;
  --chart-pos-glow: rgba(47, 209, 140, 0.55);
  --chart-neg-deep: #8f2646;
  --chart-neg-glow: rgba(245, 83, 122, 0.55);
  --chart-halo-neg: rgba(245, 83, 122, 0.26);
  --chart-halo-neg-fade: rgba(245, 83, 122, 0);
  --chart-neg-area-top: rgba(245, 83, 122, 0.04);
  --chart-neg-area-bottom: rgba(245, 83, 122, 0.46);
  --chart-highlight: rgba(255, 255, 255, 0.30);
  --chart-highlight-fade: rgba(255, 255, 255, 0);
  --chart-shade: rgba(0, 0, 0, 0.38);
  --chart-shade-fade: rgba(0, 0, 0, 0);
  --chart-track: rgba(150, 158, 178, 0.10);
  --chart-crosshair: rgba(154, 162, 180, 0.55);
  --chart-tooltip-bg: rgba(19, 22, 28, 0.96);
  --chart-tooltip-line: #333a49;
  --chart-tooltip-shadow: rgba(0, 0, 0, 0.6);
  --chart-ring-wash: rgba(150, 158, 178, 0.05);
  --chart-radar-core: rgba(76, 195, 232, 0.40);
  --chart-radar-edge: rgba(76, 195, 232, 0.04);
  --chart-radar-glow: rgba(76, 195, 232, 0.55);
  /* Chart well — must sit BELOW --surface-1 in luminance in BOTH themes, or
     the "pressed into the card" read inverts and the canvas looks like a bump
     glued on top. Verified by the contrast harness, not by eye. */
  --chart-canvas-top: #13151d;
  --chart-canvas-bottom: #0f1118;
  --chart-canvas-glow: rgba(91, 141, 239, 0.07);
  --chart-canvas-shadow: rgba(0, 0, 0, 0.55);
  /* Sparkline area fades — the alpha-0 twins of --pnl-pos/--pnl-neg. */
  --spark-pos-fade: rgba(47, 209, 140, 0);
  --spark-neg-fade: rgba(245, 83, 122, 0);

  /* Type */
  --font-ui: "Sora", "Manrope", system-ui, sans-serif;
  --font-mono: "JetBrains Mono", ui-monospace, "SF Mono", Consolas, monospace;
  --fs-micro: 11px;            /* badges, kickers — the floor */
  --fs-label: 12px;
  --fs-data: 13px;             /* table numerals */
  --fs-body: 13.5px;
  --fs-md: 15px;
  --fs-lg: 18px;
  --fs-h2: 22px;
  --fs-metric: 26px;
  --fs-hero: clamp(2rem, 4.5vw, 3.25rem);

  /* Spacing — 4px base */
  --space-1: 4px;  --space-2: 8px;  --space-3: 12px;
  --space-4: 16px; --space-5: 20px; --space-6: 24px;
  --space-8: 32px; --space-10: 40px; --space-12: 48px;

  /* ---- CLAY GEOMETRY -----------------------------------------------------
     Radius is the first half of the clay read. Below ~14px a clay element
     stops looking moulded and starts looking like a flat box with a shadow,
     so the clay scale never dips under 14. Chips and inputs that sit INSIDE a
     clay card use the legacy scale (8/12px) — nesting 26 inside 26 inflates
     the card and eats horizontal room. */
  --radius-clay-sm: 14px;      /* chart wells, nested cards */
  --radius-clay-md: 20px;      /* metric cards, buttons, tab bar items */
  --radius-clay-lg: 26px;      /* panels, view heads, sidebar */
  --radius-clay-xl: 32px;      /* hero surfaces, modals */

  /* Legacy scale — same names, clay-scaled values, so every existing rule
     rounds up without being rewritten. */
  --radius-sm: 8px;            /* pills, tags, delta chips */
  --radius-md: 12px;           /* inputs, small buttons, nav items */
  --radius-lg: var(--radius-clay-sm);
  --radius-xl: var(--radius-clay-lg);
  --radius-pill: 999px;

  /* ---- CLAY ELEVATION ----------------------------------------------------
     The signature is one box-shadow carrying three jobs: a soft hue-matched
     outer drop, an inset dark shade on the BOTTOM, and an inset light
     highlight on the TOP. One light source, top/top-left, everywhere.
     Dark clay is re-derived, never inverted: the top inset is a low-alpha
     hue-matched near-white (0.10-0.18). Push it past ~0.25 and the surface
     reads as wet plastic; drop it under ~0.08 and the volume disappears.
       --clay-raised  cards, panels — resting surfaces
       --clay-float   genuinely interactive things — buttons, nav, modal
       --clay-soft    quiet containers — outer drop only, so "puffy" keeps
                      meaning "tappable" instead of leaking onto everything
       --clay-pressed wells and active states — the surface sinks inward */
  --clay-raised:
    0 14px 28px -14px hsl(245 45% 2% / 0.80),
    0 4px 10px -6px hsl(245 45% 2% / 0.55),
    inset 0 -6px 12px -6px hsl(245 40% 2% / 0.55),
    inset 0 7px 12px -6px hsl(245 25% 80% / 0.12);
  --clay-float:
    0 22px 40px -16px hsl(245 45% 2% / 0.85),
    0 6px 14px -7px hsl(245 45% 2% / 0.60),
    inset 0 -7px 14px -7px hsl(245 40% 2% / 0.60),
    inset 0 8px 14px -7px hsl(245 25% 82% / 0.17);
  --clay-soft: 0 12px 26px -18px hsl(245 45% 2% / 0.70);
  /* Primary action. The shadow is derived from the ACCENT hue, not from the
     surface — a neutral drop under a saturated button is the tell that kills
     the moulded read. */
  --clay-accent:
    0 18px 32px -14px hsl(220 62% 16% / 0.90),
    0 5px 12px -6px hsl(220 62% 13% / 0.62),
    inset 0 -6px 12px -6px hsl(220 66% 12% / 0.50),
    inset 0 7px 12px -6px hsl(0 0% 100% / 0.26);
  --clay-pressed:
    inset 0 3px 8px -2px hsl(245 45% 2% / 0.70),
    inset 0 -2px 5px -3px hsl(245 25% 80% / 0.07);

  /* Barely-there soft edge. In dark this is load-bearing: with the drop
     shadow invisible against a dark page, the hairline is what separates
     surface from background and what holds the 3:1 non-text bar. */
  --clay-edge: hsl(245 25% 82% / 0.09);
  --clay-edge-strong: hsl(245 25% 84% / 0.14);

  /* Control boundary. Clay carries depth by shadow, but a soft shadow edge
     measures ~1.4:1 — WCAG 1.4.11 wants 3:1 to identify a control. So every
     CONTROL (button, input, select, toggle) keeps one hairline at this value
     while every CONTAINER (panel, card, rail) drops its border entirely. */
  --control-edge: #6b7385;

  /* Elevation aliases — every existing rule keeps its token name. */
  --shadow: var(--clay-raised);
  --shadow-modal:
    0 40px 80px -24px hsl(245 45% 2% / 0.88),
    0 10px 24px -10px hsl(245 45% 2% / 0.62),
    inset 0 -8px 16px -8px hsl(245 40% 2% / 0.55),
    inset 0 9px 16px -8px hsl(245 25% 82% / 0.15);
  --edge-highlight: inset 0 1px 0 var(--clay-edge);
  --focus-ring: 0 0 0 3px var(--surface-0), 0 0 0 6px var(--accent);

  /* Z-index */
  --z-nav: 100;
  --z-dropdown: 200;
  --z-overlay: 900;
  --z-modal: 1000;
  --z-toast: 1100;

  /* Motion */
  --dur-instant: 90ms;
  --dur-fast: 140ms;
  --dur-base: 200ms;
  --dur-slow: 320ms;
  --dur-tick: 480ms;
  --dur-draw: 640ms;
  --ease-out: cubic-bezier(0.2, 0, 0, 1);
  --ease-in-out: cubic-bezier(0.45, 0, 0.15, 1);
  --ease-snap: cubic-bezier(0.3, 1.1, 0.3, 1);
  --ease-squish: cubic-bezier(0.34, 1.56, 0.64, 1);   /* clay press/release */
  --dur-squish: 180ms;

  /* Landing depth kit — the marketing surface only. Every value here is a
     colour or a scalar so both themes stay authored in CSS; the gradients
     themselves are composed at the rule so light/dark share one recipe. */
  --fs-hero-xl: clamp(2.35rem, 5.2vw, 4.15rem);
  --space-16: 64px;
  --space-20: 80px;
  --space-24: 96px;
  --stagger-reveal: 70ms;

  --aurora-a: rgba(91, 141, 239, 0.32);
  --aurora-b: rgba(47, 209, 140, 0.17);
  --aurora-c: rgba(126, 92, 239, 0.22);
  --aurora-fade: rgba(91, 141, 239, 0);
  --landing-grid-line: rgba(91, 141, 239, 0.16);

  /* Clay pass: the "glass" is now opaque moulded clay. The gradient drops to
     a whisper (volume comes from the insets — a strong plane gradient fights
     them) and both depth tokens resolve to the clay stacks, which converts
     every landing consumer without rewriting its rule. */
  --glass-top: #1a1d27;
  --glass-bottom: #15181f;
  --glass-edge: var(--clay-edge);
  --glass-sheen: rgba(255, 255, 255, 0.08);
  --glass-sheen-fade: rgba(255, 255, 255, 0);
  --halo-accent: rgba(91, 141, 239, 0.42);
  --floor-glow: rgba(91, 141, 239, 0.24);
  --depth-shadow: var(--clay-float);
  --depth-shadow-sm: var(--clay-raised);

  /* Deck depth kit (Phase C) — the dashboard's card surfaces. Same authoring
     rule as the landing kit: colours and scalars only, gradients composed at
     the rule so both themes share one recipe.
     Clay pass: the card gradient drops to a whisper (clay volume comes from
     the insets, and a strong plane gradient fights them), and the two shadow
     tokens now resolve to the clay stacks so every Phase C consumer converts
     without being rewritten. */
  --deck-top: #1a1d27;              /* card gradient, lit edge */
  --deck-bottom: #15181f;           /* card gradient, floor */
  --deck-edge: var(--clay-edge);
  --deck-sheen: rgba(255, 255, 255, 0.06);   /* pointer specular */
  --deck-sheen-fade: rgba(255, 255, 255, 0);
  --deck-wash: rgba(91, 141, 239, 0.09);     /* accent inner glow at card top */
  --deck-shadow: var(--clay-raised);
  --deck-shadow-lift: var(--clay-float);
  --deck-groove: hsl(245 45% 2% / 0.70);     /* recessed meter track */
  /* Dashboard entrance (retuned in Phase E). Clay is soft and heavy, so the
     deck settles instead of overshooting: --ease-clay has no bounce, and the
     cascade is slower and wider than the Phase C snap. */
  --ease-clay: cubic-bezier(0.22, 1, 0.36, 1);
  --stagger-dash: 72ms;
  --dur-reveal: 820ms;
}

/* Light theme — only raw values override; every var()-referencing token
   (--chart-pos, --chart-line…) follows automatically.
   All values below are verified by scratchpad contrast script:
   4.5:1 body / 3:1 large numerals in both themes. */
[data-theme="light"] {
  color-scheme: light;

  /* Clay ramp, light. The page is a pale tint — never #fff — so the cards
     have something to be lighter than. */
  --surface-0: #ecedf5;
  --surface-1: #fbfbff;
  --surface-2: #ffffff;
  --surface-3: #e8ebf5;
  --surface-inset: #e7eaf4;

  --line: #dfe2ec;
  --line-strong: #c6cbda;
  --line-accent: rgba(38, 88, 199, 0.5);

  --text: #171a21;
  --text-soft: #4c5567;
  --text-faint: #5c6476;
  --text-inverse: #ffffff;

  --accent: #2658c7;
  --accent-strong: #1c47a8;
  --accent-muted: rgba(38, 88, 199, 0.09);

  --pnl-pos: #0a6e46;
  --pnl-pos-soft: rgba(10, 110, 70, 0.10);
  --pnl-pos-line: rgba(10, 110, 70, 0.35);
  --pnl-neg: #c02a4f;
  --pnl-neg-soft: rgba(192, 42, 79, 0.09);
  --pnl-neg-line: rgba(192, 42, 79, 0.35);

  --warn: #8a5a0c;
  --warn-soft: rgba(138, 90, 12, 0.10);
  --info: #0c6b8a;
  --info-soft: rgba(12, 107, 138, 0.10);

  --chart-grid: #e2e5ef;
  --chart-axis: #5c6476;

  /* Depth pass, light theme. Glows are tighter and areas lighter: on white the
     same alphas that read as atmosphere on graphite read as smudge. */
  --chart-line-deep: #16337c;
  --chart-glow: rgba(38, 88, 199, 0.32);
  --chart-halo: rgba(38, 88, 199, 0.18);
  --chart-halo-fade: rgba(38, 88, 199, 0);
  --chart-area-top: rgba(38, 88, 199, 0.28);
  --chart-area-mid: rgba(38, 88, 199, 0.09);
  --chart-area-bottom: rgba(38, 88, 199, 0);
  --chart-pos-deep: #064e31;
  --chart-pos-glow: rgba(12, 123, 78, 0.32);
  --chart-neg-deep: #8a1c39;
  --chart-neg-glow: rgba(203, 46, 86, 0.32);
  --chart-halo-neg: rgba(203, 46, 86, 0.18);
  --chart-halo-neg-fade: rgba(203, 46, 86, 0);
  --chart-neg-area-top: rgba(203, 46, 86, 0.03);
  --chart-neg-area-bottom: rgba(203, 46, 86, 0.30);
  --chart-highlight: rgba(255, 255, 255, 0.55);
  --chart-highlight-fade: rgba(255, 255, 255, 0);
  --chart-shade: rgba(23, 26, 33, 0.22);
  --chart-shade-fade: rgba(23, 26, 33, 0);
  --chart-track: rgba(100, 108, 125, 0.10);
  --chart-crosshair: rgba(76, 85, 103, 0.55);
  --chart-tooltip-bg: rgba(255, 255, 255, 0.98);
  --chart-tooltip-line: #c2c8d3;
  --chart-tooltip-shadow: rgba(23, 26, 33, 0.20);
  --chart-ring-wash: rgba(100, 108, 125, 0.05);
  --chart-radar-core: rgba(13, 115, 149, 0.26);
  --chart-radar-edge: rgba(13, 115, 149, 0.03);
  --chart-radar-glow: rgba(13, 115, 149, 0.32);
  /* Was #ffffff on a #fbfbff card — a well that read as a bump. */
  --chart-canvas-top: #f2f4fb;
  --chart-canvas-bottom: #e9ecf7;
  --chart-canvas-glow: rgba(38, 88, 199, 0.05);
  --chart-canvas-shadow: rgba(23, 26, 33, 0.10);
  --spark-pos-fade: rgba(12, 123, 78, 0);
  --spark-neg-fade: rgba(203, 46, 86, 0);

  /* Clay elevation, light. Re-derived from the light surface, not inverted:
     the top inset is near-opaque white (that is what sells the moulded edge
     on a pale surface) and every outer shadow is hue-matched 245deg, never
     neutral grey — grey kills the moulded read instantly. Negative spread
     keeps the drops tight so two cards 16px apart do not merge into one
     smudge. */
  --clay-raised:
    0 10px 20px -10px hsl(245 30% 45% / 0.22),
    0 3px 6px -4px hsl(245 30% 45% / 0.13),
    inset 0 -5px 10px -4px hsl(245 30% 45% / 0.15),
    inset 0 6px 10px -5px hsl(0 0% 100% / 0.95);
  --clay-float:
    0 18px 30px -12px hsl(245 32% 42% / 0.28),
    0 6px 12px -6px hsl(245 32% 42% / 0.16),
    inset 0 -6px 12px -5px hsl(245 32% 42% / 0.20),
    inset 0 7px 12px -6px hsl(0 0% 100% / 1);
  --clay-soft: 0 8px 18px -12px hsl(245 30% 45% / 0.22);
  --clay-accent:
    0 16px 28px -12px hsl(220 60% 40% / 0.42),
    0 5px 10px -6px hsl(220 60% 40% / 0.26),
    inset 0 -6px 12px -5px hsl(220 66% 24% / 0.32),
    inset 0 7px 12px -6px hsl(0 0% 100% / 0.34);
  --clay-pressed:
    inset 0 3px 7px -2px hsl(245 30% 45% / 0.24),
    inset 0 -2px 5px -3px hsl(0 0% 100% / 0.9);

  --clay-edge: hsl(245 22% 30% / 0.07);
  --clay-edge-strong: hsl(245 22% 30% / 0.12);
  --control-edge: #82889a;

  --shadow: var(--clay-raised);
  --shadow-modal:
    0 34px 64px -22px hsl(245 32% 42% / 0.32),
    0 10px 22px -10px hsl(245 32% 42% / 0.18),
    inset 0 -7px 14px -6px hsl(245 32% 42% / 0.16),
    inset 0 8px 14px -7px hsl(0 0% 100% / 1);
  --edge-highlight: inset 0 1px 0 hsl(0 0% 100% / 0.9);
  --focus-ring: 0 0 0 3px var(--surface-1), 0 0 0 6px var(--accent);

  /* Landing depth kit, light. On white the same alphas that read as
     atmosphere on graphite read as dirt, so every wash is roughly halved
     and the glass becomes an opaque paper card with a soft edge. */
  --aurora-a: rgba(38, 88, 199, 0.17);
  --aurora-b: rgba(12, 123, 78, 0.11);
  --aurora-c: rgba(104, 74, 214, 0.13);
  --aurora-fade: rgba(38, 88, 199, 0);
  --landing-grid-line: rgba(38, 88, 199, 0.13);

  --glass-top: #ffffff;
  --glass-bottom: #f6f7fd;
  --glass-edge: var(--clay-edge);
  --glass-sheen: rgba(255, 255, 255, 0.9);
  --glass-sheen-fade: rgba(255, 255, 255, 0);
  --halo-accent: rgba(38, 88, 199, 0.30);
  --floor-glow: rgba(38, 88, 199, 0.16);
  --depth-shadow: var(--clay-float);
  --depth-shadow-sm: var(--clay-raised);

  /* Deck depth kit, light. On paper the card gradient is a whisper and the
     specular is white-on-white — anything stronger reads as a smudge over
     the numerals. */
  --deck-top: #fdfdff;
  --deck-bottom: #f5f6fc;
  --deck-edge: hsl(0 0% 100% / 0.9);
  --deck-sheen: rgba(255, 255, 255, 0.85);
  --deck-sheen-fade: rgba(255, 255, 255, 0);
  --deck-wash: rgba(38, 88, 199, 0.055);
  --deck-shadow: var(--clay-raised);
  --deck-shadow-lift: var(--clay-float);
  --deck-groove: hsl(245 30% 45% / 0.22);
}

/* Theme crossfade (graft) — JS adds .theme-switching before flipping
   data-theme, removes it ~300ms after. Skipped when reduced motion. */
html.theme-switching,
html.theme-switching *,
html.theme-switching *::before,
html.theme-switching *::after {
  transition: background-color var(--dur-base) var(--ease-in-out),
              color var(--dur-base) var(--ease-in-out),
              border-color var(--dur-base) var(--ease-in-out) !important;
}

/* Reduced motion — belt AND suspenders (graft: zeroed duration tokens make
   future motion compliant by construction; global block catches strays).
   JS mirrors with matchMedia('(prefers-reduced-motion: reduce)') for
   count-up / draw-in / ticks / crossfade. */
@media (prefers-reduced-motion: reduce) {
  :root {
    --dur-instant: 0.01ms; --dur-fast: 0.01ms; --dur-base: 0.01ms;
    --dur-slow: 0.01ms; --dur-tick: 0.01ms; --dur-draw: 0.01ms;
  }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
    /* Delays must die with durations: a `backwards`-filled entrance or a
       staggered reveal would otherwise sit in its invisible FROM state for
       the whole delay. Required by the landing stagger + terminal settle. */
    animation-delay: 0ms !important;
    transition-delay: 0ms !important;
  }
}

* {
  box-sizing: border-box;
}

html,
body {
  margin: 0;
  min-height: 100%;
  height: 100%;
}

body {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  font-family: var(--font-ui);
  color: var(--text);
  background: var(--surface-0);
  font-variant-numeric: tabular-nums;
  line-height: 1.4;
  position: relative;
}

body.modal-open {
  overflow: hidden;
}

/* Landing/app backdrop (§4): 24px dot grid — GPU-cheap, edge-masked, no
   fixed-blur jank on mobile Safari. */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  opacity: 0.5;
  background-image: radial-gradient(var(--line) 1px, transparent 1px);
  background-size: 24px 24px;
  mask-image: radial-gradient(circle at center, rgba(0, 0, 0, 0.92), rgba(0, 0, 0, 0.52) 66%, transparent 100%);
}

/* Signature rule (§3): numerals never jitter — tabular figures everywhere a
   number can tick. */
.metric-value,
td,
.live-cell,
.calendar-cell {
  font-feature-settings: "tnum";
}

.app-layout {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: 280px minmax(0, 1fr);
  gap: 18px;
  width: min(1420px, 96vw);
  margin: 18px auto;
}

/* .is-guest (demo mode) joins .is-authenticated/.is-preview everywhere the CSS
   decides "the app shell is on screen". body.demo-signup is the one exception:
   a demo visitor reaching for the account form gets the landing back WITHOUT
   the demo being torn down, so nothing they logged is lost. */
body:not(.is-authenticated):not(.is-preview):not(.is-guest) .app-layout,
body.demo-signup .app-layout {
  display: none;
}

body.is-authenticated .auth-shell,
body.is-preview .auth-shell,
body.is-guest:not(.demo-signup) .auth-shell {
  display: none;
}

.auth-shell {
  position: relative;
  z-index: 1;
  width: min(1280px, 96vw);
  flex: 1 0 auto;
  margin: 0 auto;
  padding: 6px 24px 8px;
  border: 0;
  border-radius: 0;
  background: transparent;
  box-shadow: none;
  overflow: visible;
  animation: fadeUp var(--dur-slow) var(--ease-out);
  display: flex;
  flex-direction: column;
  justify-content: center;
}

/* live-pulse (graft): the only infinite animation in the product; the global
   reduced-motion block freezes it into a static dot. */
.live-pulse-dot {
  display: inline-block;
  width: 6px;
  height: 6px;
  flex: 0 0 6px;
  border-radius: 50%;
  background: var(--pnl-pos);
  animation: livePulse 2s var(--ease-in-out) infinite;
}

@keyframes livePulse {
  0%,
  100% {
    opacity: 1;
  }
  50% {
    opacity: 0.3;
  }
}

/* Hero CTAs: the primary button is the single accent use above the fold;
   the secondary stays a hairline .btn. */

/* Honest feed tag — replaces the unbacked "Verified" pill. */

/* Terminal tape: hairline-separated rows inside one bordered track. */

/* Direction tag — Long/Short is money-adjacent, so it may wear P&L color
   (shared with the in-progress cards on the journal action bar). */

/* Modal treatment (§4): flat dim overlay (no blur), panel fades and settles
   from scale(0.98). The public trades board keeps ticking underneath. */
.auth-overlay {
  position: fixed;
  inset: 0;
  z-index: var(--z-overlay);
  background: rgba(4, 5, 8, 0.65);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity var(--dur-base) var(--ease-out), visibility 0s linear var(--dur-base);
}

[data-theme="light"] .auth-overlay {
  background: rgba(23, 26, 33, 0.35);
}

.auth-panel {
  position: fixed;
  top: 50%;
  left: 50%;
  z-index: var(--z-modal);
  transform: translate(-50%, -50%) scale(0.98);
  width: min(90vw, 400px);
  max-height: min(82vh, 680px);
  overflow-y: auto;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition:
    opacity var(--dur-base) var(--ease-out),
    transform var(--dur-base) var(--ease-out),
    visibility 0s linear var(--dur-base);
}

.auth-shell.auth-panel-visible .auth-overlay {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transition: opacity var(--dur-base) var(--ease-out), visibility 0s;
}

.auth-shell.auth-panel-visible .auth-panel {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transform: translate(-50%, -50%) scale(1);
  transition:
    opacity var(--dur-base) var(--ease-out),
    transform var(--dur-base) var(--ease-out),
    visibility 0s;
}

.sidebar {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  padding: var(--space-4);
  border: 1px solid var(--line);
  border-radius: var(--radius-xl);
  background: var(--surface-2);
  box-shadow: var(--edge-highlight);
  min-height: calc(100vh - 36px);
  position: sticky;
  top: 18px;
  z-index: var(--z-nav);
}

.sidebar-header {
  display: block;
}

.brand-mark {
  margin: 6px 0 0;
  display: inline-flex;
  align-items: baseline;
  gap: 0.34rem;
  font: 700 clamp(1.45rem, 3vw, 2rem)/0.98 var(--font-ui);
  letter-spacing: -0.05em;
}

.brand-word-accent {
  color: var(--accent);
}

.brand-word-primary {
  color: var(--text);
}

h1 {
  margin: 10px 0 8px;
  font-size: 1.6rem;
  line-height: 1.1;
}

.nav-toggle {
  display: none;
  align-items: center;
  gap: var(--space-2);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-md);
  background: var(--surface-2);
  color: var(--text);
  padding: 8px 10px;
  cursor: pointer;
  font: 600 var(--fs-label)/1.2 var(--font-mono);
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

.nav-toggle.is-open {
  border-color: var(--accent);
  background: var(--surface-3);
}

.nav-toggle-bars,
.nav-toggle-bars::before,
.nav-toggle-bars::after {
  display: block;
  width: 14px;
  height: 2px;
  background: var(--text-soft);
  border-radius: var(--radius-pill);
  content: "";
}

.nav-toggle-bars {
  position: relative;
}

.nav-toggle-bars::before {
  position: absolute;
  top: -4px;
  left: 0;
}

.nav-toggle-bars::after {
  position: absolute;
  top: 4px;
  left: 0;
}

.main-nav {
  display: grid;
  gap: var(--space-1);
}

/* Nav buttons (§4): mono uppercase, quiet by default; the active state is an
   accent underline rail (desktop) or a 2px inset left rail (sidebar) — never
   a filled pill. */
.nav-btn {
  border: 0;
  border-radius: var(--radius-md);
  background: transparent;
  color: var(--text-soft);
  padding: 8px 12px;
  text-align: left;
  cursor: pointer;
  font: 600 var(--fs-label)/1.2 var(--font-mono);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  transition: color var(--dur-fast) var(--ease-out), background-color var(--dur-fast) var(--ease-out);
}

.nav-btn:hover {
  background: var(--surface-3);
  color: var(--text);
}

.nav-btn.is-active {
  color: var(--text);
  box-shadow: inset 0 -2px 0 var(--accent);
  border-radius: var(--radius-md) var(--radius-md) 0 0;
}

.main-nav .nav-btn.is-active {
  box-shadow: inset 2px 0 0 var(--accent);
  border-radius: var(--radius-md);
}

.sidebar-foot {
  margin-top: auto;
  display: grid;
  gap: 10px;
}

.sidebar-foot p {
  margin: 0;
  font: 500 var(--fs-label)/1.4 var(--font-mono);
  color: var(--text-soft);
}

.auth-panel {
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-xl);
  background: var(--surface-2);
  padding: var(--space-5);
  display: grid;
  gap: 10px;
  box-shadow: var(--shadow-modal);
}

.auth-panel[hidden] {
  display: none !important;
}

.auth-status:empty,
#authMessage:empty {
  display: none;
}

.auth-title {
  margin: 0;
  color: var(--text);
  font: 700 var(--fs-lg)/1.2 var(--font-ui);
  letter-spacing: -0.01em;
}

.auth-status {
  margin: 0;
  color: var(--text-soft);
  font: 600 var(--fs-label)/1.3 var(--font-mono);
}

.auth-status.is-on {
  color: var(--accent);
}

.auth-status.is-off {
  color: var(--warn);
}

.auth-status:empty {
  display: none;
}

.auth-copy {
  margin: 0;
  color: var(--text-soft);
  font-size: 0.86rem;
  line-height: 1.42;
}

#resetPasswordView {
  display: none;
  gap: 12px;
}

#resetPasswordView.is-visible {
  display: grid;
}

#resetPasswordView[hidden] {
  display: none !important;
}

.auth-form-actions {
  display: grid;
  grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr);
  gap: 10px;
}

.auth-form-actions .btn {
  width: 100%;
  flex: none;
}

.auth-link-btn {
  justify-self: flex-start;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--text-soft);
  cursor: pointer;
  font: 500 var(--fs-label)/1.35 var(--font-mono);
  letter-spacing: 0.02em;
}

.auth-link-btn:hover {
  color: var(--text);
  text-decoration: underline;
}

kbd {
  background: var(--surface-inset);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  padding: 2px 6px;
  font: 600 var(--fs-micro)/1.2 var(--font-mono);
  color: var(--text);
}

.content {
  display: grid;
  gap: 14px;
  align-content: start;
}

body.is-authenticated .auth-panel {
  display: none !important;
}

.brand-block .brand-mark {
  font-size: 1.48rem;
}

.nav-logout-mobile {
  width: 100%;
}

.view {
  display: none;
  gap: 14px;
}

.view.is-active {
  display: grid;
  animation: viewEnter var(--dur-base) var(--ease-out);
}

.view-head {
  padding: var(--space-4) var(--space-5);
  border: 1px solid var(--line);
  border-radius: var(--radius-xl);
  background: var(--surface-1);
  box-shadow: var(--edge-highlight);
}

.view-head h2 {
  margin: 0;
  font: 700 var(--fs-h2)/1.2 var(--font-ui);
  letter-spacing: -0.01em;
}

.view-head p {
  margin: 6px 0 0;
  color: var(--text-soft);
  font-size: var(--fs-body);
}

.metric-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: var(--space-3);
}

.metric-grid.compact {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

.metric-card {
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--surface-1);
  padding: var(--space-4);
  min-height: 88px;
  display: grid;
  align-content: center;
  box-shadow: var(--edge-highlight);
}

/* Money metrics: signed left hairline stamped by JS (.is-pos/.is-neg). */
.metric-card.is-pos {
  border-left: 2px solid var(--pnl-pos-line);
}

.metric-card.is-neg {
  border-left: 2px solid var(--pnl-neg-line);
}

/* Balance card only: --pl-intensity (|todayPnl| / dailyMaxLoss, clamped 0-1)
   widens the hairline 2px -> 4px and deepens its opacity. Static token color
   above is the non-color-mix fallback. */
.metric-card-balance.is-pos,
.metric-card-balance.is-neg {
  border-left-width: calc(2px + var(--pl-intensity) * 2px);
}

.metric-card-balance.is-pos {
  border-left-color: var(--pnl-pos-line);
  border-left-color: color-mix(in srgb, var(--pnl-pos) calc(30% + var(--pl-intensity) * 55%), transparent);
}

.metric-card-balance.is-neg {
  border-left-color: var(--pnl-neg-line);
  border-left-color: color-mix(in srgb, var(--pnl-neg) calc(30% + var(--pl-intensity) * 55%), transparent);
}

.metric-label {
  margin: 0;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font: 600 var(--fs-micro)/1.2 var(--font-mono);
  color: var(--text-faint);
}

.metric-value {
  margin: 7px 0 0;
  font: 700 var(--fs-metric)/1.15 var(--font-mono);
}

.metric-delta {
  justify-self: start;
  margin-top: var(--space-2);
  padding: 2px 6px;
  border-radius: var(--radius-sm);
  background: var(--surface-inset);
  color: var(--text-soft);
  font: 600 var(--fs-micro)/1.2 var(--font-mono);
  letter-spacing: 0.02em;
}

.metric-delta.is-pos {
  background: var(--pnl-pos-soft);
  color: var(--pnl-pos);
}

.metric-delta.is-neg {
  background: var(--pnl-neg-soft);
  color: var(--pnl-neg);
}

/* display:grid on these would defeat the hidden attribute (author rules beat
   the UA [hidden] rule) — JS toggles hidden on all three. */
.metric-grid[hidden],
.dashboard-empty[hidden] {
  display: none;
}

/* Empty state (§4 graft): replaces the metric-card wall when zero trades. */
.dashboard-empty {
  display: grid;
  justify-items: center;
  gap: var(--space-3);
  padding: var(--space-12) var(--space-5);
  border: 1px solid var(--line);
  border-radius: var(--radius-xl);
  background: var(--surface-1);
  text-align: center;
}

.dashboard-empty svg {
  width: 48px;
  height: 48px;
  color: var(--text-faint);
}

.dashboard-empty h3 {
  margin: 0;
  font: 600 var(--fs-lg)/1.3 var(--font-ui);
}

.dashboard-empty p {
  margin: 0;
  max-width: 380px;
  color: var(--text-soft);
  font-size: var(--fs-body);
}

.panel-grid {
  display: grid;
  gap: 12px;
}

.panel-grid-analytics {
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
}

.panel-grid-two {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

/* Panels: one recipe — flat surface, hairline, token radius. */
.panel {
  border: 1px solid var(--line);
  border-radius: var(--radius-xl);
  background: var(--surface-1);
  padding: var(--space-5);
  box-shadow: var(--edge-highlight);
}

.panel-head {
  margin-bottom: var(--space-3);
}

.panel-head h3 {
  margin: 0;
  font: 600 var(--fs-md)/1.3 var(--font-ui);
}

.panel-head p {
  margin: 4px 0 0;
  color: var(--text-soft);
  font-size: var(--fs-body);
}

.panel-head-inline {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
}

/* Chart wells: a lit stage rather than a flat inset. The radial wash sits
   behind the canvas bitmap (which is drawn transparent), so it reads as
   atmosphere under the curve without the renderer knowing about it. */
.panel-chart canvas {
  display: block;
  width: 100%;
  height: auto;
  border-radius: var(--radius-lg);
  border: 1px solid var(--line);
  background:
    radial-gradient(130% 88% at 50% 0%, var(--chart-canvas-glow) 0%, transparent 64%),
    linear-gradient(180deg, var(--chart-canvas-top) 0%, var(--chart-canvas-bottom) 100%);
  box-shadow:
    inset 0 1px 0 var(--chart-highlight),
    0 14px 30px -22px var(--chart-canvas-shadow);
  transition: border-color var(--dur-base) var(--ease-out), box-shadow var(--dur-base) var(--ease-out);
}

.panel-chart:hover canvas {
  border-color: var(--line-strong);
  box-shadow:
    inset 0 1px 0 var(--chart-highlight),
    0 18px 38px -22px var(--chart-canvas-shadow);
}

/* Crosshair only where a crosshair actually appears (the two line charts);
   bar and radar panels keep the default cursor. */
#equityChart,
#drawdownChart {
  cursor: crosshair;
}

.panel-strategy-performance {
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
}

.strategy-performance-toolbar {
  display: grid;
  gap: 10px;
  justify-items: end;
}

.strategy-toggle-group {
  display: inline-grid;
  grid-auto-flow: column;
  gap: 2px;
  padding: 2px;
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--surface-inset);
}

.strategy-toggle-btn {
  min-height: 32px;
  padding: 0 12px;
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--text-soft);
  font: 600 var(--fs-label)/1 var(--font-mono);
  cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}

.strategy-toggle-btn:hover {
  color: var(--text);
}

.strategy-toggle-btn.is-active {
  background: var(--surface-3);
  color: var(--text);
}

.panel-trader-score {
  display: grid;
  grid-template-rows: auto minmax(0, 1fr) auto;
}

.trader-score-foot {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--space-4);
  align-items: end;
  margin-top: var(--space-2);
  padding-top: var(--space-3);
  border-top: 1px solid var(--line);
}

.trader-score-value-wrap {
  display: grid;
  gap: 6px;
}

.trader-score-value {
  margin: 0;
  font: 700 var(--fs-metric)/1 var(--font-mono);
  font-feature-settings: "tnum";
  color: var(--text);
}

.trader-score-caption {
  margin: 0;
  color: var(--text-soft);
  font-size: var(--fs-body);
  line-height: 1.45;
}

label {
  display: grid;
  gap: 6px;
  margin-bottom: 11px;
  color: var(--text-soft);
  font: 600 var(--fs-label)/1.25 var(--font-mono);
}

input,
select,
textarea,
button {
  font-family: var(--font-mono);
}

button,
.btn,
.mini-btn,
.nav-btn,
.nav-toggle {
  min-height: 42px;
}

/* Token contract: text-entry surfaces always sit on --surface-inset (opaque).
   Focus feedback is instant — no transition. */
input,
select,
textarea {
  width: 100%;
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--surface-inset);
  color: var(--text);
  font-size: var(--fs-body);
  font-weight: 400;
  padding: 9px 11px;
}

textarea {
  resize: vertical;
}

input:focus,
select:focus,
textarea:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: var(--focus-ring);
}

.compact-form {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
}

.bulk-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
}

#bulkInput {
  min-height: 170px;
  font-family: var(--font-mono);
  font-size: var(--fs-label);
}

.compact-form .form-actions {
  grid-column: 1 / -1;
}

.form-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 10px;
}

#trade-entry .panel {
  padding: 18px;
}

#trade-entry .panel-head {
  margin-bottom: 14px;
}

#trade-entry .view-head {
  padding: 16px 18px;
}

#trade-entry .form-grid {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}

#trade-entry .panel:first-of-type {
  max-width: 100%;
}

.trade-form-shell {
  display: grid;
  gap: 16px;
}

.trade-form-layout {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  align-items: start;
  gap: 14px;
}

/* Trade-entry sections are nested wells (§4): opaque inset surface. */
.trade-section {
  display: grid;
  gap: 12px;
  align-content: start;
  height: 100%;
  padding: 14px;
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--surface-inset);
}

.trade-section-head {
  display: grid;
  gap: 3px;
}

.trade-section-kicker {
  margin: 0;
  color: var(--text-faint);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font: 600 var(--fs-micro)/1.2 var(--font-mono);
}

.trade-section-head h3 {
  margin: 0;
  font-size: var(--fs-md);
}

/* 1f: the psychology section no longer hides behind a disclosure, so it needs
   the one line that says why it is not optional. */
.trade-section-note {
  margin: 2px 0 0;
  color: var(--text-soft);
  font: 400 var(--fs-label)/1.5 var(--font-ui);
}

.trade-grid {
  display: grid;
  gap: 10px;
}

.trade-grid label {
  margin-bottom: 0;
}

.trade-status-toggle {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  min-height: 42px;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--surface-1);
  font: 600 var(--fs-body)/1.3 var(--font-mono);
  color: var(--text);
}

.trade-status-toggle input {
  margin: 0;
  inline-size: 15px;
  block-size: 15px;
  accent-color: var(--accent);
}

/* Long/Short segmented control (graft): two buttons in one hairline track;
   active fill uses the P&L soft tokens — direction is a money statement.
   The native select stays in the DOM (hidden) as the form's source of truth. */
.direction-field {
  display: grid;
  gap: 6px;
  align-content: start;
}

.direction-field-label {
  color: var(--text-soft);
  font: 600 var(--fs-label)/1.25 var(--font-mono);
}

.direction-native {
  display: none;
}

.direction-toggle {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--surface-inset);
  overflow: hidden;
}

.direction-btn {
  appearance: none;
  border: 0;
  min-height: 40px;
  padding: 9px 11px;
  background: transparent;
  color: var(--text-soft);
  cursor: pointer;
  font: 600 var(--fs-label)/1.2 var(--font-mono);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  transition: background-color var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}

.direction-btn + .direction-btn {
  border-left: 1px solid var(--line);
}

.direction-btn:hover {
  color: var(--text);
}

.direction-btn:focus-visible {
  outline: none;
  box-shadow: inset 0 0 0 2px var(--accent);
}

.direction-btn.is-active[data-direction="Buy"] {
  background: var(--pnl-pos-soft);
  color: var(--pnl-pos);
}

.direction-btn.is-active[data-direction="Sell"] {
  background: var(--pnl-neg-soft);
  color: var(--pnl-neg);
}

/* Form-shake (graft): 4px horizontal, 3 cycles, offending field only. Under
   reduced motion JS skips the shake and .is-invalid carries a border flash. */
@keyframes formShake {
  0%,
  100% {
    transform: none;
  }
  16%,
  48%,
  80% {
    transform: translateX(-4px);
  }
  32%,
  64%,
  96% {
    transform: translateX(4px);
  }
}

.form-shake {
  animation: formShake 240ms var(--ease-out);
}

input.is-invalid,
select.is-invalid,
textarea.is-invalid {
  border-color: var(--pnl-neg);
  box-shadow: 0 0 0 1px var(--pnl-neg-line);
}

.trade-grid-context {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

.trade-grid-risk {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

.trade-grid-review {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

.trade-grid-record {
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
}

.setup-inline-group {
  align-self: end;
}

.setup-inline-controls {
  display: grid;
  grid-template-columns: minmax(160px, 220px) minmax(0, 1fr);
  gap: 8px;
  align-items: center;
}

.field-span-2 {
  grid-column: span 2;
}

.field-span-all {
  grid-column: 1 / -1;
}

.screenshot-preview {
  min-height: 88px;
  border: 1px dashed var(--line-strong);
  border-radius: var(--radius-md);
  background: var(--surface-1);
  display: grid;
  place-items: center;
  color: var(--text-soft);
  font-size: var(--fs-body);
  padding: 8px;
  overflow: hidden;
}

.screenshot-preview img {
  max-width: 100%;
  max-height: 140px;
  border-radius: var(--radius-sm);
}

.form-actions {
  display: flex;
  gap: 8px;
  margin-top: 10px;
  flex-wrap: wrap;
}

.split-wrap {
  justify-content: space-between;
}

.inline-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.journal-action-icon {
  display: inline-flex;
  width: 16px;
  height: 16px;
  flex: 0 0 16px;
}

.journal-action-icon svg {
  width: 16px;
  height: 16px;
  stroke: currentColor;
  stroke-width: 1.7;
  stroke-linecap: round;
  stroke-linejoin: round;
  fill: none;
}

.btn {
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-md);
  padding: 10px 16px;
  background: var(--surface-2);
  color: var(--text);
  cursor: pointer;
  font: 600 12.5px/1.2 var(--font-mono);
  transition: border-color var(--dur-fast) var(--ease-out), background-color var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}

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

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

.btn.primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--text-inverse);
}

.btn.primary:hover {
  background: var(--accent-strong);
  border-color: var(--accent-strong);
}

button:focus-visible,
.btn:focus-visible,
.mini-btn:focus-visible,
.nav-btn:focus-visible,
summary:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
}

/* Async buttons (§4): pending = dimmed + inert; label swapped by JS. */
.btn.is-pending,
button.is-pending {
  opacity: 0.6;
  pointer-events: none;
}

.form-message {
  margin: 10px 0 0;
  min-height: 1.2em;
  font: 500 var(--fs-label)/1.3 var(--font-mono);
  color: var(--text-soft);
}

.form-message.success {
  color: var(--pnl-pos);
}

.form-message.error {
  color: var(--pnl-neg);
}

/* Third tone, for the demo-mode nudges. Neither red (nothing failed — the
   trade really did save into the demo) nor green (it will not persist).
   Amber, matching the demo notice's own mark. */
.form-message.notice {
  color: var(--warn);
}

.panel-collapsible {
  padding: 0;
  overflow: hidden;
}

.panel-collapsible:not([open]) .panel-collapsible-body {
  display: none;
}

.panel-collapse-summary {
  list-style: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 16px;
  cursor: pointer;
}

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

.panel-collapse-toggle {
  flex: 0 0 auto;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  padding: 8px 10px;
  color: var(--text-soft);
  font: 600 var(--fs-micro)/1 var(--font-mono);
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

.panel-collapsible[open] .panel-collapse-toggle {
  color: var(--text);
}

.panel-collapsible[open] .panel-collapse-toggle .toggle-label-closed,
.panel-collapsible:not([open]) .panel-collapse-toggle .toggle-label-open {
  display: none;
}

.panel-collapsible-body {
  padding: 0 16px 16px;
}

/* Calendar (§4): hairline grid via 1px gaps on a --line background — no
   per-cell borders. Traded days are real buttons that filter the journal. */
.calendar-grid {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 1px;
  padding: 1px;
  margin-top: 10px;
  background: var(--line);
  border-radius: var(--radius-md);
  overflow: hidden;
}

.calendar-weekday {
  background: var(--surface-inset);
  color: var(--text-faint);
  text-align: center;
  font: 600 var(--fs-micro)/1.2 var(--font-mono);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  padding: 6px 4px;
}

.calendar-cell {
  appearance: none;
  min-height: 108px;
  border: 0;
  border-radius: 0;
  background: var(--surface-1);
  color: var(--text);
  padding: 10px;
  display: grid;
  align-content: start;
  gap: 6px;
  text-align: left;
  font: 500 var(--fs-data)/1.3 var(--font-mono);
}

/* Intensity ramp (graft): static soft-tint fallback first, then the
   --day-intensity color-mix ramp for browsers that support it. */
.calendar-cell-positive {
  background: var(--pnl-pos-soft);
  background: color-mix(in srgb, var(--pnl-pos) calc(var(--day-intensity, 0) * 18%), var(--surface-1));
}

.calendar-cell-negative {
  background: var(--pnl-neg-soft);
  background: color-mix(in srgb, var(--pnl-neg) calc(var(--day-intensity, 0) * 18%), var(--surface-1));
}

.calendar-cell-today {
  box-shadow: inset 0 0 0 1px var(--accent);
}

.calendar-cell-empty {
  min-height: 0;
  background: var(--surface-inset);
  padding: 0;
}

button.calendar-cell {
  cursor: pointer;
  transition: box-shadow var(--dur-fast) var(--ease-out);
}

button.calendar-cell:hover {
  box-shadow: inset 0 0 0 1px var(--line-strong);
}

button.calendar-cell.calendar-cell-today:hover {
  box-shadow: inset 0 0 0 1px var(--accent);
}

button.calendar-cell:focus-visible {
  outline: none;
  box-shadow: inset 0 0 0 2px var(--accent);
}

.calendar-cell-day {
  justify-self: end;
  color: var(--text-faint);
  font: 600 var(--fs-micro)/1 var(--font-mono);
}

.calendar-cell-pnl {
  font: 600 var(--fs-data)/1.2 var(--font-mono);
}

.calendar-cell-meta {
  color: var(--text-soft);
  font: 500 var(--fs-micro)/1.3 var(--font-mono);
}

/* Shown only in the <=760px agenda layout when the month has no trades. */
.calendar-agenda-note {
  display: none;
  background: var(--surface-1);
  color: var(--text-soft);
  padding: 12px;
  font: 500 var(--fs-label)/1.4 var(--font-mono);
}

.filter-grid {
  display: grid;
  gap: 10px;
  grid-template-columns: repeat(4, minmax(0, 1fr));
}

.table-wrap {
  margin-top: 10px;
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  overflow-x: auto;
}

table {
  width: 100%;
  min-width: 980px;
  border-collapse: collapse;
}

th,
td {
  padding: 8px 10px;
  border-bottom: 1px solid var(--line);
  text-align: left;
}

thead th {
  position: sticky;
  top: 0;
  z-index: 1;
  background: var(--surface-inset);
  color: var(--text-faint);
  text-transform: uppercase;
  letter-spacing: 0.07em;
  font: 600 var(--fs-micro)/1.2 var(--font-mono);
  border-bottom: 1px solid var(--line-strong);
}

td {
  font: 500 var(--fs-data)/1.4 var(--font-mono);
}

/* Numeric columns: right-aligned, tabular. */
th.num,
td.num {
  text-align: right;
}

tbody tr {
  transition: background-color var(--dur-fast) var(--ease-out);
}

tbody tr:hover {
  background: var(--surface-3);
}

.user-agent-cell {
  max-width: 360px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.empty-row td {
  color: var(--text-soft);
  text-align: center;
  padding: 20px 8px;
}

.pill {
  display: inline-block;
  border-radius: var(--radius-sm);
  padding: 3px 7px;
  font: 600 var(--fs-micro)/1.2 var(--font-mono);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  border: 1px solid var(--line-strong);
  color: var(--text-soft);
}

.pill-win {
  color: var(--pnl-pos);
  background: var(--pnl-pos-soft);
  border-color: var(--pnl-pos-line);
}

.pill-loss {
  color: var(--pnl-neg);
  background: var(--pnl-neg-soft);
  border-color: var(--pnl-neg-line);
}

.pill-be {
  color: var(--warn);
  background: var(--warn-soft);
  border-color: var(--warn);
  border-color: color-mix(in srgb, var(--warn) 38%, transparent);
}

.pnl-positive {
  color: var(--pnl-pos);
  font-weight: 700;
}

.pnl-negative {
  color: var(--pnl-neg);
  font-weight: 700;
}

.row-actions {
  white-space: nowrap;
}

.mini-btn {
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  color: var(--text);
  padding: 6px 8px;
  cursor: pointer;
  font: 600 var(--fs-micro)/1.2 var(--font-mono);
  transition: border-color var(--dur-fast) var(--ease-out), background-color var(--dur-fast) var(--ease-out);
}

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

.mini-btn + .mini-btn {
  margin-left: 6px;
}

/* Destructive: transparent until hover. */
.mini-btn.danger {
  border-color: transparent;
  background: transparent;
  color: var(--pnl-neg);
}

.mini-btn.danger:hover {
  background: var(--pnl-neg-soft);
  border-color: transparent;
}

.score-stack {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 8px;
  margin-bottom: 10px;
}

.score-item {
  background: var(--surface-inset);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  padding: var(--space-3);
}

.score-item p {
  margin: 0;
  color: var(--text-faint);
  font: 600 var(--fs-micro)/1.2 var(--font-mono);
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.score-item strong {
  display: block;
  margin-top: 7px;
  font: 700 var(--fs-lg)/1.2 var(--font-mono);
  font-feature-settings: "tnum";
}

.warning-list {
  margin: 0;
  padding-left: 18px;
  display: grid;
  gap: 6px;
}

.warning-list li {
  color: var(--pnl-neg);
}

.warning-list li.ok {
  color: var(--pnl-pos);
}

.tag-set {
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  padding: 12px;
  margin: 8px 0;
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 8px;
}

.tag-set legend {
  padding: 0 6px;
  color: var(--text-soft);
  font: 600 var(--fs-micro)/1.2 var(--font-mono);
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

/* Behavior tags: rectangular chips (§4 badges), not pills. */
.tag-set label {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin: 0;
  min-height: 36px;
  padding: 6px 10px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  background: var(--surface-inset);
  font-size: var(--fs-label);
  flex: 0 0 auto;
  min-width: fit-content;
  white-space: nowrap;
  line-height: 1.2;
  max-width: 100%;
}

.tag-set input {
  margin: 0;
  inline-size: 14px;
  block-size: 14px;
  accent-color: var(--accent);
}

.reflection-list {
  margin: 0;
  padding-left: 0;
  list-style: none;
  display: grid;
  gap: 9px;
  max-height: 580px;
  overflow: auto;
}

.reflection-list li {
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--surface-inset);
  padding: 10px;
}

.reflection-list h4 {
  margin: 0;
  font-size: 0.95rem;
}

.reflection-list p {
  margin: 6px 0 0;
  color: var(--text-soft);
  font-size: 0.86rem;
}

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

body.auth-locked .view {
  display: none !important;
}

.nav-btn:disabled {
  opacity: 0.52;
  cursor: not-allowed;
}

.nav-btn:disabled:hover {
  transform: none;
  border-color: var(--line);
}

.site-footer {
  width: min(1280px, 96vw);
  margin: 12px auto 16px;
  padding: 12px 14px;
  border: 0;
  border-radius: 0;
  background: transparent;
  color: var(--text-soft);
  box-shadow: none;
  font: 500 var(--fs-label)/1.45 var(--font-mono);
  text-align: center;
}

.trust-row {
  width: min(1420px, 96vw);
  margin: 0 auto 12px;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 10px;
}

.trust-item {
  padding: 16px 18px;
  border: 1px solid var(--clay-edge);
  border-radius: var(--radius-clay-md);
  background: var(--surface-1);
  box-shadow: var(--clay-soft);
  color: var(--text-soft);
  text-align: center;
  font: 600 var(--fs-label)/1.35 var(--font-mono);
  text-transform: lowercase;
}

body.is-authenticated .trust-row {
  display: none;
}

body.auth-locked .trust-row,
body.is-preview .trust-row,
body.is-guest:not(.demo-signup) .trust-row {
  display: none;
}

.footer-main,
.footer-donation {
  margin: 0;
}

.footer-main {
  color: var(--text-soft);
}

.footer-support {
  margin-top: 0;
}

.footer-support summary {
  cursor: pointer;
  color: var(--text-soft);
  list-style: none;
  display: inline-flex;
  justify-content: center;
}

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

.footer-support summary:hover {
  color: var(--text);
}

.footer-support-body {
  display: grid;
  gap: 4px;
  margin-top: 8px;
  justify-items: center;
}

.site-footer .footer-main {
  margin-top: 8px;
}

.site-footer code {
  display: inline-block;
  margin-left: 4px;
  padding: 2px 6px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  background: var(--surface-inset);
  color: var(--text);
  word-break: break-all;
}

@media (min-width: 1025px) {
  .app-layout {
    grid-template-columns: 1fr;
    width: min(1480px, 96vw);
  }

  .sidebar {
    display: none;
  }

}

@media (max-width: 1300px) {
  .metric-grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }

  .form-grid,
  .filter-grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }

  .trade-grid-context,
  .trade-grid-risk,
  .trade-grid-review {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

}

@media (max-width: 1024px) {
  .content,
  .view,
  .panel-grid {
    gap: 10px;
  }

  .panel-grid-analytics {
    grid-template-columns: 1fr;
  }

  .strategy-performance-toolbar {
    width: 100%;
    justify-items: start;
  }

  .auth-shell {
    width: min(100%, calc(100vw - 16px));
    margin: 0 auto;
    padding: 6px 18px 8px;
    border-radius: 0;
  }

  .trust-row {
    width: min(100%, calc(100vw - 12px));
  }

  .app-layout {
    grid-template-columns: 1fr;
    width: min(100%, calc(100vw - 12px));
    margin: 8px auto 18px;
  }

  .sidebar {
    min-height: auto;
    position: static;
    gap: 12px;
  }

  .sidebar-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 10px;
  }

  .brand-block {
    min-width: 0;
    flex: 1 1 auto;
  }

  .nav-toggle {
    display: inline-flex;
    align-self: center;
    justify-content: center;
    min-width: 92px;
    padding: 10px 12px;
  }

  .main-nav {
    grid-template-columns: 1fr;
    justify-items: center;
    max-height: 0;
    opacity: 0;
    visibility: hidden;
    overflow: hidden;
    pointer-events: none;
    margin-top: 0;
    padding-bottom: 0;
    transition:
      max-height 220ms ease,
      opacity 220ms ease,
      margin-top 220ms ease,
      padding-bottom 220ms ease;
  }

  .sidebar.nav-open .main-nav {
    max-height: 460px;
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
    margin-top: 8px;
    padding-bottom: 10px;
  }

  .sidebar-foot {
    max-height: 0;
    opacity: 0;
    visibility: hidden;
    overflow: hidden;
    pointer-events: none;
    margin-top: 0;
    transition:
      max-height 220ms ease,
      opacity 220ms ease,
      margin-top 220ms ease;
  }

  .sidebar.nav-open .sidebar-foot {
    max-height: 140px;
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
    margin-top: 14px;
  }

  .panel-grid-two,
  .score-stack {
    grid-template-columns: 1fr;
  }

  .metric-grid {
    gap: 8px;
  }

  .metric-card {
    min-height: 76px;
    padding: var(--space-3);
  }

  .metric-value {
    font-size: var(--fs-lg);
  }

  #trade-entry .form-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .trade-form-layout {
    gap: 12px;
  }

  .trade-section {
    padding: 12px;
  }

  .trade-grid-context,
  .trade-grid-risk,
  .trade-grid-review {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .trade-grid-record {
    grid-template-columns: 1fr;
  }

  .panel-head-inline,
  .panel-collapse-summary {
    flex-direction: column;
    align-items: stretch;
  }

  .trader-score-foot {
    grid-template-columns: 1fr;
    gap: 10px;
    align-items: start;
  }

  .auth-panel {
    order: 2;
    width: 100%;
    max-width: 560px;
    padding: 18px;
  }

  .shortcut-hint {
    display: none;
  }

  .bulk-grid {
    grid-template-columns: 1fr;
  }

  .site-footer {
    width: min(100%, calc(100vw - 12px));
    margin: 0 auto 16px;
    padding: 10px 12px;
  }
}

@media (max-width: 980px) and (min-width: 901px) {
  .table-wrap {
    overflow-x: auto;
  }

  table {
    min-width: 900px;
  }
}

@media (max-width: 900px) {
  .table-wrap {
    border: 0;
    background: transparent;
    overflow: visible;
  }

  table {
    min-width: 0;
    border-collapse: separate;
    border-spacing: 0 10px;
  }

  thead {
    display: none;
  }

  tbody,
  tr {
    display: block;
  }

  tbody tr {
    border: 1px solid var(--line);
    border-radius: var(--radius-md);
    background: var(--surface-1);
    box-shadow: var(--edge-highlight);
    padding: 8px 10px;
  }

  tbody tr:hover {
    background: var(--surface-1);
  }

  td {
    display: grid;
    grid-template-columns: minmax(96px, 42%) minmax(0, 1fr);
    gap: 10px;
    padding: 7px 0;
    border-bottom: 1px dashed var(--line);
  }

  td::before {
    content: attr(data-label);
    color: var(--text-faint);
    font: 600 var(--fs-micro)/1.2 var(--font-mono);
    letter-spacing: 0.04em;
    text-transform: uppercase;
    text-align: left;
  }

  td:last-child {
    border-bottom: 0;
  }

  .user-agent-cell {
    max-width: none;
    white-space: normal;
    word-break: break-word;
  }

  .row-actions {
    display: flex;
    justify-content: flex-end;
    gap: 8px;
    padding-top: 8px;
  }

  .row-actions::before,
  .empty-row td::before {
    content: none;
  }

  .mini-btn + .mini-btn {
    margin-left: 0;
  }

  .empty-row td {
    display: block;
    border-bottom: 0;
    text-align: center;
    padding: 12px 4px;
  }
}

@media (max-width: 760px) {
  .metric-grid.compact,
  .form-grid,
  .filter-grid,
  .compact-form,
  .bulk-grid,
  .main-nav {
    grid-template-columns: 1fr;
  }

  .metric-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .field-span-2,
  .field-span-all {
    grid-column: auto;
  }

  h1 {
    font-size: 1.25rem;
  }

  .brand-block .brand-mark {
    font-size: 1.16rem;
    gap: 0.2rem;
  }

  .nav-btn,
  .nav-logout-mobile {
    width: 100%;
    text-align: center;
  }

  .auth-shell {
    width: min(100%, calc(100vw - 16px));
    padding: 4px 12px 8px;
    border-radius: 0;
    display: flex;
    flex-direction: column;
    justify-content: center;
  }

  body:not(.auth-ready):not(.is-authenticated) .auth-panel,
  body:not(.auth-ready):not(.is-authenticated) #resetPasswordView {
    display: none !important;
  }

  .view-head,
  .panel,
  .sidebar {
    padding: 12px;
  }

  .metric-card {
    min-height: 72px;
    padding: 10px;
  }

  .metric-value {
    font-size: var(--fs-lg);
  }

  /* Agenda mode (§4): vertical list of traded days only. */
  .calendar-grid {
    grid-template-columns: 1fr;
  }

  .calendar-cell-empty,
  .calendar-cell:not(.calendar-cell-has-trades) {
    display: none;
  }

  .calendar-agenda-note {
    display: block;
  }

  .setup-inline-controls {
    grid-template-columns: 1fr;
  }

  .trade-form-shell {
    gap: 12px;
  }

  .trade-form-layout {
    grid-template-columns: 1fr;
    gap: 10px;
  }

  #trade-entry .form-grid {
    grid-template-columns: 1fr;
  }

  .trade-grid-context,
  .trade-grid-risk,
  .trade-grid-review,
  .trade-grid-record {
    grid-template-columns: 1fr;
  }

  .trade-section {
    height: auto;
    padding: 11px;
  }

  .trade-section-head {
    gap: 2px;
  }

  .trade-grid {
    gap: 9px;
  }

  .trade-status-toggle {
    width: 100%;
    min-height: 44px;
  }

  .screenshot-preview {
    min-height: 64px;
  }

  #trade-entry .form-actions {
    margin-top: 4px;
    justify-content: stretch;
  }

  #trade-entry .form-actions .btn {
    flex: 1 1 100%;
  }

  .calendar-weekday {
    display: none;
  }

  .calendar-cell {
    min-height: 0;
    padding: 8px 10px;
  }

  .calendar-cell-meta {
    font-size: var(--fs-micro);
    line-height: 1.25;
  }

  .auth-panel {
    padding: 16px;
  }

  .auth-title,
  .auth-status,
  .auth-copy {
    text-align: center;
  }

  .auth-form-actions {
    display: grid;
    grid-template-columns: 1fr;
  }

  .auth-panel .btn {
    width: 100%;
  }

  .auth-link-btn {
    margin-top: -2px;
    font-size: 0.82rem;
  }

  .split-wrap {
    justify-content: flex-start;
  }

  input,
  select,
  textarea {
    font-size: 16px;
  }

  .site-footer {
    font-size: 11px;
  }

  .trust-row {
    display: none;
  }

  .tag-set {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 8px;
    padding: 10px;
  }

  .tag-set label {
    min-width: 0;
    width: 100%;
    white-space: normal;
    overflow-wrap: anywhere;
    align-items: flex-start;
    padding: 8px 9px;
    font-size: var(--fs-label);
  }

  .tag-set input {
    margin-top: 2px;
  }

  .strategy-toggle-group {
    width: 100%;
  }

  .strategy-toggle-btn {
    width: 100%;
    padding-inline: 10px;
  }

  .nav-logout-mobile {
    margin-top: 6px;
    margin-bottom: 10px;
  }
}

@keyframes fadeUp {
  from {
    opacity: 0;
    transform: translateY(8px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

/* View enter — 4px rise + fade on activation; no exit animation. */
@keyframes viewEnter {
  from {
    opacity: 0;
    transform: translateY(4px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

/* pnl-tick — directional flash on live value change. Applied by app.js on
   the poll patch path; removed on animationend so each change replays. */
@keyframes tickUp {
  from {
    background-color: var(--pnl-pos-soft);
  }
  to {
    background-color: transparent;
  }
}

@keyframes tickDown {
  from {
    background-color: var(--pnl-neg-soft);
  }
  to {
    background-color: transparent;
  }
}

.tick-up {
  animation: tickUp var(--dur-tick) var(--ease-out);
}

.tick-down {
  animation: tickDown var(--dur-tick) var(--ease-out);
}

/* Skeleton pulse — body.is-journal-loading is set while the server journal
   loads (auth restore / manual load); metric values and journal rows become
   pulsing blocks. Under reduced motion the zeroed tokens leave a static
   block. */
@keyframes skeletonPulse {
  0%,
  100% {
    opacity: 1;
  }
  50% {
    opacity: 0.45;
  }
}

body.is-journal-loading .metric-value,
body.is-journal-loading #tradesBody td {
  color: transparent !important;
  background: var(--surface-3);
  border-radius: var(--radius-sm);
  animation: skeletonPulse 1.2s var(--ease-in-out) infinite;
}

body.is-journal-loading .metric-value *,
body.is-journal-loading #tradesBody td * {
  visibility: hidden;
}

/* ==========================================================================
   Phase 6 — feature adds: risk strip, report panels support, sortable
   headers, score-info dialog, bulk-import honesty, close-at-market.
   ========================================================================== */

/* Balance card note chip: balanceOverride is set, so the number is manual
   and may disagree with the computed equity curve. */
.metric-note {
  justify-self: start;
  margin-top: var(--space-2);
  padding: 2px 6px;
  border-radius: var(--radius-sm);
  background: var(--warn-soft);
  color: var(--warn);
  font: 600 var(--fs-micro)/1.2 var(--font-mono);
  letter-spacing: 0.02em;
  cursor: help;
}

/* Risk-budget strip: today vs dailyMaxLoss, week vs weeklyMaxLoss.
   display:grid would defeat the hidden attribute — guard both levels. */
.risk-strip[hidden],
.risk-strip-item[hidden] {
  display: none;
}

.risk-strip {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: var(--space-3);
}

.risk-strip-item {
  display: grid;
  gap: 7px;
  padding: var(--space-3) var(--space-4);
  background: var(--surface-1);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  box-shadow: var(--edge-highlight);
}

.risk-strip-label {
  color: var(--text-faint);
  font: 600 var(--fs-micro)/1.2 var(--font-mono);
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.risk-strip-track {
  height: 4px;
  border-radius: var(--radius-pill);
  background: var(--surface-inset);
  border: 1px solid var(--line);
  overflow: hidden;
}

.risk-strip-fill {
  display: block;
  height: 100%;
  width: 0;
  background: var(--line-strong);
  transition: width var(--dur-base) var(--ease-out), background-color var(--dur-fast) var(--ease-out);
}

.risk-strip-value {
  color: var(--text-soft);
  font: 500 var(--fs-label)/1.3 var(--font-mono);
}

.risk-strip-item.is-warn .risk-strip-fill {
  background: var(--warn);
}

.risk-strip-item.is-warn .risk-strip-value {
  color: var(--warn);
}

.risk-strip-item.is-breach {
  border-color: var(--pnl-neg-line);
}

.risk-strip-item.is-breach .risk-strip-fill {
  background: var(--pnl-neg);
}

.risk-strip-item.is-breach .risk-strip-value {
  color: var(--pnl-neg);
}

/* Sortable journal headers: mono arrow indicator via aria-sort. */
th[data-sort] {
  cursor: pointer;
  user-select: none;
}

th[data-sort]:hover,
th[data-sort]:focus-visible {
  color: var(--text-soft);
}

th[data-sort]:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
}

th[aria-sort="ascending"]::after {
  content: " \2191";
}

th[aria-sort="descending"]::after {
  content: " \2193";
}

th[aria-sort] {
  color: var(--text);
}

/* Score-formula info popover (native <dialog>). */
.info-btn {
  flex-shrink: 0;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--text-faint);
  padding: 3px 8px;
  cursor: pointer;
  font: 600 var(--fs-label)/1.2 var(--font-mono);
  transition: color var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out);
}

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

.info-btn:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
}

.score-info-dialog {
  width: min(560px, calc(100vw - 32px));
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-xl);
  background: var(--surface-2);
  color: var(--text);
  padding: var(--space-5);
  box-shadow: var(--shadow-modal);
}

.score-info-dialog::backdrop {
  background: rgba(4, 5, 8, 0.65);
}

[data-theme="light"] .score-info-dialog::backdrop {
  background: rgba(23, 26, 33, 0.35);
}

.score-info-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-3);
  margin-bottom: var(--space-3);
}

.score-info-head h3 {
  margin: 0;
  font: 600 var(--fs-md)/1.3 var(--font-ui);
}

.score-info-list {
  margin: 0;
  display: grid;
  gap: var(--space-2);
}

.score-info-list dt {
  color: var(--text-faint);
  font: 600 var(--fs-micro)/1.2 var(--font-mono);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  padding-top: var(--space-2);
  border-top: 1px solid var(--line);
}

.score-info-list dt:first-child {
  padding-top: 0;
  border-top: 0;
}

.score-info-list dd {
  margin: 0;
  color: var(--text-soft);
  font-size: var(--fs-body);
  line-height: 1.55;
}

/* Bulk-import preview: values the paste did not provide are flagged, never
   fabricated. */
.bulk-missing {
  color: var(--text-faint);
}

/* Close-at-market affordances. */
.mini-btn-close {
  color: var(--accent);
}

/* ==========================================================================
   DASHBOARD ELEVATION — Command Bento
   Replaces the uniform 12-card wall with a three-tier hierarchy:
   deck (balance hero + risk meters) → edge quad → dense rail, then a
   12-column chart bento. Entrance choreography is a single staggered rise;
   the global reduced-motion block above neutralizes all of it.
   ========================================================================== */

@keyframes dashReveal {
  from {
    opacity: 0;
    transform: translateY(10px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

.dash-reveal {
  animation: dashReveal var(--dur-slow) var(--ease-out) backwards;
  animation-delay: calc(var(--i, 0) * 45ms);
}

/* Scroll-revealed panels (JS adds data-reveal + .is-revealed via
   IntersectionObserver; under reduced motion JS reveals immediately). */
[data-reveal] {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity var(--dur-slow) var(--ease-out), transform var(--dur-slow) var(--ease-out);
}

[data-reveal].is-revealed {
  opacity: 1;
  transform: none;
}

.view-kicker {
  margin: 0 0 6px;
  color: var(--text-faint);
  text-transform: uppercase;
  letter-spacing: 0.13em;
  font: 600 var(--fs-micro)/1.2 var(--font-mono);
}

.view-head-support {
  margin: 0;
  max-width: 46ch;
  color: var(--text-soft);
  font-size: var(--fs-body);
}

.dash-stats {
  display: grid;
  gap: var(--space-3);
}

.dash-stats[hidden] {
  display: none;
}

/* --- Tier 1: command deck ------------------------------------------------ */
.dash-deck {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  gap: var(--space-3);
}

.dash-hero {
  grid-column: span 8;
  position: relative;
  overflow: hidden;
  min-height: 208px;
  padding: var(--space-5) var(--space-5) 0;
  align-content: start;
  border-radius: var(--radius-xl);
}

.dash-hero-top {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
}

.dash-hero-value {
  position: relative;
  z-index: 1;
  margin: var(--space-2) 0 0;
  font-size: clamp(38px, 4.6vw, 58px);
  line-height: 1.02;
  letter-spacing: -0.025em;
}

.dash-hero-meta {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
  margin-top: var(--space-3);
  min-height: 22px;
}

.dash-hero-meta .metric-delta {
  margin-top: 0;
}

/* Today's realized P&L — money tone, mono, sits beside the delta chip. */
.dash-hero-today {
  padding: 2px 8px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  color: var(--text-soft);
  font: 600 var(--fs-micro)/1.4 var(--font-mono);
  letter-spacing: 0.02em;
}

.dash-hero-today[hidden] {
  display: none;
}

.dash-hero-today.is-pos {
  border-color: var(--pnl-pos-line);
  color: var(--pnl-pos);
}

.dash-hero-today.is-neg {
  border-color: var(--pnl-neg-line);
  color: var(--pnl-neg);
}

/* Equity sparkline bleeds to the card edges behind the numerals. */
.dash-spark-wrap {
  position: absolute;
  inset: auto 0 0 0;
  height: 96px;
  pointer-events: none;
}

.dash-spark {
  display: block;
  width: 100%;
  height: 100%;
}

/* --- Risk meters --------------------------------------------------------- */
.dash-risk {
  grid-column: span 4;
  align-content: start;
  gap: var(--space-3);
  padding: var(--space-5);
  background: var(--surface-1);
  border: 1px solid var(--line);
  border-radius: var(--radius-xl);
  box-shadow: var(--edge-highlight);
}

.dash-risk-kicker {
  margin: 0;
  color: var(--text-faint);
  text-transform: uppercase;
  letter-spacing: 0.13em;
  font: 600 var(--fs-micro)/1.2 var(--font-mono);
}

/* Inside the deck card the items lose their own frame — the card is the frame. */
.dash-risk .risk-strip-item {
  padding: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
  box-shadow: none;
  gap: var(--space-2);
}

.dash-risk .risk-strip-track {
  height: 6px;
}

.dash-risk .risk-strip-value {
  font-size: var(--fs-data);
  color: var(--text);
}

.dash-risk .risk-strip-item.is-breach {
  border: 0;
}

/* --- Tier 2: edge quad --------------------------------------------------- */
.dash-quad {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--space-3);
}

.dash-quad-card {
  min-height: 108px;
  padding: var(--space-4) var(--space-5);
  align-content: center;
}

.dash-quad-card .metric-value {
  font-size: clamp(24px, 2.3vw, 31px);
  letter-spacing: -0.015em;
}

/* --- Tier 3: dense rail -------------------------------------------------- */
.dash-rail {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  border: 1px solid var(--line);
  border-radius: var(--radius-xl);
  background: var(--surface-1);
  box-shadow: var(--edge-highlight);
  overflow: hidden;
}

.dash-rail-item {
  min-height: 0;
  padding: var(--space-4);
  border: 0;
  border-left: 2px solid transparent;
  border-radius: 0;
  background: transparent;
  box-shadow: none;
  align-content: start;
}

.dash-rail-item + .dash-rail-item {
  box-shadow: inset 1px 0 0 var(--line);
}

.dash-rail-item .metric-value {
  margin-top: 5px;
  font-size: 17px;
  letter-spacing: -0.01em;
}

.dash-rail-item .metric-delta {
  font-size: 10.5px;
  padding: 1px 5px;
}

/* --- Chart bento --------------------------------------------------------- */
.panel-grid-analytics {
  grid-template-columns: repeat(12, minmax(0, 1fr));
  gap: var(--space-3);
}

.panel-span-8 {
  grid-column: span 8;
}

.panel-span-4 {
  grid-column: span 4;
}

.panel-grid-bottom {
  grid-template-columns: repeat(12, minmax(0, 1fr));
  gap: var(--space-3);
}

.panel-grid-bottom > .panel-span-5 {
  grid-column: span 5;
}

.panel-grid-bottom > .panel-span-7 {
  grid-column: span 7;
}

/* --- Responsive ---------------------------------------------------------- */
@media (max-width: 1180px) {
  .dash-hero {
    grid-column: span 12;
    min-height: 190px;
  }

  .dash-risk {
    grid-column: span 12;
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  }

  .dash-risk-kicker {
    grid-column: 1 / -1;
  }

  .panel-grid-analytics,
  .panel-grid-bottom {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .panel-span-4,
  .panel-grid-bottom > .panel-span-5,
  .panel-grid-bottom > .panel-span-7 {
    grid-column: span 1;
  }

  .panel-span-8 {
    grid-column: span 2;
  }

  .dash-rail {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }

  .dash-rail-item:nth-child(4n + 1) {
    box-shadow: none;
  }

  .dash-rail-item:nth-child(n + 5) {
    box-shadow: inset 0 1px 0 var(--line), inset 1px 0 0 var(--line);
  }

  .dash-rail-item:nth-child(5) {
    box-shadow: inset 0 1px 0 var(--line);
  }
}

@media (max-width: 900px) {
  .dash-quad {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .panel-grid-analytics,
  .panel-grid-bottom {
    grid-template-columns: minmax(0, 1fr);
  }

  .panel-span-8,
  .panel-span-4,
  .panel-grid-bottom > .panel-span-5,
  .panel-grid-bottom > .panel-span-7 {
    grid-column: span 1;
  }
}

@media (max-width: 720px) {

  .dash-hero {
    min-height: 172px;
    padding: var(--space-4) var(--space-4) 0;
  }

  .dash-hero-value {
    font-size: clamp(32px, 10vw, 44px);
  }

  .dash-spark-wrap {
    height: 74px;
  }

  .dash-risk {
    padding: var(--space-4);
  }

  .dash-rail {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .dash-rail-item {
    padding: var(--space-3);
    box-shadow: inset 0 1px 0 var(--line), inset 1px 0 0 var(--line);
  }

  .dash-rail-item:nth-child(odd) {
    box-shadow: inset 0 1px 0 var(--line);
  }

  .dash-rail-item:nth-child(-n + 2) {
    box-shadow: inset 1px 0 0 var(--line);
  }

  .dash-rail-item:nth-child(1) {
    box-shadow: none;
  }
}

/* Rail captions (best/worst-day dates) + rail density: tertiary cells drop
   their delta chips so the rail stays a glance strip, not a third card wall. */
.metric-sub {
  margin-top: 3px;
  color: var(--text-faint);
  font: 500 var(--fs-micro)/1.3 var(--font-mono);
  letter-spacing: 0.02em;
}

.metric-sub[hidden] {
  display: none;
}

.dash-rail-item .metric-delta {
  display: none;
}

/* Risk meters: the remaining budget is the headline numeral; items spread to
   fill the deck card so it reads as a panel, not a label stack. */
.dash-risk {
  align-content: space-between;
  min-height: 208px;
}

.dash-risk .risk-strip-item {
  gap: var(--space-1);
}

.risk-strip-remain {
  font: 700 21px/1.15 var(--font-mono);
  font-feature-settings: "tnum";
  letter-spacing: -0.01em;
  color: var(--text);
}

.risk-strip-item.is-warn .risk-strip-remain {
  color: var(--warn);
}

.risk-strip-item.is-breach .risk-strip-remain {
  color: var(--pnl-neg);
}

.dash-risk .risk-strip-track {
  margin-top: var(--space-1);
}

@media (max-width: 1180px) {
  .dash-risk {
    min-height: 0;
  }
}

/* ==========================================================================
   LANDING (design-source/1d) — the tape is the pitch
   The logged-out shell is four bands stacked in one column:
     top bar → hero (claim + the real public tape + three facts) →
     what you get back → closing CTA.
   Everything is Clay V2: warm bone ground, violet accent, large radii, and
   the depth rule carried into the tape (raised row = the trade made money,
   sunk row = it lost). Colour and the result word carry the same state, so
   depth is never the only signal (WCAG 1.4.1).

   The auth panel and overlay stay position:fixed (defined far above), so the
   modal still opens over everything and the tape stays behind it.
   ========================================================================== */

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* The page dot-grid steps back: the landing is clay on bone now, and a grid
   showing through the shadows kills the moulded read. */
body:not(.is-authenticated):not(.is-preview):not(.is-guest)::before,
body.demo-signup::before {
  opacity: 0.22;
}

/* --text-soft and --text-faint were tuned against --surface-1 and
   --surface-inset. The landing's body copy sits on --surface-0 (the page
   ground), where both measure ~4.3:1 — under the 4.5:1 floor. One darker step
   of the same warm hue, scoped to the shell, takes it to 4.9:1 on the ground
   and 6.0:1 on the cards. Light only: dark already measures 7.3:1. */
[data-theme="light"] .auth-shell {
  --text-soft: #635a51;
  --text-faint: #635a51;
}

.auth-shell {
  width: min(1200px, 94vw);
  justify-content: flex-start;
  gap: var(--space-16);
  padding: 0 0 var(--space-16);
}

/* --- Top bar ------------------------------------------------------------- */
.lnd-topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  flex-wrap: wrap;
  padding: var(--space-5) 0 0;
}

.lnd-brand {
  display: flex;
  align-items: center;
  gap: 12px;
}

.lnd-mark {
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  border-radius: 14px;
  background: linear-gradient(180deg, var(--accent-strong) 0%, var(--accent) 100%);
  color: var(--text-inverse);
  font: 700 15px/1 var(--font-ui);
  letter-spacing: 0.01em;
  box-shadow: var(--clay-accent);
}

.lnd-brand-name {
  font: 700 19px/1 var(--font-ui);
  letter-spacing: -0.02em;
  color: var(--text);
}

.lnd-topbar-actions {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}

/* --- Buttons ------------------------------------------------------------- */
.lnd-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 46px;
  padding: 0 22px;
  border: 0;
  border-radius: var(--radius-pill);
  font: 600 13.5px/1 var(--font-ui);
  cursor: pointer;
  transition: transform var(--dur-squish) var(--ease-squish),
    box-shadow var(--dur-squish) var(--ease-squish);
}

.lnd-btn-quiet {
  background: var(--surface-1);
  color: var(--text);
  box-shadow: var(--clay-raised);
}

.lnd-btn-accent {
  min-height: 56px;
  padding: 0 28px;
  background: linear-gradient(180deg, var(--accent-strong) 0%, var(--accent) 100%);
  color: var(--text-inverse);
  font: 700 15px/1 var(--font-ui);
  box-shadow: var(--clay-accent);
}

.lnd-btn-lg {
  min-height: 62px;
  padding: 0 34px;
  font-size: 17px;
}

.lnd-btn:active {
  transform: translateY(2px) scale(0.99);
  box-shadow: var(--clay-pressed);
}

.lnd-btn-accent:active {
  background: var(--accent-strong);
  box-shadow: var(--clay-pressed);
}

/* The clay stacks out-specify the global button:focus-visible ring, so it is
   restated here together with the elevation it must not delete. */
.lnd-btn-quiet:focus-visible {
  box-shadow: var(--focus-ring), var(--clay-raised);
}

.lnd-btn-accent:focus-visible {
  box-shadow: var(--focus-ring), var(--clay-accent);
}

.lnd-textlink {
  appearance: none;
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0 8px;
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--text-soft);
  font: 600 12.5px/1.3 var(--font-ui);
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-color: var(--line-strong);
  cursor: pointer;
}

.lnd-textlink:hover {
  color: var(--text);
  text-decoration-color: var(--accent);
}

.lnd-textlink-inline {
  padding: 0;
  font-size: 12.5px;
}

/* --- Hero ---------------------------------------------------------------- */
.lnd-hero {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 500px);
  gap: var(--space-10);
  align-items: center;
  width: 100%;
  max-width: none;
  margin: 0;
}

.lnd-hero-copy {
  display: grid;
  gap: var(--space-5);
  justify-items: start;
  width: 100%;
  max-width: none;
  padding: 0;
  text-align: left;
}

/* Counted from the rows on the tape below — app.js hides it at zero rather
   than printing a nought, and the label names exactly what it counted. */
.lnd-hero-badge {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  margin: 0;
  padding: 9px 16px;
  border-radius: var(--radius-pill);
  background: var(--surface-1);
  color: var(--text-faint);
  font: 600 var(--fs-micro)/1 var(--font-mono);
  letter-spacing: 0.06em;
  box-shadow: var(--clay-soft), var(--edge-highlight);
}

.lnd-hero-badge[hidden] {
  display: none;
}

.lnd-hero-title {
  margin: 0;
  font: 700 clamp(2.5rem, 5.6vw, 4.75rem) / 1.02 var(--font-ui);
  letter-spacing: -0.045em;
  color: var(--text);
}

.lnd-hero-title-accent {
  color: var(--accent);
}

.lnd-hero-lede {
  margin: 0;
  max-width: 44ch;
  color: var(--text-soft);
  font: 400 clamp(1rem, 1.2vw, 1.125rem) / 1.55 var(--font-ui);
}

/* One real step of sign-up: the value is handed to the auth panel's
   identifier field, which accepts a username or an email. */
.lnd-hero-form {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  max-width: 500px;
  padding: 8px;
  border-radius: var(--radius-pill);
  background: var(--surface-1);
  box-shadow: var(--clay-float);
}

.lnd-hero-input {
  flex: 1 1 auto;
  width: auto;
  min-width: 0;
  min-height: 44px;
  padding: 0 20px;
  /* The global input recipe is a pressed well on --surface-inset; inside this
     pill it has to be nothing at all, so all three are reset explicitly. */
  border: 0;
  border-radius: var(--radius-pill);
  background: transparent;
  box-shadow: none;
  color: var(--text);
  font: 500 15px/1 var(--font-ui);
}

.lnd-hero-input::placeholder {
  color: var(--text-faint);
}

.lnd-hero-input:focus,
.lnd-hero-input:focus-visible {
  outline: none;
  background: transparent;
  box-shadow: var(--focus-ring);
}

.lnd-hero-fine {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 4px 10px;
  margin: 0;
  color: var(--text-faint);
  font: 400 13px/1.5 var(--font-ui);
}

/* --- Hero side: the live tape + three facts ------------------------------ */
.lnd-hero-side {
  display: grid;
  gap: var(--space-4);
  align-content: start;
  width: 100%;
}

.lnd-tape {
  padding: 8px;
  border-radius: var(--radius-clay-lg);
  background: var(--surface-1);
  box-shadow: var(--clay-float);
}

.lnd-tape-head {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 14px 18px 16px;
}

.lnd-tape-title {
  color: var(--text-faint);
  font: 600 var(--fs-micro)/1 var(--font-mono);
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

.lnd-tape-delay {
  margin-left: auto;
  color: var(--text-faint);
  font: 500 var(--fs-micro)/1 var(--font-mono);
}

.lnd-tape-list {
  display: grid;
  gap: 6px;
  padding: 8px;
  border-radius: var(--radius-clay-md);
  background: var(--surface-inset);
  box-shadow: var(--clay-pressed);
}

.lnd-tape-note {
  margin: 0;
  padding: 16px 18px 12px;
  color: var(--text-faint);
  font: 400 12.5px/1.45 var(--font-ui);
}

.lnd-tape-empty {
  margin: 0;
  padding: 18px 16px;
  color: var(--text-soft);
  font: 500 var(--fs-label)/1.4 var(--font-mono);
}

/* Depth as data. The row is raised when the trade made money and sunk when it
   lost — and the result word plus its money colour say the same thing, so the
   state never depends on seeing the shadow. */
.lnd-row {
  display: grid;
  grid-template-columns: minmax(72px, auto) minmax(0, 1fr) auto;
  align-items: center;
  gap: 12px;
  width: 100%;
  min-height: 48px;
  padding: 13px 16px;
  border: 0;
  border-radius: var(--radius-clay-sm);
  color: var(--text);
  text-align: left;
  cursor: pointer;
  transition: transform var(--dur-fast) var(--ease-out);
}

.lnd-row.is-win {
  background: var(--surface-1);
  box-shadow: var(--clay-raised);
}

.lnd-row.is-loss {
  background: var(--surface-inset);
  box-shadow: var(--clay-pressed);
}

.lnd-row.is-flat {
  background: var(--surface-1);
  box-shadow: var(--clay-soft), var(--edge-highlight);
}

.lnd-row:hover {
  transform: translateX(2px);
}

.lnd-row:focus-visible {
  box-shadow: var(--focus-ring);
}

.lnd-row-symbol {
  font: 700 14px/1 var(--font-ui);
  letter-spacing: -0.01em;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.lnd-row-meta {
  color: var(--text-faint);
  font: 500 11.5px/1.2 var(--font-mono);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The pill sits on --surface-2, not on the tinted --pnl-*-soft: at 10.5px the
   money colours measure 4.0:1 on their own soft tint over the sunk row, under
   the 4.5:1 body floor. On --surface-2 they measure 5.9 / 6.1 in light and
   5.2 / 8.0 in dark, and the money-coloured hairline keeps the tint reading. */
.lnd-row-result {
  padding: 5px 11px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-pill);
  background: var(--surface-2);
  font: 700 10.5px/1.3 var(--font-mono);
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.lnd-row.is-win .lnd-row-result {
  border-color: var(--pnl-pos-line);
  color: var(--pnl-pos);
}

.lnd-row.is-loss .lnd-row-result {
  border-color: var(--pnl-neg-line);
  color: var(--pnl-neg);
}

.lnd-row.is-flat .lnd-row-result {
  color: var(--text-soft);
}

/* Three facts, each checkable inside the product. */
.lnd-stats {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 14px;
  margin: 0;
}

.lnd-stat {
  display: flex;
  flex-direction: column-reverse;
  align-items: flex-start;
  padding: 20px;
  border-radius: var(--radius-clay-md);
  background: var(--surface-1);
  box-shadow: var(--clay-raised);
}

.lnd-stat-value {
  margin: 0;
  font: 700 clamp(1.5rem, 2.4vw, 1.875rem) / 1 var(--font-mono);
  font-feature-settings: "tnum";
  color: var(--text);
}

.lnd-stat-unit {
  font-size: 0.55em;
  color: var(--text-faint);
}

.lnd-stat-label {
  margin: 6px 0 0;
  color: var(--text-soft);
  font: 500 11.5px/1.3 var(--font-ui);
}

/* --- What you get back --------------------------------------------------- */
.lnd-value {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr);
  gap: var(--space-12);
  align-items: center;
  padding: var(--space-12);
  border-radius: var(--radius-clay-xl);
  background: var(--surface-1);
  box-shadow: var(--clay-raised);
}

.lnd-value-copy {
  display: grid;
  gap: var(--space-4);
  align-content: start;
}

.lnd-kicker {
  margin: 0;
  color: var(--text-faint);
  font: 600 var(--fs-micro)/1 var(--font-mono);
  letter-spacing: 0.16em;
  text-transform: uppercase;
}

.lnd-value-title {
  margin: 0;
  font: 700 clamp(1.75rem, 3.2vw, 2.875rem) / 1.08 var(--font-ui);
  letter-spacing: -0.035em;
  color: var(--text);
}

.lnd-value-lede {
  margin: 0;
  max-width: 46ch;
  color: var(--text-soft);
  font: 400 var(--fs-md)/1.6 var(--font-ui);
}

.lnd-ticks {
  display: grid;
  gap: 12px;
  margin: 6px 0 0;
  padding: 0;
  list-style: none;
}

.lnd-ticks li {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  color: var(--text);
  font: 400 14.5px/1.5 var(--font-ui);
}

.lnd-tick {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 24px;
  height: 24px;
  border-radius: 9px;
  background: linear-gradient(180deg, var(--accent-strong) 0%, var(--accent) 100%);
  color: var(--text-inverse);
  font: 700 12px/1 var(--font-ui);
  box-shadow: var(--clay-soft);
}

/* --- Expectancy panel — ILLUSTRATIVE ------------------------------------- */
/* A logged-out visitor has no journal, and the public feed carries no setup
   or P&L, so these bars cannot be anyone's real numbers. The Example tag, the
   accessible name and the footnote all say so. */
.lnd-expect {
  margin: 0;
  padding: 22px;
  border-radius: var(--radius-clay-lg);
  background: var(--surface-inset);
  box-shadow: var(--clay-pressed);
}

.lnd-expect-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 16px;
  padding: 0;
}

.lnd-expect-title {
  color: var(--text-faint);
  font: 600 var(--fs-micro)/1 var(--font-mono);
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

/* Raised on --surface-1, not on --warn-soft: this label is the honesty of the
   whole panel, so it has to clear 4.5:1 (it measures 5.2 light / 9.2 dark). */
.lnd-expect-tag {
  padding: 4px 10px;
  border-radius: var(--radius-pill);
  background: var(--surface-1);
  box-shadow: var(--clay-soft);
  color: var(--warn);
  font: 700 10.5px/1.3 var(--font-mono);
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.lnd-expect-rows {
  display: grid;
  gap: 12px;
}

.lnd-expect-row {
  display: grid;
  grid-template-columns: minmax(0, 118px) minmax(0, 1fr) 68px;
  align-items: center;
  gap: 12px;
}

.lnd-expect-name {
  font: 600 13px/1.2 var(--font-ui);
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
}

.lnd-expect-row.is-neg .lnd-expect-name {
  color: var(--pnl-neg);
}

.lnd-expect-track {
  display: flex;
  height: 26px;
  border-radius: var(--radius-pill);
  background: var(--line);
  overflow: hidden;
  box-shadow: inset 0 3px 7px -2px var(--clay-edge-strong);
}

.lnd-expect-row.is-neg .lnd-expect-track {
  justify-content: flex-end;
}

.lnd-expect-fill {
  display: block;
  width: var(--w, 0%);
  height: 100%;
  border-radius: var(--radius-pill);
  background: var(--pnl-pos);
  box-shadow: var(--edge-highlight);
}

.lnd-expect-row.is-neg .lnd-expect-fill {
  background: var(--pnl-neg);
}

.lnd-expect-value {
  text-align: right;
  font: 700 13px/1.2 var(--font-mono);
  font-feature-settings: "tnum";
  color: var(--pnl-pos);
}

.lnd-expect-row.is-neg .lnd-expect-value {
  color: var(--pnl-neg);
}

.lnd-expect-insight {
  margin: 18px 0 0;
  padding: 14px 16px;
  border-radius: var(--radius-clay-sm);
  background: var(--pnl-neg-soft);
  color: var(--text);
  font: 400 13px/1.45 var(--font-ui);
}

.lnd-expect-foot {
  margin: 12px 0 0;
  color: var(--text-faint);
  font: 400 12px/1.45 var(--font-ui);
}

/* --- Closing CTA --------------------------------------------------------- */
.lnd-close {
  display: grid;
  justify-items: center;
  gap: var(--space-5);
  padding: var(--space-12) var(--space-6);
  text-align: center;
}

.lnd-close-title {
  margin: 0;
  max-width: 20ch;
  font: 700 clamp(1.75rem, 3.1vw, 2.75rem) / 1.1 var(--font-ui);
  letter-spacing: -0.035em;
  color: var(--text);
}

.lnd-close-lede {
  margin: 0;
  max-width: 52ch;
  color: var(--text-soft);
  font: 400 var(--fs-md)/1.55 var(--font-ui);
}

.lnd-close-fine {
  margin: 0;
  color: var(--text-faint);
  font: 500 12.5px/1.4 var(--font-mono);
}

/* --- Reveal choreography ------------------------------------------------- */
/* Gated on body.auth-ready (stamped in init()) so a visitor with JS disabled
   never gets a permanently invisible section. Reduced motion is handled by
   the global kill block plus setupLandingReveals()'s own opt-out. */
body.auth-ready [data-landing-reveal] {
  opacity: 0;
  transform: translateY(18px);
  transition: opacity var(--dur-reveal) var(--ease-clay), transform var(--dur-reveal) var(--ease-clay);
}

body.auth-ready [data-landing-reveal].is-revealed {
  opacity: 1;
  transform: none;
}

/* --- Dev-only affordances ------------------------------------------------ */
/* These render only in local preview mode (app.js keeps them [hidden]
   otherwise). Deliberately small, muted and tagged so they can never read as
   a "use the app without an account" path next to the sign-up CTA. */
.dev-chip {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 5px 10px;
  border: 1px dashed var(--line-strong);
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--text-faint);
  cursor: pointer;
  font: 600 var(--fs-micro)/1.2 var(--font-mono);
  letter-spacing: 0.06em;
  opacity: 0.72;
  transition: opacity var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}

.dev-chip:hover {
  opacity: 1;
  color: var(--text-soft);
}

.dev-chip[hidden] {
  display: none;
}

.dev-chip-tag {
  padding: 1px 5px;
  border-radius: var(--radius-sm);
  background: var(--warn-soft);
  color: var(--warn);
  font-size: 10px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.nav-preview-landing {
  justify-content: center;
}

/* --- Landing responsive -------------------------------------------------- */
@media (max-width: 1080px) {
  .lnd-hero {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-8);
  }

  .lnd-value {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-8);
    padding: var(--space-8);
  }
}

@media (max-width: 760px) {
  .auth-shell {
    width: min(1200px, 100vw);
    gap: var(--space-10);
    padding: 0 var(--space-4) var(--space-12);
  }

  .lnd-topbar {
    padding-top: var(--space-4);
  }

  .lnd-hero-form {
    flex-wrap: wrap;
    border-radius: var(--radius-clay-md);
  }

  .lnd-hero-input {
    flex: 1 0 100%;
    padding: 0 14px;
  }

  .lnd-hero-form .lnd-btn-accent {
    flex: 1 0 100%;
  }

  .lnd-stats {
    gap: 10px;
  }

  .lnd-stat {
    padding: 14px 12px;
  }

  .lnd-value {
    padding: var(--space-6);
    border-radius: var(--radius-clay-lg);
  }

  .lnd-expect {
    padding: 16px;
  }

  .lnd-expect-row {
    grid-template-columns: minmax(0, 1fr) 62px;
    gap: 6px 10px;
  }

  .lnd-expect-track {
    grid-column: 1 / -1;
    height: 20px;
  }

  .lnd-close {
    padding: var(--space-8) 0;
  }

  .lnd-close .lnd-btn-lg {
    width: 100%;
  }
}

@media (max-width: 420px) {
  .lnd-brand-name {
    font-size: 17px;
  }

  /* Keep the bar on one line at 375px: brand + Log in + Start free. */
  .lnd-topbar-actions {
    gap: 4px;
  }

  .lnd-topbar .lnd-btn {
    padding: 0 16px;
    font-size: 12.5px;
  }

  .lnd-topbar .lnd-textlink {
    padding: 0 4px;
  }

  .lnd-row {
    grid-template-columns: minmax(0, 1fr) auto;
    gap: 4px 10px;
    padding: 11px 13px;
  }

  .lnd-row-meta {
    grid-column: 1 / -1;
  }
}

/* ==========================================================================
   PHASE C — DASHBOARD: DEPTH, TILT, CHOREOGRAPHY
   The bento stops being flat cards on a flat page. Three additions, in
   layers:
     1. surfaces  — every card is a lit gradient plane with an inner top
                    highlight, a deep drop shadow and an accent wash.
     2. 3D        — the deck/quad are perspective containers; [data-tilt]
                    cards rotate a couple of degrees toward the pointer and
                    carry a specular that tracks it. Bound in app.js behind
                    (hover: hover) and (pointer: fine) + reduced motion, so
                    touch and reduced-motion users get a still, flat card.
     3. motion    — a richer per-tier entrance, meters that fill from zero,
                    delta chips that pop in, a live pulse on open positions,
                    and a fatter directional flash on ticking numbers.
   Every id, [data-*] and class the JS queries is untouched; this file only
   adds rules and one attribute hook ([data-tilt]) that JS reads, never
   writes structure into.
   ========================================================================== */

/* --- 1. Surfaces --------------------------------------------------------- */
/* One recipe, applied everywhere a card or panel already exists so the whole
   product gains depth (Monthly Review's .metric-grid.compact included) —
   only the 3D and hover-lift below are dashboard-scoped. */
/* :not(.dash-rail-item) is load-bearing, not taste: the rail's cells are
   panes of one plane and their inset hairline separators (including the
   nth-child rewrites in the responsive blocks above) would be clobbered by a
   later box-shadow here. The rail itself takes the surface instead. */
.panel,
.metric-card:not(.dash-rail-item),
.dashboard-empty,
.dash-risk,
.dash-rail {
  background-image: linear-gradient(178deg, var(--deck-top) 0%, var(--deck-bottom) 100%);
  box-shadow: inset 0 1px 0 var(--deck-edge), var(--deck-shadow);
}

/* Accent wash at the top of the tall deck cards — the light source. */
.dash-hero,
.dash-risk {
  background-image:
    radial-gradient(120% 78% at 50% 0%, var(--deck-wash) 0%, transparent 62%),
    linear-gradient(178deg, var(--deck-top) 0%, var(--deck-bottom) 100%);
}

/* Hero accent edge: a left-anchored bloom in the day's P&L colour whose
   opacity rides --pl-intensity (the same scalar that widens the hairline).
   .is-pos / .is-neg are stamped by renderDashboardMetrics. */
.dash-hero {
  --hero-edge: transparent;
}

.dash-hero.is-pos {
  --hero-edge: var(--pnl-pos-soft);
}

.dash-hero.is-neg {
  --hero-edge: var(--pnl-neg-soft);
}

.dash-hero::before {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: min(38%, 280px);
  z-index: -1;
  pointer-events: none;
  opacity: calc(0.3 + var(--pl-intensity) * 0.7);
  background-image: linear-gradient(90deg, var(--hero-edge) 0%, transparent 82%);
  transition: opacity var(--dur-slow) var(--ease-out);
}

/* Money numerals get an atmospheric bloom rather than a heavier weight. */
.dash-hero-value.pnl-positive {
  text-shadow: 0 0 34px var(--chart-pos-glow);
}

.dash-hero-value.pnl-negative {
  text-shadow: 0 0 34px var(--chart-neg-glow);
}

/* Panel hover elevation — dashboard only, so the form-heavy views stay calm. */
#dashboard .panel {
  transition: transform var(--dur-base) var(--ease-out), box-shadow var(--dur-base) var(--ease-out),
    border-color var(--dur-base) var(--ease-out);
}

#dashboard .panel:hover {
  transform: translateY(-2px);
  border-color: var(--line-strong);
  box-shadow: inset 0 1px 0 var(--deck-edge), var(--deck-shadow-lift);
}

/* --- 2. 3D tilt ---------------------------------------------------------- */
/* Perspective lives on the tier containers so sibling cards share one vanishing
   point instead of each rotating in its own private space. */
.dash-deck,
.dash-quad {
  perspective: 1500px;
  perspective-origin: 50% 40%;
}

[data-tilt] {
  --tilt-x: 0deg;
  --tilt-y: 0deg;
  --px: 50%;
  --py: 0%;
  --sheen: 0;
  position: relative;
  /* isolate + a negative-z pseudo is what keeps the specular ON the surface
     and UNDER the numerals without touching a single child's position — the
     absolutely-positioned .dash-spark-wrap keeps its own layout. */
  isolation: isolate;
  transform-style: preserve-3d;
  transform: rotateX(var(--tilt-x)) rotateY(var(--tilt-y));
  transition: transform var(--dur-slow) var(--ease-out), box-shadow var(--dur-slow) var(--ease-out),
    border-color var(--dur-base) var(--ease-out);
}

[data-tilt]::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: inherit;
  pointer-events: none;
  opacity: var(--sheen);
  background-image: radial-gradient(
    440px circle at var(--px) var(--py),
    var(--deck-sheen) 0%,
    var(--deck-sheen-fade) 62%
  );
  transition: opacity var(--dur-base) var(--ease-out);
}

[data-tilt]:hover {
  border-color: var(--line-strong);
  box-shadow: inset 0 1px 0 var(--deck-edge), var(--deck-shadow-lift);
}

/* Promote only while the pointer is actually on the card: a permanent
   will-change would keep a dozen layers alive on mobile Safari for nothing. */
[data-tilt]:hover,
[data-tilt]:focus-within {
  will-change: transform;
}

/* Keyboard users read the numbers straight on. */
[data-tilt]:focus-within {
  --tilt-x: 0deg;
  --tilt-y: 0deg;
}

/* --- 3a. Entrance choreography ------------------------------------------ */
/* Richer than the old 10px rise: cards arrive from below and slightly behind
   the screen plane, so the deck assembles in depth. --i is stamped in the
   markup; the reduced-motion block at the top of this file zeroes both the
   duration and the delay, leaving every card in its final state. */
@keyframes dashReveal {
  from {
    opacity: 0;
    transform: translate3d(0, 22px, 0) scale(0.964);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

.dash-reveal {
  animation: dashReveal var(--dur-reveal) var(--ease-snap) backwards;
  animation-delay: calc(var(--i, 0) * var(--stagger-dash));
}

/* Per-tier timing: the deck lands slow and heavy, the quad snaps, the rail
   slides in last as one plane. */
.dash-hero,
.dash-risk {
  animation-duration: 760ms;
}

.dash-quad-card {
  animation-duration: 520ms;
}

.dash-rail {
  animation-duration: 700ms;
}

/* --- 3b. Risk meters fill from zero -------------------------------------- */
.dash-risk .risk-strip-item {
  --meter: var(--accent);
}

.dash-risk .risk-strip-item.is-warn {
  --meter: var(--warn);
}

.dash-risk .risk-strip-item.is-breach {
  --meter: var(--pnl-neg);
}

.dash-risk .risk-strip-item .risk-strip-track {
  height: 8px;
  background: var(--surface-inset);
  border-color: var(--line);
  box-shadow: inset 0 1px 3px var(--deck-groove);
}

/* JS keeps writing width; the scaleX keyframe is what fills it from empty, so
   the meter animates at whatever width the data happens to be. */
.dash-risk .risk-strip-item .risk-strip-fill {
  background: var(--meter);
  background-image: linear-gradient(90deg, color-mix(in srgb, var(--meter) 45%, transparent) 0%, var(--meter) 100%);
  box-shadow: 0 0 14px -3px var(--meter);
  transform-origin: left center;
  animation: riskFill 820ms var(--ease-out) 340ms backwards;
}

@keyframes riskFill {
  from {
    transform: scaleX(0);
  }
  to {
    transform: scaleX(1);
  }
}

/* --- 3c. Delta chips pop in --------------------------------------------- */
/* renderMetricDeltas flips [hidden]; going from display:none to visible
   restarts the animation, so a chip that appears always announces itself and
   a chip that merely updates its text does not. */
@keyframes chipIn {
  from {
    opacity: 0;
    transform: translateY(-5px) scale(0.9);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

.metric-delta,
.dash-hero-today,
.metric-note {
  animation: chipIn var(--dur-base) var(--ease-snap) backwards;
}

.metric-delta.is-pos {
  box-shadow: inset 0 0 0 1px var(--pnl-pos-line);
}

.metric-delta.is-neg {
  box-shadow: inset 0 0 0 1px var(--pnl-neg-line);
}

/* --- 3d. Live-data motion ------------------------------------------------ */
/* pnl-tick: the flash gains a ring and a glow. Keyframe NAMES are load-bearing
   — app.js removes the class on animationend by matching "tickUp"/"tickDown". */
@keyframes tickUp {
  0% {
    background-color: var(--pnl-pos-soft);
    box-shadow: 0 0 0 3px var(--pnl-pos-soft), 0 0 20px -4px var(--pnl-pos);
  }
  100% {
    background-color: transparent;
    box-shadow: 0 0 0 3px transparent, 0 0 20px -4px transparent;
  }
}

@keyframes tickDown {
  0% {
    background-color: var(--pnl-neg-soft);
    box-shadow: 0 0 0 3px var(--pnl-neg-soft), 0 0 20px -4px var(--pnl-neg);
  }
  100% {
    background-color: transparent;
    box-shadow: 0 0 0 3px transparent, 0 0 20px -4px transparent;
  }
}

.tick-up,
.tick-down {
  border-radius: var(--radius-sm);
}

@keyframes livePulse {
  0% {
    box-shadow: 0 0 0 0 var(--pnl-pos-line);
    opacity: 1;
  }
  70% {
    box-shadow: 0 0 0 7px transparent;
    opacity: 0.6;
  }
  100% {
    box-shadow: 0 0 0 0 transparent;
    opacity: 1;
  }
}

.btn.primary {
  background-image: linear-gradient(180deg, var(--accent-strong) 0%, var(--accent) 100%);
  box-shadow: 0 8px 20px -10px var(--halo-accent), inset 0 1px 0 rgba(255, 255, 255, 0.2);
}

.btn.primary:hover {
  box-shadow: 0 12px 26px -10px var(--halo-accent), inset 0 1px 0 rgba(255, 255, 255, 0.26);
}

/* Press depth: the button sinks into the page instead of just nudging down. */
.btn:active {
  transform: translateY(1px) scale(0.985);
  box-shadow: inset 0 2px 6px -2px var(--deck-groove);
}

/* --- 6. Focus with presence ---------------------------------------------- */
button:focus-visible,
.btn:focus-visible,
.mini-btn:focus-visible,
.nav-btn:focus-visible,
[data-tilt]:focus-visible,
summary:focus-visible {
  box-shadow: var(--focus-ring), 0 0 26px -6px var(--halo-accent);
}

/* --- 7. Responsive ------------------------------------------------------- */
/* Below the tilt breakpoint app.js never binds a pointer listener, but the
   perspective container would still flatten a scrolled card oddly on some
   mobile GPUs — drop the whole 3D context and the specular with it. */
@media (max-width: 979px), (hover: none), (pointer: coarse) {
  .dash-deck,
  .dash-quad {
    perspective: none;
  }

  [data-tilt] {
    transform: none;
    transform-style: flat;
  }

  [data-tilt]::after {
    display: none;
  }
}

/* ==========================================================================
   PHASE D — CLAY SYSTEM: SHARED COMPONENTS
   The token layer at the top of this file is now clay (soft ramp, hue-matched
   three-shadow stacks, 14/20/26/32 radii). This section spends those tokens on
   the shared recipes and takes the hairlines back out.

   The split that keeps a trading journal readable — clay is CHROME, flat is
   CONTENT:
     clay        panels, cards, buttons, nav, modals, wells, chips
     flat        table rows, calendar cells, chart interiors — anything that
                 repeats more than ~6 times. A clay row costs ~28px of padding
                 and turns a dense table into a bouncy castle.

   Two rules are accessibility, not taste, and must survive any restyle:
     1. CONTAINERS lose their border (depth is the shadow). CONTROLS keep one
        1px --control-edge — a soft shadow edge measures ~1.4:1 and WCAG
        1.4.11 wants 3:1 to identify a control.
     2. No state is signalled by shadow alone. Every pressed/active state also
        moves a colour or a fill, because shadow direction is invisible in
        sunlight, on a dimmed screen, and to a screen reader.
   ========================================================================== */

/* --- 1. Containers: border out, clay in ---------------------------------- */
.panel,
.view-head,
.dashboard-empty,
.dash-risk,
.dash-rail,
.sidebar,
.table-wrap,
.tag-set,
.reflection-list li {
  border: 0;
}

.panel,
.view-head,
.dashboard-empty,
.dash-risk,
.dash-rail,
.sidebar {
  border-radius: var(--radius-clay-lg);
  box-shadow: inset 0 1px 0 var(--clay-edge), var(--clay-raised);
}

/* The rail is a strip of seven read-only panes, not a thing you press. Full
   three-shadow clay on it would say "tappable" seven times over, so it gets
   the outer drop alone — this is the rule that stops clay leaking onto
   everything. */
.dash-rail {
  box-shadow: var(--clay-soft);
}

/* Minimalism half of the brief: the cards got rounder, so they also get room.
   16px was cramped at 26px radius — the corner ate the padding. */
.panel {
  padding: var(--space-6);
}

.view-head {
  padding: var(--space-6);
}

.view {
  gap: var(--space-4);
}

.metric-grid {
  gap: var(--space-4);
}

.panel-grid {
  gap: var(--space-4);
}

/* Metric cards are the second clay level and stop there — a clay card inside
   a clay card inside a clay card is mush, so everything below this (chips,
   inputs, meters) is either flat or pressed, never raised again. */
.metric-card:not(.dash-rail-item) {
  border: 0;
  border-radius: var(--radius-clay-md);
  padding: var(--space-5);
  min-height: 96px;
  box-shadow: inset 0 1px 0 var(--clay-edge), var(--clay-raised);
}

/* Money rail. The old 2px border-left tapers into a wedge against a 20px
   radius; an inset shadow traces the rounded edge properly. --pl-intensity
   still widens it on the balance card, exactly as before. */
.metric-card {
  --money-rail: transparent;
  --money-rail-w: 0px;
}

/* :not(.dash-rail-item) again, for the same reason Phase C flagged: the rail's
   cells are panes of one plane and their separator shadows are rewritten by
   nth-child in two responsive blocks. Rail cells have radius 0, so the
   original 2px border-left renders as a clean straight rail there and is left
   exactly as it was. */
.metric-card:not(.dash-rail-item).is-pos,
.metric-card:not(.dash-rail-item).is-neg {
  border-left: 0;
}

.metric-card.is-pos {
  --money-rail: var(--pnl-pos-line);
  --money-rail-w: 3px;
}

.metric-card.is-neg {
  --money-rail: var(--pnl-neg-line);
  --money-rail-w: 3px;
}

.metric-card-balance.is-pos,
.metric-card-balance.is-neg {
  --money-rail-w: calc(3px + var(--pl-intensity) * 3px);
  border-left-width: 0;
}

.metric-card-balance.is-pos {
  --money-rail: color-mix(in srgb, var(--pnl-pos) calc(35% + var(--pl-intensity) * 55%), transparent);
}

.metric-card-balance.is-neg {
  --money-rail: color-mix(in srgb, var(--pnl-neg) calc(35% + var(--pl-intensity) * 55%), transparent);
}

.metric-card:not(.dash-rail-item).is-pos,
.metric-card:not(.dash-rail-item).is-neg {
  box-shadow: inset var(--money-rail-w) 0 0 var(--money-rail), inset 0 1px 0 var(--clay-edge), var(--clay-raised);
}

/* Bento geometry: the hero is the single biggest clay blob on the page. */
.dash-hero {
  border-radius: var(--radius-clay-xl);
  padding: var(--space-6) var(--space-6) 0;
}

.dash-risk {
  padding: var(--space-6);
}

.dash-quad-card {
  border-radius: var(--radius-clay-md);
  padding: var(--space-5);
}

/* Wells inside clay: pressed, never raised. */
.dash-risk .risk-strip-item .risk-strip-track,
.risk-strip-track {
  border: 0;
  border-radius: var(--radius-pill);
  background: var(--surface-inset);
  box-shadow: var(--clay-pressed);
  overflow: hidden;
}

.risk-strip-fill {
  border-radius: var(--radius-pill);
}

.table-wrap {
  border-radius: var(--radius-clay-sm);
  background: var(--surface-inset);
  box-shadow: var(--clay-pressed);
}

/* Chart wells drop to the small clay radius — nesting 26 inside 26 inflates
   the panel and steals width from the plot. */
.panel-chart canvas {
  border: 0;
  border-radius: var(--radius-clay-sm);
  box-shadow: inset 0 1px 0 var(--chart-highlight), var(--clay-pressed);
}

.panel-chart:hover canvas {
  box-shadow: inset 0 1px 0 var(--chart-highlight), var(--clay-pressed),
    0 10px 24px -20px var(--chart-canvas-shadow);
}

.reflection-list li,
.tag-set {
  border-radius: var(--radius-clay-sm);
  background: var(--surface-inset);
  box-shadow: var(--clay-pressed);
}

/* --- 2. Buttons: puffy, pill, and they actually squish -------------------- */
/* Press state moves THREE things at once — transform, shadow direction and
   fill — so the state survives on a dimmed screen and for anyone who cannot
   see the shadow flip at all. */
.btn,
.mini-btn,
.nav-btn,
.nav-toggle {
  min-height: 44px;
}

.btn {
  border: 1px solid var(--control-edge);
  border-radius: var(--radius-pill);
  padding: 12px 22px;
  background: var(--surface-2);
  background-image: none;
  box-shadow: var(--clay-float);
  transition: transform var(--dur-squish) var(--ease-squish),
    background-color var(--dur-fast) var(--ease-out),
    border-color var(--dur-fast) var(--ease-out),
    color var(--dur-fast) var(--ease-out);
}

.btn:hover {
  background: var(--surface-3);
  border-color: var(--accent);
}

.btn.primary {
  border-color: transparent;
  background: var(--accent);
  background-image: linear-gradient(180deg, var(--accent-strong) 0%, var(--accent) 100%);
  color: var(--text-inverse);
  box-shadow: var(--clay-accent);
}

.btn.primary:hover {
  background-image: linear-gradient(180deg, var(--accent-strong) 0%, var(--accent-strong) 100%);
  box-shadow: var(--clay-accent);
}

.btn.danger,
.btn.destructive {
  border-color: var(--pnl-neg-line);
  color: var(--pnl-neg);
  background: var(--pnl-neg-soft);
}

.btn.danger:hover,
.btn.destructive:hover {
  border-color: var(--pnl-neg);
  background: var(--pnl-neg-soft);
}

.btn:active,
.btn.primary:active {
  transform: translateY(2px) scale(0.985);
  background: var(--surface-3);
  background-image: none;
  box-shadow: var(--clay-pressed);
}

.btn.primary:active {
  background: var(--accent-strong);
  color: var(--text-inverse);
}

.mini-btn {
  border: 1px solid var(--control-edge);
  border-radius: var(--radius-pill);
  padding: 8px 14px;
  min-height: 36px;
  background: var(--surface-2);
  box-shadow: var(--clay-raised);
  transition: transform var(--dur-squish) var(--ease-squish),
    background-color var(--dur-fast) var(--ease-out),
    border-color var(--dur-fast) var(--ease-out);
}

.mini-btn:hover {
  background: var(--surface-3);
  border-color: var(--accent);
}

.mini-btn:active {
  transform: translateY(1px) scale(0.98);
  background: var(--surface-3);
  box-shadow: var(--clay-pressed);
}

/* Destructive stays quiet until touched, but it is a control, so it keeps a
   boundary — a tinted one, so the row does not grow a grey ladder. */
.mini-btn.danger {
  border-color: var(--pnl-neg-line);
  background: transparent;
  color: var(--pnl-neg);
  box-shadow: none;
}

.mini-btn.danger:hover {
  border-color: var(--pnl-neg);
  background: var(--pnl-neg-soft);
  box-shadow: var(--clay-raised);
}

.mini-btn.danger:active {
  box-shadow: var(--clay-pressed);
}

/* Row-action buttons sit inside a 40px table row: they may not grow it. */
.row-actions .mini-btn {
  min-height: 32px;
  padding: 6px 12px;
}

/* --- 3. Form controls: pressed into the clay ----------------------------- */
/* Text entry is the one place the surface should sink. Opaque --surface-inset
   is the token contract (never translucency behind text being typed); the
   inset stack is what makes it read as a well rather than a flat box. */
input,
select,
textarea {
  border: 1px solid var(--control-edge);
  border-radius: var(--radius-clay-sm);
  background: var(--surface-inset);
  padding: 12px 14px;
  min-height: 44px;
  box-shadow: var(--clay-pressed);
}

textarea {
  min-height: 88px;
}

input[type="checkbox"],
input[type="radio"] {
  min-height: 0;
  box-shadow: none;
}

input:focus,
select:focus,
textarea:focus {
  border-color: var(--accent);
  background: var(--surface-inset);
  box-shadow: var(--clay-pressed), var(--focus-ring);
}

/* Chips with a checkbox in them are controls: boundary, 44px target, pill. */
.tag-set label {
  border: 1px solid var(--control-edge);
  border-radius: var(--radius-pill);
  min-height: 44px;
  padding: 8px 14px;
  background: var(--surface-2);
  box-shadow: var(--clay-raised);
  transition: transform var(--dur-squish) var(--ease-squish),
    border-color var(--dur-fast) var(--ease-out), background-color var(--dur-fast) var(--ease-out);
}

.tag-set label:hover {
  border-color: var(--accent);
  background: var(--surface-3);
}

.tag-set label:active {
  transform: translateY(1px) scale(0.985);
  box-shadow: var(--clay-pressed);
}

.tag-set:focus-within {
  box-shadow: var(--clay-pressed), var(--focus-ring);
}

/* --- 4. Tables: flat content inside a pressed well ------------------------ */
/* Deliberately NOT clay. Rows repeat, and clay on a repeating row is the
   canonical way this style ruins a data product. */
thead th {
  background: var(--surface-inset);
  border-bottom: 1px solid var(--line-strong);
}

th,
td {
  padding: 11px 14px;
  border-bottom: 1px solid var(--line);
}

tbody tr:last-child td {
  border-bottom: 0;
}

tbody tr:hover {
  background: var(--surface-3);
}

/* --- 5. Badges and pills: soft tint, no ladder of borders ----------------- */
.pill {
  border: 0;
  border-radius: var(--radius-pill);
  padding: 4px 10px;
  background: var(--surface-inset);
  color: var(--text-soft);
}

.pill-win {
  background: var(--pnl-pos-soft);
  color: var(--pnl-pos);
}

.pill-loss {
  background: var(--pnl-neg-soft);
  color: var(--pnl-neg);
}

.pill-be {
  background: var(--warn-soft);
  color: var(--warn);
}

.metric-delta,
.dash-hero-today {
  border: 0;
  border-radius: var(--radius-pill);
  padding: 3px 10px;
}

.dash-hero-today.is-pos {
  background: var(--pnl-pos-soft);
}

.dash-hero-today.is-neg {
  background: var(--pnl-neg-soft);
}

/* --- 6. Nav ------------------------------------------------------------- */
.nav-btn {
  border-radius: var(--radius-pill);
  padding: 10px 16px;
}

.nav-btn:hover {
  background: var(--surface-3);
}

/* Active is never colour-only: fill + text weight + the rail Phase C slides. */
.nav-btn.is-active {
  background: var(--accent-muted);
  color: var(--text);
  border-radius: var(--radius-pill);
  box-shadow: none;
}

.main-nav .nav-btn.is-active {
  border-radius: var(--radius-pill);
  box-shadow: inset 3px 0 0 var(--accent);
}

.nav-toggle {
  border: 1px solid var(--control-edge);
  border-radius: var(--radius-pill);
  padding: 10px 16px;
  box-shadow: var(--clay-raised);
  transition: transform var(--dur-squish) var(--ease-squish),
    background-color var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out);
}

.nav-toggle:active {
  transform: translateY(1px) scale(0.98);
  box-shadow: var(--clay-pressed);
}

.nav-toggle.is-open {
  background: var(--accent-muted);
  border-color: var(--accent);
  box-shadow: var(--clay-pressed);
}

/* --- 7. Modals and dialogs ----------------------------------------------- */
.auth-panel,
.score-info-dialog {
  border: 0;
  border-radius: var(--radius-clay-xl);
  padding: var(--space-6);
  box-shadow: inset 0 1px 0 var(--clay-edge), var(--shadow-modal);
}

.auth-panel {
  gap: var(--space-3);
}

/* --- 8. Focus: an explicit ring, always outside the clay blur ------------- */
/* The inset highlight on a clay surface mimics a focus ring closely enough
   that users cannot tell them apart — so the real one stays loud, sits 3px
   clear of the edge, and was measured against BOTH the card and the page
   (5.4:1 / 6.1:1 dark, 6.2:1 / 5.5:1 light). */
button:focus-visible,
.btn:focus-visible,
.mini-btn:focus-visible,
.nav-btn:focus-visible,
.nav-toggle:focus-visible,
a:focus-visible,
[tabindex]:focus-visible,
summary:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
}

/* --- 9. Dark-only soft edge ---------------------------------------------- */
/* Dark is :root (no attribute), hence :not([data-theme="light"]). In dark the
   drop shadow is nearly invisible against the page, so the barely-there top
   edge is what separates surface from background — the one hairline clay is
   not allowed to delete. */
:root:not([data-theme="light"]) .panel,
:root:not([data-theme="light"]) .view-head,
:root:not([data-theme="light"]) .metric-card:not(.dash-rail-item),
:root:not([data-theme="light"]) .dash-risk,
:root:not([data-theme="light"]) .dash-rail,
:root:not([data-theme="light"]) .dashboard-empty,
:root:not([data-theme="light"]) .sidebar {
  border: 1px solid var(--clay-edge);
}

/* Modals float over a dim overlay with no page behind them to lift off, so
   they take the stronger edge. */
:root:not([data-theme="light"]) .auth-panel,
:root:not([data-theme="light"]) .score-info-dialog {
  border: 1px solid var(--clay-edge-strong);
}

/* --- 10. Reduced motion --------------------------------------------------- */
/* The global block at the top of this file zeroes durations and delays. The
   squish is a transform, so it lands instantly in its final state — but the
   press transform itself is decoration, so drop it and let the fill and the
   shadow flip carry the state on their own. */
@media (prefers-reduced-motion: reduce) {
  .btn:active,
.mini-btn:active,
.nav-toggle:active,
.tag-set label:active {
    transform: none;
  }
}

/* --- 11. Mobile ---------------------------------------------------------- */
/* Clay eats width: a 26px radius plus 24px padding leaves very little of a
   375px screen for the numbers. Below 560px the radii step down one rung and
   the padding follows. */
@media (max-width: 720px) {
  .panel,
  .view-head,
  .dash-risk {
    padding: var(--space-5);
  }

  .dash-hero {
    padding: var(--space-5) var(--space-5) 0;
  }
}

@media (max-width: 560px) {
  .panel,
  .view-head,
  .dashboard-empty,
  .dash-risk,
  .dash-rail,
  .sidebar {
    border-radius: var(--radius-clay-md);
  }

  .dash-hero,
  .auth-panel,
  .score-info-dialog {
    border-radius: var(--radius-clay-lg);
  }

  .panel,
  .view-head,
  .dash-risk,
  .auth-panel,
  .score-info-dialog {
    padding: var(--space-4);
  }

  .dash-hero {
    padding: var(--space-4) var(--space-4) 0;
  }

  .metric-card:not(.dash-rail-item) {
    padding: var(--space-4);
    border-radius: var(--radius-clay-sm);
  }

  th,
  td {
    padding: 10px 12px;
  }
}

/* Tone-agnostic: the ring follows whatever colour the chip is wearing. */
@keyframes livePulse {
  0% {
    box-shadow: 0 0 0 0 currentColor;
    opacity: 1;
  }
  70% {
    box-shadow: 0 0 0 7px transparent;
    opacity: 0.55;
  }
  100% {
    box-shadow: 0 0 0 0 transparent;
    opacity: 1;
  }
}

.view-head-support {
  max-width: 44ch;
  color: var(--text-soft);
  font-size: var(--fs-body);
}

/* --- 3. Bento breathing room -------------------------------------------- */
/* Clay elements need >= 16px of air or the shadows merge into one smudge. */
.dash-stats,
.dash-deck,
.dash-quad,
.panel-grid,
.panel-grid-analytics,
.panel-grid-bottom {
  gap: var(--space-4);
}

/* --- 4. Deck, quad, rail ------------------------------------------------- */
.dash-hero {
  min-height: 216px;
}

.dash-hero-value {
  font-size: clamp(38px, 4.4vw, 56px);
  letter-spacing: -0.03em;
}

/* The today chip loses its hairline like every other pill — soft fill only. */
.dash-hero-today {
  border: 0;
  padding: 3px 10px;
  background: var(--surface-inset);
}

.dash-risk {
  min-height: 216px;
}

.dash-quad-card {
  padding: var(--space-5);
}

/* The rail is the quietest tier: one soft plane, seven read-only panes. It
   keeps --clay-soft (outer drop only) from Phase D, and NOTHING in this phase
   touches .dash-rail-item — its separators are inset shadows rewritten by
   :nth-child in two responsive blocks above, and any box-shadow set here
   would clobber them. Third phase in a row to flag this; not re-armed. */

/* --- 5. Chart panels ----------------------------------------------------- */
/* Panels lift on hover, but the shadow swap is NOT transitioned: a 4-layer
   clay stack re-blurring on every pointer pass is the repaint mobile Safari
   cannot pay, and the live loop is already touching these canvases. */
#dashboard .panel {
  transition: transform var(--dur-base) var(--ease-out);
}

#dashboard .panel:hover {
  transform: translateY(-2px);
  box-shadow: inset 0 1px 0 var(--clay-edge), var(--clay-float);
}

[data-tilt] {
  transition: transform var(--dur-slow) var(--ease-out);
}

/* Containers and wells only. The renderers are Phase A's and stay untouched. */
#dashboard .panel-head {
  margin-bottom: var(--space-4);
}

#dashboard .panel-head-inline {
  gap: var(--space-4);
}

.panel-chart canvas {
  border-radius: var(--radius-clay-sm);
}

/* Hover on a chart panel repaints a 4-layer clay stack on a canvas the live
   loop is also touching. Lift the panel instead; the well stays put. */
.panel-chart:hover canvas {
  box-shadow: inset 0 1px 0 var(--chart-highlight), var(--clay-pressed);
}

.strategy-performance-toolbar {
  gap: var(--space-3);
}

.trader-score-foot {
  margin-top: var(--space-4);
}

/* --- 6. Discipline monitor ----------------------------------------------- */
/* Scores are readouts, so they sink into the panel rather than rising off it —
   raised clay here would read as three buttons. */
.score-stack {
  gap: var(--space-3);
  margin-bottom: var(--space-4);
}

.score-item {
  border: 0;
  border-radius: var(--radius-clay-sm);
  padding: var(--space-4);
  background: var(--surface-inset);
  box-shadow: var(--clay-pressed);
}

/* --- 7. Risk form -------------------------------------------------------- */
.compact-form {
  gap: var(--space-4);
}

/* --- 8. Reveal choreography ---------------------------------------------- */
/* Softer and slightly slower to suit a heavy material: less travel, a hair of
   scale, and an easing that settles instead of overshooting. The global
   reduced-motion block zeroes both duration and delay, so every card lands in
   its final state immediately — never invisible, never mid-flight. */
@keyframes dashReveal {
  from {
    opacity: 0;
    transform: translate3d(0, 16px, 0) scale(0.988);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

.dash-reveal {
  animation: dashReveal var(--dur-reveal) var(--ease-clay) backwards;
  animation-delay: calc(var(--i, 0) * var(--stagger-dash));
}

.dash-hero,
.dash-risk {
  animation-duration: 900ms;
}

.dash-quad-card {
  animation-duration: 720ms;
}

.dash-rail {
  animation-duration: 860ms;
}

@media (max-width: 720px) {
  /* The hero keeps a floor because the sparkline is absolutely positioned
     into its lower 74px — collapse it and the curve eats the numerals. */
  .dash-hero {
    min-height: 176px;
  }

  .dash-risk {
    min-height: 0;
  }

}

/* One column at phone width: the actions drop under the price instead of
   squeezing it to four characters. */
@media (max-width: 560px) {

  .dash-stats,
  .dash-deck,
  .dash-quad,
  .panel-grid,
  .panel-grid-analytics,
  .panel-grid-bottom {
    gap: var(--space-3);
  }

  .score-stack {
    grid-template-columns: minmax(0, 1fr);
  }

  .compact-form {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-3);
  }

}

/* ==========================================================================
   PHASE F — MOBILE TAB BAR + CENTRE "+" FAB, RESPONSIVE PASS
   Research brief §7b: the floating-pill bar, not the notch-cut variant. A
   mask would clip box-shadow and force the whole elevation onto a
   filter: drop-shadow wrapper (no insets, more expensive). A detached pill
   IS a clay blob, so the recipe and the token layer already agree.

   Geometry contract — every number below derives from these four:
     bar inset from the viewport bottom .... 12px + safe-area
     bar height ........................... 64px  (48px tab + 2x8 padding)
     FAB diameter ......................... 60px, centred on the bar's top
                                            edge -> protrudes exactly 30px
     content clearance .................... 12 + 64 + 30 + 18 = 124px
                                            + safe-area, applied as footer
                                            padding (see §5 for why not body)
   ========================================================================== */

/* --- 1. Visibility -------------------------------------------------------- */
/* Chrome, not content: the bar exists below 900px and only once the app shell
   is on screen. Before auth-ready the body carries neither class, so the
   logged-out landing page never renders it — not even for a frame. */
.tabbar,
.tabbar-fab {
  display: none;
}

@media (max-width: 899px) {
  body.is-authenticated .tabbar,
  body.is-preview .tabbar,
  body.is-guest:not(.demo-signup) .tabbar {
    display: grid;
  }

  body.is-authenticated .tabbar-fab,
  body.is-preview .tabbar-fab,
  body.is-guest:not(.demo-signup) .tabbar-fab {
    display: grid;
  }
}

/* --- 2. The bar ----------------------------------------------------------- */
.tabbar {
  position: fixed;
  left: max(12px, env(safe-area-inset-left, 0px));
  right: max(12px, env(safe-area-inset-right, 0px));
  bottom: calc(env(safe-area-inset-bottom, 0px) + 12px);
  height: 64px;
  /* Two tabs, the FAB gutter, two tabs. The gutter is 68px: a 60px FAB plus a
     4px clear ring either side. Every px here is label width — at 360 the
     four tabs get 64px each, and "Dashboard" in 11px mono needs 60 of them,
     so the gutter, the gaps and the padding are all cut to the bone. */
  grid-template-columns: repeat(2, minmax(0, 1fr)) 68px repeat(2, minmax(0, 1fr));
  align-items: stretch;
  gap: 0;
  padding: 8px 4px;
  border-radius: var(--radius-clay-lg);
  background: var(--surface-2);
  box-shadow: var(--clay-float);
  z-index: var(--z-nav);
  /* transform/opacity only — never the shadow (4-layer, large-blur). */
  transition: transform var(--dur-base) var(--ease-out), opacity var(--dur-fast) var(--ease-out);
}

/* Dark-only structural edge: the drop shadow is invisible against a dark
   page, so the barely-there hairline is what separates bar from background
   and what holds the 3:1 non-text bar (WCAG 1.4.11). */
:root:not([data-theme="light"]) .tabbar {
  border: 1px solid var(--clay-edge-strong);
}

/* --- 3. Tabs -------------------------------------------------------------- */
/* These are .nav-btn, so they inherit the app-wide nav rules and then undo
   the ones written for a horizontal desktop rail / full-width sidebar list. */
.tabbar .nav-btn {
  position: relative;
  display: grid;
  grid-template-rows: auto auto;
  justify-items: center;
  align-content: center;
  gap: 4px;
  width: auto;
  min-width: 0;
  min-height: 48px;      /* Material 48dp / Apple 44pt — take the larger */
  padding: 4px 1px;
  border-radius: var(--radius-clay-sm);
  background: transparent;
  color: var(--text-soft);
  text-align: center;
  text-transform: none;
  letter-spacing: 0.01em;
  transition: color var(--dur-fast) var(--ease-out), transform var(--dur-squish) var(--ease-squish);
}

.tabbar .nav-btn:hover {
  background: transparent;
  color: var(--text);
}

.tabbar .nav-btn:active {
  transform: scale(0.94);
}

.tabbar-icon {
  display: block;
  width: 24px;
  height: 24px;
}

.tabbar-icon svg {
  display: block;
  width: 24px;
  height: 24px;
  stroke: currentColor;
  stroke-width: 1.7;
  stroke-linecap: round;
  stroke-linejoin: round;
  fill: none;
}

.tabbar-label {
  max-width: 100%;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  font: 600 var(--fs-micro)/1 var(--font-mono);
}

/* Active is never colour-only (WCAG 1.4.1): colour + a 3px indicator rail
   above the icon. The Phase D accent-muted pill is dropped here — a filled
   chip inside a 60px slot swallows the icon. */
.tabbar .nav-btn.is-active {
  background: transparent;
  color: var(--accent);
}

/* :not(:focus-visible) is load-bearing — this selector outranks the global
   button:focus-visible ring, so clearing the Phase D active shadow
   unconditionally would leave the current tab with no visible focus at all
   (WCAG 2.4.7). Same guard the desktop nav rail uses. */
.tabbar .nav-btn.is-active:not(:focus-visible) {
  box-shadow: none;
}

.tabbar .nav-btn.is-active::before {
  content: "";
  position: absolute;
  top: -4px;
  left: 50%;
  translate: -50% 0;
  width: 24px;
  height: 3px;
  border-radius: var(--radius-pill);
  background: currentColor;
}

/* --- 4. The "+" FAB ------------------------------------------------------- */
/* An action, not a destination — it lives outside the <nav> landmark. Its
   centre sits exactly on the bar's top edge:
     bar top edge = safe + 12 + 64 = safe + 76 from the viewport bottom
     FAB bottom   = safe + 76 - 30 = safe + 46 */
.tabbar-fab {
  position: fixed;
  left: 50%;
  bottom: calc(env(safe-area-inset-bottom, 0px) + 46px);
  translate: -50% 0;
  place-items: center;
  width: 60px;
  height: 60px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: var(--accent);
  background-image: linear-gradient(180deg, var(--accent-strong) 0%, var(--accent) 100%);
  color: var(--text-inverse);
  cursor: pointer;
  box-shadow: var(--clay-accent);
  z-index: calc(var(--z-nav) + 1);
  transition: transform var(--dur-squish) var(--ease-squish), opacity var(--dur-fast) var(--ease-out);
}

.tabbar-fab-glyph {
  display: block;
  width: 28px;
  height: 28px;
}

.tabbar-fab-glyph svg {
  display: block;
  width: 28px;
  height: 28px;
  stroke: currentColor;
  stroke-width: 2.2;
  stroke-linecap: round;
  fill: none;
}

/* The squish. Paired with a fill change so the pressed state is never
   carried by shadow alone (WCAG 1.4.1) — clay's depth flip is invisible on a
   dimmed screen and to a screen reader. */
.tabbar-fab:active {
  transform: scale(0.92);
  background-image: none;
  background: var(--accent-strong);
  box-shadow: var(--clay-pressed);
}

/* --- 5. Keyboard + content clearance -------------------------------------- */
/* iOS: interactive-widget=resizes-content stops the bar riding the keyboard
   up, but a fixed bar directly above the keyboard still eats the field under
   it. Drop both out of the way while a text field has focus. :has is a
   progressive enhancement — where it is unsupported the whole rule is
   dropped and the bar simply stays put. */
body:has(input:not([type="checkbox"]):not([type="radio"]):not([type="file"]):focus) .tabbar,
body:has(input:not([type="checkbox"]):not([type="radio"]):not([type="file"]):focus) .tabbar-fab,
body:has(textarea:focus) .tabbar,
body:has(textarea:focus) .tabbar-fab {
  transform: translateY(150px);
  opacity: 0;
  pointer-events: none;
}

/* Clearance goes on the footer, not on the body: html/body are height:100%,
   so body padding-bottom would land at the 100vh mark rather than after the
   overflowing content. Footer padding always grows the document. */
@media (max-width: 899px) {
  body.is-authenticated .site-footer,
  body.is-preview .site-footer,
  body.is-guest:not(.demo-signup) .site-footer {
    padding-bottom: calc(env(safe-area-inset-bottom, 0px) + 124px);
  }

  /* Anchor jumps and calendar/journal deep links must not land under the bar. */
  html {
    scroll-padding-bottom: calc(env(safe-area-inset-bottom, 0px) + 124px);
  }
}

/* Landscape phone: 124px of reserved bottom on a 400px-tall viewport is a
   third of the screen. Compact geometry — 52px bar, 52px FAB, labels clipped
   to screen-reader-only (never display:none, that would strip the button's
   accessible name). Bar top edge = safe + 12 + 52 = safe + 64, so the FAB
   sits at safe + 64 - 26 = safe + 38. Clearance 12 + 52 + 26 + 18 = 108. */
@media (max-width: 899px) and (max-height: 480px) {
  .tabbar {
    height: 52px;
    padding: 4px;
  }

  .tabbar .nav-btn {
    min-height: 44px;
  }

  .tabbar-label {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  .tabbar .nav-btn.is-active::before {
    top: -2px;
  }

  .tabbar-fab {
    width: 52px;
    height: 52px;
    bottom: calc(env(safe-area-inset-bottom, 0px) + 38px);
  }

  body.is-authenticated .site-footer,
  body.is-preview .site-footer,
  body.is-guest:not(.demo-signup) .site-footer {
    padding-bottom: calc(env(safe-area-inset-bottom, 0px) + 108px);
  }

  html {
    scroll-padding-bottom: calc(env(safe-area-inset-bottom, 0px) + 108px);
  }
}

/* From 900px up the FAB is gone, so the command bar keeps the full-size
   "New Trade" button; below it the button would be a second, redundant copy
   of the FAB sitting 40px away. The + keeps a non-circular route either way:
   the sidebar's "Trade Entry" destination. */
@media (max-width: 899px) {
  #journalNewTradeBtn {
    display: none;
  }

}

/* --- 6. Touch targets ----------------------------------------------------- */
/* 44px floor on every control a thumb can reach. Coarse pointers get it at
   any width — a touch laptop is still a thumb. Inputs, selects and textareas
   already carry min-height: 44px from the Phase D control pass. */
@media (max-width: 899px), (pointer: coarse) {
  .btn,
  .mini-btn,
  .nav-toggle,
  .direction-btn,
  .strategy-toggle-btn,
  .tag-set label {
    min-height: 44px;
  }

  /* Below 900px the journal table is card-mode, so row actions no longer sit
     inside a 40px row and are free to grow. */
  .row-actions .mini-btn {
    min-height: 44px;
    padding: 10px 14px;
  }

  .strategy-toggle-btn {
    padding: 0 14px;
  }

  /* Sidebar list items are full-width rows at this width. */
  .main-nav .nav-btn,
  .nav-logout-mobile {
    min-height: 44px;
  }
}

/* --- 7. Small-screen type floor ------------------------------------------- */
/* --fs-micro (11px) is the floor. The rail's delta chip was the last holdout
   at 10.5px; it tightens its padding instead of its type. */
.dash-rail-item .metric-delta {
  font-size: var(--fs-micro);
  padding: 1px 4px;
}

/* The edge quad goes 2-up below 900px. Its widest value is Expectancy, which
   can read "-$1,234.56" — 10 mono glyphs. The desktop clamp floor (24px)
   needs 144px and a 360px phone only gives the cell 128px, so the floor goes
   viewport-relative rather than letting the numeral run past the card. */
@media (max-width: 560px) {
  .dash-quad-card .metric-value {
    font-size: clamp(19px, 5.6vw, 24px);
  }
}

/* --- 8. Reduced motion ---------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  /* The press squish dies; the fill flip and the pressed shadow still say
     "held". Never leaves anything invisible or mid-animation. */
  .tabbar .nav-btn:active,
  .tabbar-fab:active {
    transform: none;
  }
}

/* ==========================================================================
   PHASE G — GUEST (DEMO) MODE + LANDING CLAY FINISH
   Guest mode is the whole app on an ephemeral journal; the ONLY thing an
   account buys is persistence. So the chrome has exactly one job: be honest
   about that on every screen without shouting.
   ========================================================================== */

/* --- 1. The demo notice --------------------------------------------------- */
/* Sticky, not fixed: it rides the content column so it clears the tab bar and
   the FAB on mobile without a second geometry contract to keep in sync.
   Surface-2 + a warn rail, never a warn FILL — a full amber bar on every
   screen of a financial product reads as an error state, and this is not one.
   Interactive (it holds the sign-up CTA), so it earns the full clay stack. */
.demo-notice {
  position: sticky;
  top: var(--space-2);
  z-index: var(--z-dropdown);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3) var(--space-4);
  margin-bottom: var(--space-4);
  padding: var(--space-4) var(--space-5);
  border: 1px solid var(--clay-edge);
  border-radius: var(--radius-clay-lg);
  background: var(--surface-2);
  box-shadow: inset 3px 0 0 var(--warn), var(--clay-raised);
}

.demo-notice[hidden] {
  display: none;
}

/* Redundant coding: the state is named in words, not carried by colour. */
.demo-notice-mark {
  flex: 0 0 auto;
  padding: 5px 10px;
  border-radius: var(--radius-pill);
  background: var(--warn-soft);
  color: var(--warn);
  font: 700 var(--fs-micro)/1.2 var(--font-mono);
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

.demo-notice-copy {
  flex: 1 1 260px;
  margin: 0;
  color: var(--text-soft);
  font-size: var(--fs-body);
  line-height: 1.55;
}

.demo-notice-copy strong {
  color: var(--text);
  font-weight: 700;
}

.demo-notice-actions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-left: auto;
}

.demo-notice-btn {
  white-space: nowrap;
}

/* Quiet by design — dismissing is per-view, and switchView brings the notice
   straight back, so this control never needs to compete with the CTA. */
.demo-notice-dismiss {
  min-height: 44px;
  min-width: 44px;
  padding: 0 var(--space-3);
  border: 0;
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--text-soft);
  cursor: pointer;
  font: 600 var(--fs-micro)/1.2 var(--font-mono);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  transition: color var(--dur-fast) var(--ease-out), background-color var(--dur-fast) var(--ease-out);
}

.demo-notice-dismiss:hover {
  background: var(--surface-3);
  color: var(--text);
}

@media (max-width: 720px) {
  .demo-notice {
    padding: var(--space-3) var(--space-4);
    gap: var(--space-2) var(--space-3);
  }

  .demo-notice-actions {
    width: 100%;
    margin-left: 0;
  }

  .demo-notice-btn {
    flex: 1 1 auto;
  }
}

/* --- 3. Demo sign-up hand-off -------------------------------------------- */
/* body.demo-signup shows the landing shell OVER a live demo instead of tearing
   it down, so the journal survives until submitAuth decides what to carry.
   Escape or an overlay click returns to the app. The display swap itself lives
   in the access-gate rules near the top of this file. */

/* ==========================================================================
   Mobile fixes — must sit last in the cascade to beat the clay component
   rules above, which re-set .dash-hero padding.
   ========================================================================== */
@media (max-width: 720px) {
  /* The sparkline is absolutely positioned into the hero's lower 74px. Without
     reserving that band in flow, the curve runs straight through the delta and
     "Today" chips. Reserve it so the meta row always sits clear of the art.
     Selector must be .metric-card.dash-hero: the hero is also a .metric-card,
     and `.metric-card:not(.dash-rail-item)` (0,2,0) outranks a bare .dash-hero. */
  .metric-card.dash-hero {
    padding-bottom: 82px;
  }
}
