/* ============================================================================
   Whales Sonar v2 — design tokens + components.

   Ported from the 2026-07-30 design delivery. Lives ALONGSIDE styles.css: v1 is
   untouched and still serves /app, this serves /v2 until the founder flips the
   default. Nothing here may leak into v1 — different file, different class
   prefix where a name would otherwise collide.

   The direction is an inversion of v1: near-black ground, one green accent, and
   monospace as the BODY face rather than a labelling accent. Read the font
   block before changing anything there — the ratio is the identity.
   ========================================================================= */

/* Clash Display is self-hosted because Fontshare is NOT in our CSP
   (style-src/font-src allow Google only). The other four faces come from
   Google Fonts, which the CSP already permits, so they stay on the CDN. */
@font-face {
  font-family: "Clash Display";
  src: url("/assets/fonts/ClashDisplay-500.woff2") format("woff2");
  font-weight: 500; font-display: swap; font-style: normal;
}
@font-face {
  font-family: "Clash Display";
  src: url("/assets/fonts/ClashDisplay-600.woff2") format("woff2");
  font-weight: 600; font-display: swap; font-style: normal;
}
@font-face {
  font-family: "Clash Display";
  src: url("/assets/fonts/ClashDisplay-700.woff2") format("woff2");
  font-weight: 700; font-display: swap; font-style: normal;
}

:root {
  /* ── ground ─────────────────────────────────────────────────────────── */
  --bg: #050706;          /* page — near-black, biased green not blue */
  --card: #070a08;        /* the default card */
  --card-2: #0a0d0b;      /* raised: modals, configure panels */

  /* ── ink, 5 steps ───────────────────────────────────────────────────── */
  --ink: #e7ecea;         /* primary text */
  --ink-2: #aeb5b0;       /* secondary, still readable at 14px */
  --ink-3: #8b948e;       /* body copy on dark, the workhorse */
  --ink-4: #5f6862;       /* labels, captions */
  --ink-5: #454b47;       /* disabled */
  --wordmark: #f4f3f0;    /* the brand lockup only */

  /* ── accent ─────────────────────────────────────────────────────────── */
  --acc: #57cf92;
  --acc-2: #7fe1ac;       /* hover / emphasis */
  --acc-ink: #04100a;     /* text ON the accent — never plain black */
  --acc-bg: rgba(87, 207, 146, .1);
  --acc-line: rgba(87, 207, 146, .3);

  /* ── semantic: these two MEAN money down and caution. Not decoration.
        Never reassign them to a brand colour. ─────────────────────────── */
  --neg: #e07a5f;
  --neg-2: #ff8f77;       /* on small text, where #e07a5f is too dim */
  --neg-bg: rgba(224, 122, 95, .1);
  --neg-line: rgba(224, 122, 95, .32);
  --warn: #b99a4b;        /* "not a track record", pending, in-development */
  --warn-bg: rgba(185, 154, 75, .04);
  --warn-line: rgba(185, 154, 75, .35);

  /* ── lines ──────────────────────────────────────────────────────────── */
  --line: rgba(255, 255, 255, .1);
  --line-soft: rgba(255, 255, 255, .06);
  --line-hair: rgba(255, 255, 255, .045);

  /* ── type ───────────────────────────────────────────────────────────────
     MONO IS THE BODY FACE. That is the identity: this reads as an instrument,
     not a marketing site. Display carries headings and every number; the serif
     is an italic accent used once per heading at most; px is for tiny
     all-caps labels only. If running text starts using --ui, the look is gone. */
  --mono: "JetBrains Mono", ui-monospace, monospace;
  --disp: "Clash Display", "Space Grotesk", system-ui, sans-serif;
  --serif: "Instrument Serif", Georgia, serif;
  --brand: "Bricolage Grotesque", "Arial Black", sans-serif;
  --px: "Silkscreen", "JetBrains Mono", monospace;

  /* ── geometry ───────────────────────────────────────────────────────── */
  --r-card: 14px;
  --r-ctl: 9px;           /* buttons, inputs */
  --r-pill: 5px;
  --sidebar-w: 228px;
}

* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; height: 100%; }
body {
  background: var(--bg);
  color: var(--ink);
  font-family: var(--mono);
  font-size: 13px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}
a { color: var(--acc); text-decoration: none; }
a:hover { color: var(--acc-2); }
button { font-family: inherit; color: inherit; cursor: pointer; }
::selection { background: var(--acc); color: var(--acc-ink); }

/* The dot grid. Fixed and pointer-events:none so it never intercepts a click
   and never scrolls — it reads as the surface the app sits on, not content. */
.v2-grid-bg {
  position: fixed; inset: 0; z-index: 0; pointer-events: none;
  background-image: radial-gradient(rgba(255, 255, 255, .028) 1px, transparent 1px);
  background-size: 24px 24px; background-position: center;
}

.brand { font-family: var(--brand); font-weight: 700; letter-spacing: -.035em; }
.disp { font-family: var(--disp); font-weight: 600; letter-spacing: -.01em; }
.serif { font-family: var(--serif); font-style: italic; font-weight: 400; }
.px { font-family: var(--px); text-transform: uppercase; line-height: 1; }
/* tabular figures everywhere a number can sit in a column */
.num { font-variant-numeric: tabular-nums; font-feature-settings: "tnum" 1; }
.pos { color: var(--acc); }
.neg { color: var(--neg); }
.muted { color: var(--ink-3); }
.hidden { display: none !important; }

/* ============================ SHELL ====================================== */
.v2-app {
  position: relative; z-index: 1;
  display: grid; grid-template-columns: var(--sidebar-w) 1fr; min-height: 100vh;
}
.v2-side {
  position: sticky; top: 0; height: 100vh;
  display: flex; flex-direction: column; gap: 4px;
  padding: 18px 12px;
  border-right: 1px solid var(--line);
  background: rgba(7, 10, 8, .6);
}
.v2-brand { display: flex; align-items: center; gap: 11px; padding: 4px 10px 16px; }
.v2-brand .nm { font-size: 15px; color: var(--wordmark); }
.v2-brand .sub { font-size: 7.5px; letter-spacing: .14em; color: var(--acc); margin-top: 4px; }
.v2-nav { display: flex; flex-direction: column; gap: 2px; }
.v2-nav a {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  padding: 10px 12px; border-radius: var(--r-ctl);
  font-size: 12.5px; color: var(--ink-3);
  border: 1px solid transparent; transition: background .14s, color .14s;
}
.v2-nav a:hover { background: rgba(255, 255, 255, .03); color: var(--ink); }
.v2-nav a.on {
  color: var(--ink); font-weight: 700;
  background: var(--acc-bg); border-color: var(--acc-line);
}
.v2-nav .tag { font-family: var(--px); font-size: 7.5px; color: var(--ink-4); }
.v2-nav a.on .tag { color: var(--acc); }
.v2-side-foot { margin-top: auto; padding: 12px 12px 4px; border-top: 1px solid var(--line-soft); }

.v2-main { min-width: 0; padding: clamp(16px, 2.4vw, 28px); }
.v2-topbar {
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px; flex-wrap: wrap; margin-bottom: 18px;
}
.v2-title { font-family: var(--disp); font-size: 22px; }

/* ============================ PRIMITIVES ================================= */
.v2-card {
  border: 1px solid var(--line); border-radius: var(--r-card);
  background: var(--card); overflow: hidden;
}
.v2-card.acc { border-color: var(--acc-line); background: rgba(87, 207, 146, .03); }
.v2-card.warn { border-color: var(--warn-line); background: var(--warn-bg); }
.v2-card.danger { border-color: var(--neg-line); background: rgba(224, 122, 95, .03); }
.v2-card-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px; flex-wrap: wrap;
  padding: 14px 20px; border-bottom: 1px solid var(--line-soft);
}
.v2-pad { padding: 20px 22px; }

/* A pill states the BASIS of a number or a risk. Colour is the verdict:
   accent = verified/clean, neg = a caveat that changes how to read the card,
   warn = provisional. Restyle freely, but keep the three meanings distinct. */
.v2-pill {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 9.5px; padding: 3px 8px; border-radius: var(--r-pill);
  border: 1px solid var(--line); color: var(--ink-3); white-space: nowrap;
}
.v2-pill.ok { color: var(--acc-2); background: var(--acc-bg); border-color: var(--acc-line); }
.v2-pill.bad { color: var(--neg-2); background: var(--neg-bg); border-color: var(--neg-line); }
.v2-pill.warn { color: var(--warn); background: var(--warn-bg); border-color: var(--warn-line); }
.v2-pill.solid { color: var(--acc-ink); background: var(--acc); border-color: var(--acc); font-weight: 700; }

.v2-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--acc); }
.v2-dot.live { animation: v2pulse 1.4s infinite; }
/* The dot was green in every state, so an ops row saying "2 open tickets" or a
   stale backup still read as healthy. State has to be visible in the FORM, not
   only in the number beside it. `idle` = we could not read the source, which is
   deliberately neither green nor red — unknown is its own state. */
.v2-dot.bad { background: var(--neg); animation: none; }
.v2-dot.idle { background: var(--ink-4); animation: none; }
@keyframes v2pulse { 0%, 100% { opacity: 1; transform: scale(1); } 50% { opacity: .25; transform: scale(.82); } }

.v2-btn {
  font-size: 11px; border-radius: var(--r-ctl); padding: 9px 15px;
  background: transparent; border: 1px solid var(--line); color: var(--ink-2);
  transition: border-color .14s, color .14s;
}
.v2-btn:hover { border-color: rgba(255, 255, 255, .3); color: var(--ink); }
.v2-btn.primary { background: var(--acc); border-color: var(--acc); color: var(--acc-ink); font-weight: 700; }
.v2-btn.primary:hover { background: var(--acc-2); border-color: var(--acc-2); color: var(--acc-ink); }
.v2-btn.danger { color: var(--neg); border-color: var(--neg-line); }
.v2-btn.danger:hover { color: var(--neg-2); border-color: var(--neg); }
.v2-btn.lg { font-size: 14px; padding: 15px 26px; }

/* Stat tiles share one hairline grid: the 1px gap IS the border, so tiles
   never double-rule against the card edge. */
.v2-stats {
  display: grid; gap: 1px; background: var(--line-soft);
  border-top: 1px solid var(--line-soft);
}
.v2-stats > div { background: var(--card); padding: 11px 14px; }
.v2-stats .k { font-family: var(--px); font-size: 7px; color: var(--ink-4); }
.v2-stats .v { font-family: var(--disp); font-size: 16px; margin-top: 4px; font-variant-numeric: tabular-nums; }
.v2-stats-3 { grid-template-columns: repeat(3, 1fr); }

.v2-row {
  display: grid; align-items: center; gap: 12px;
  padding: 11px 20px; border-bottom: 1px solid var(--line-hair);
}
.v2-row:last-child { border-bottom: none; }
.v2-thead { padding: 9px 20px; border-bottom: 1px solid var(--line-soft); }
.v2-thead span { font-family: var(--px); font-size: 8px; color: var(--ink-4); }

.v2-empty { padding: 26px 22px; color: var(--ink-4); font-size: 12.5px; text-align: center; }

/* Charts are canvas. Give the element a height in the markup, not here — each
   surface wants a different one, and a global height silently squashes them. */
canvas { display: block; }

/* A value that just changed flashes its cell. 500ms, then back to transparent —
   long enough to catch, short enough not to strobe on a fast tick. */
.v2-flash { transition: background .5s; padding: 2px 4px; border-radius: 4px; }
.v2-flash.up { background: rgba(87, 207, 146, .2); }
.v2-flash.down { background: rgba(224, 122, 95, .2); }

/* toggles */
.v2-sw { flex: none; width: 38px; height: 22px; border-radius: 99px; position: relative; transition: background .18s; background: rgba(120, 180, 140, .16); }
.v2-sw.on { background: var(--acc); }
.v2-sw i { position: absolute; top: 3px; left: 3px; width: 16px; height: 16px; border-radius: 50%; background: #cfd6cb; transition: transform .18s, background .18s; }
.v2-sw.on i { background: var(--acc-ink); transform: translateX(16px); }

@media (max-width: 1000px) {
  .v2-app { grid-template-columns: 1fr; }
  .v2-side {
    position: static; height: auto; flex-direction: row; flex-wrap: wrap;
    align-items: center; border-right: none; border-bottom: 1px solid var(--line);
  }
  .v2-side-foot { margin-top: 0; border-top: none; padding: 4px 12px; }
  .v2-nav { flex-direction: row; flex-wrap: wrap; }
}
@media (max-width: 900px) {
  .v2-2col, .v2-cards { grid-template-columns: 1fr !important; }
}

/* Range input. The browser default is a blue track and thumb — off-brand on the
   single most-manipulated control in the product, so both are restyled. The
   track gradient is set inline per-value by v2.js; only the geometry and the
   thumb live here. */
input[type=range] {
  -webkit-appearance: none; appearance: none;
  width: 100%; height: 6px; border-radius: 99px; outline: none; cursor: pointer;
  background: linear-gradient(90deg, var(--acc) 0%, var(--acc-2) var(--fill, 100%), rgba(120, 180, 140, .14) var(--fill, 100%));
}
input[type=range]::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 22px; height: 22px; border-radius: 50%;
  background: var(--acc-2); border: 3px solid var(--bg);
  box-shadow: 0 0 0 1px var(--acc), 0 4px 14px rgba(0, 0, 0, .55);
  cursor: grab;
}
input[type=range]::-webkit-slider-thumb:active { cursor: grabbing; }
input[type=range]::-moz-range-thumb {
  width: 20px; height: 20px; border-radius: 50%;
  background: var(--acc-2); border: 3px solid var(--bg); cursor: grab;
}
input[type=range]:focus-visible { box-shadow: 0 0 0 3px var(--acc-bg); }

/* ── uPlot theming ────────────────────────────────────────────────────────
   The library ships a light default; these override it onto the v2 tokens.
   Scoped under .v2-chart so nothing here can reach another surface. */
.v2-chart { position: relative; width: 100%; }
.v2-chart .u-wrap { position: relative; }
.v2-chart .u-axis { color: var(--ink-4); }
.v2-chart .u-select { background: rgba(87, 207, 146, .12); }
.v2-chart .u-cursor-x, .v2-chart .u-cursor-y {
  border-color: rgba(255, 255, 255, .18) !important;
}
.v2-chart .u-legend { display: none; }

/* Cursor readout. Absolutely positioned inside the chart box, transform-driven
   so it never triggers layout while tracking the pointer. */
.v2-tip {
  position: absolute; top: 0; left: 0; z-index: 5;
  pointer-events: none; opacity: 0; transition: opacity .12s;
  display: flex; gap: 8px; align-items: baseline;
  background: rgba(10, 13, 11, .94); border: 1px solid var(--line);
  border-radius: 6px; padding: 4px 8px; white-space: nowrap;
  font-size: 11px; font-variant-numeric: tabular-nums;
}
.v2-tip b { color: var(--ink); font-weight: 700; }
.v2-tip span { color: var(--ink-4); font-size: 10px; }

/* Range selector. Rendered only for the windows the DATA can actually fill —
   a daily whale curve never offers 24H, an hourly portfolio curve does. */
.v2-ranges { display: inline-flex; gap: 3px; flex-wrap: wrap; }
.v2-range {
  font-family: var(--px); font-size: 8px; letter-spacing: .04em;
  padding: 4px 7px; border-radius: var(--r-pill);
  background: transparent; border: 1px solid var(--line); color: var(--ink-3);
  transition: border-color .12s, color .12s, background .12s;
}
.v2-range:hover { border-color: rgba(255, 255, 255, .28); color: var(--ink); }
.v2-range.on { background: var(--acc); border-color: var(--acc); color: var(--acc-ink); }

/* Form controls. Added 2026-07-30 with the first WRITE surfaces in v2 (risk
   guards, invite code, chat). Until then the app was read-only and had none —
   an unstyled input is a white box on a near-black page, which is both ugly
   and, with the default black text, perfectly legible in a way that makes it
   look like someone else's UI dropped in. */
.v2-in {
  background: var(--card-2);
  border: 1px solid var(--line);
  border-radius: var(--r-ctl);
  color: var(--ink);
  font-family: var(--mono);
  font-size: 12.5px;
  padding: 9px 11px;
  outline: none;
}
.v2-in::placeholder { color: var(--ink-5); }
.v2-in:focus { border-color: var(--acc-line); box-shadow: 0 0 0 2px var(--acc-bg); }
.v2-in:disabled { opacity: .5; cursor: not-allowed; }
textarea.v2-in { resize: vertical; min-height: 64px; line-height: 1.5; }

/* Inline status line next to a save button. */
.msg { font-size: 11.5px; color: var(--ink-3); }
.msg.ok { color: var(--acc); }
.msg.error { color: var(--neg); }
