/* Palette matches lib/dashboard.py's self-contained dashboards, so the app's
   own chrome doesn't jar against the pages it links out to.

   Chosen with the dataviz skill's method: chrome (bg/panel/line/ink) is a
   plain lightness/warmth lift off the original near-black navy (was
   #0b0f17) -- not CVD-sensitive, just less of a void. --queued is a new
   slot, split out from --soft specifically because the two need to stay
   apart from --accent under color-blindness simulation (the skill's
   validator script needs Node >=14 for its ES module syntax; this VPS has
   v10, so the checks were run via a Python port of the same OKLab/Machado
   CVD-simulation math instead -- same thresholds, same result). Verified:
   done/#up vs failed/#down (the pair that matters most) clears the CVD
   target comfortably (protan dE 25.6, deutan 14.8, target 8.0); queued
   vs running clears the harder normal-vision floor (dE 15.6, floor 15.0)
   only at this exact shade -- lighter grays read better on their own but
   drift back under the floor. queued vs failed sits in the 6-8 "floor
   band" under protan (7.5) -- per the skill's own rule that's legal only
   with a redundant non-color cue, which every status pill already has
   (the state name is spelled out as text, never color alone). */
:root{
  --bg:#12161f; --panel:#1b2230; --panel2:#161c28; --line:#2f3b52;
  --ink:#eef1f7; --soft:#a7b2c6; --faint:#8996ab; --queued:#727d90;
  --up:#3fd7c0; --down:#e5645f; --accent:#5a9ef5; --amber:#f5b642;
}
*{box-sizing:border-box}
.htmx-indicator{opacity:0; transition:opacity 150ms ease-in}
.htmx-request.htmx-indicator, .htmx-request .htmx-indicator{opacity:1}
body{
  background:var(--bg); color:var(--ink); margin:0;
  font-family:-apple-system,system-ui,'Inter',sans-serif; font-size:14px;
  display:flex; min-height:100vh; align-items:stretch;
}
.sidebar{
  flex:0 0 230px; width:230px; padding:18px 14px;
  border-right:1px solid var(--line); background:var(--panel2);
  display:flex; flex-direction:column; gap:18px;
  position:sticky; top:0; align-self:flex-start; height:100vh; overflow-y:auto;
}
.brand{
  font-weight:700; font-size:17px; letter-spacing:.02em;
  display:flex; align-items:center; gap:10px;
  padding-bottom:14px; border-bottom:1px solid var(--line);
}
.brand img{filter:drop-shadow(0 0 14px rgba(90,158,245,.75)) saturate(1.3)}
/* only shown for a real logged-in session (auth.py) -- local/tunnel
   access has current_user.username == 'local' and never sees this */
.sidebar-user{
  display:flex; align-items:center; justify-content:space-between; gap:8px;
  font-size:11.5px; color:var(--faint); padding-bottom:14px; border-bottom:1px solid var(--line);
}
/* server-side auth.require_admin/require_analyst is the real boundary
   (this is just UI polish so a role can't see a button it'd 403 on) --
   .admin-only: 'admin' can use it, hidden for 'analyst' AND 'read-only'.
   .analyst-only: 'admin' or 'analyst' can use it (e.g. the Predict page's
   run buttons), hidden only for 'read-only'. Never hidden for local/
   tunnel access, which is always role='admin' (see base.html's
   <body class="role-...">). */
body.role-read-only .admin-only, body.role-analyst .admin-only{display:none}
body.role-read-only .analyst-only{display:none}
/* the universe/slice picker sits right under the brand, ABOVE the page nav
   -- it sets the context every page below it acts on, so it reads as the
   thing you set first, not an afterthought at the bottom. */
.sidebar-universe{border-bottom:1px solid var(--line); padding-bottom:16px}
.sidebar-universe label{
  display:block; color:var(--faint); font-size:11px; text-transform:uppercase;
  letter-spacing:.05em; margin-bottom:6px;
}
.sidebar-universe select{max-width:none}
/* as-of date sits right under the universe picker, same border-bottom
   rhythm -- it's the second half of "context", not a separate concern. */
.sidebar-asof{border-bottom:1px solid var(--line); padding-bottom:16px}
.sidebar-asof label{
  display:block; color:var(--faint); font-size:11px; text-transform:uppercase;
  letter-spacing:.05em; margin-bottom:6px;
}
.sidebar-asof input[type=date]{max-width:none; width:100%}
.sidebar nav{display:flex; flex-direction:column; gap:2px}
.sidebar nav a{
  color:var(--soft); text-decoration:none; padding:8px 12px; border-radius:8px;
  font-size:13.5px;
}
.sidebar nav a:hover{background:var(--panel); color:var(--ink)}
.sidebar nav a.active{background:var(--panel); color:var(--ink); font-weight:600}
main{flex:1; min-width:0; padding:18px 24px}
.content-inner{max-width:900px; margin:0 auto}
h1{font-size:19px; margin:0 0 4px}
.hint{color:var(--faint); font-size:12.5px; margin-bottom:12px; line-height:1.45}

.panel{
  background:var(--panel); border:1px solid var(--line); border-radius:12px;
  padding:15px 18px; margin-bottom:12px;
}

/* control-page forms deliberately compact -- every extra px of field
   spacing pushes #job-panel's post-submit confirmation further below the
   fold, on pages (Train's universe multiselect + full recipe, Screen's
   stacked forms) that already run long. */
form .field{margin-bottom:9px}
form label{
  display:block; font-size:12.5px; color:var(--soft); margin-bottom:3px;
}
input[type=text], input[type=number], select, textarea{
  background:var(--panel2); border:1px solid var(--line); color:var(--ink);
  border-radius:7px; padding:6px 10px; font-size:13.5px; width:100%;
  max-width:420px; font-family:inherit;
}
input[type=checkbox]{margin-right:7px}
.checkbox-row{display:flex; align-items:center; gap:8px; margin-bottom:6px}
.checkbox-row label{margin:0; color:var(--ink); font-size:13.5px}

/* Charts page: one ticker field to start, "+ add another" appends more
   (app.js's wireChartsTickerFields) -- input + its own remove button
   side by side, one row per ticker. */
.charts-ticker-row{display:flex; align-items:center; gap:6px; margin-bottom:6px}
.charts-ticker-row input{margin-bottom:0}
.charts-ticker-row .btn-sm{flex:0 0 auto; padding:5px 9px}

/* Charts result: tabs (Fundamentals/Technicals/Sentiment/Correlation,
   app.js's wireChartsTabs) + sortable tables (wireSortableTables) --
   same th[data-sort] hook lib/dashboard.py's own standalone dashboards
   already use, so a click-to-sort header behaves identically everywhere
   in this app. */
.tabs-bar{display:flex; gap:2px; border-bottom:1px solid var(--line); margin-bottom:14px}
.tab-btn{background:none; border:none; border-bottom:2px solid transparent; color:var(--faint);
  font-size:13px; padding:8px 12px; cursor:pointer; font-family:inherit}
.tab-btn:hover{color:var(--ink)}
.tab-btn.active{color:var(--ink); border-bottom-color:var(--accent); font-weight:600}
.tab-panel.hidden{display:none}
th[data-sort]{cursor:pointer; user-select:none}
th[data-sort]:hover{color:var(--ink)}
.corr-cell{font-variant-numeric:tabular-nums; text-align:center}

/* Fundamentals/Technicals leading 'signal' column (engine.classify_trend
   via charts.py's _signal_cell) -- same up/down/neutral palette as the
   correlation heatmap above, so "bullish" reads as the same green
   everywhere on this page. Two-class selectors (not just .sig-up) so
   this beats the .faint every other cell in these tables also carries,
   regardless of which rule happens to come later in the file. */
.faint.sig-up{color:var(--up); font-weight:600}
.faint.sig-down{color:var(--down); font-weight:600}
.faint.sig-neutral{color:var(--faint)}

/* Fundamentals/Technicals ticker cell -- click loads that one ticker's
   sentiment into #charts-sentiment-panel (app.js's wireChartsTickerLinks
   just tracks which one is highlighted; the fetch itself is a plain
   hx-get on the button). Styled to read like the old plain <b>TICKER</b>
   text at rest, so this doesn't look like "yet another button" in a
   dense table -- only the hover/selected states reveal it's clickable. */
.ticker-link{background:none; border:none; padding:0; margin:0; font:inherit; font-weight:600;
  color:var(--ink); cursor:pointer}
.ticker-link:hover{color:var(--accent)}
fieldset{border:1px solid var(--line); border-radius:8px; padding:9px 14px; margin-bottom:10px}
legend{color:var(--soft); font-size:12.5px; padding:0 6px}

.btn{
  background:var(--panel2); border:1px solid var(--line); color:var(--ink);
  border-radius:7px; padding:9px 16px; font-size:13.5px; cursor:pointer;
}
.btn:hover:not(:disabled){border-color:var(--accent)}
.btn:disabled{opacity:.45; cursor:not-allowed}
.btn-primary{background:var(--accent); border-color:var(--accent); color:#04101f; font-weight:600}
.btn-sm{padding:5px 11px; font-size:12px}

/* "set as default" star, injected next to single-value <select>s by
   app.js's wireDefaultableSelects -- wraps the select in a flex row so the
   star sits beside it instead of wrapping onto its own line (the select's
   own width:100% otherwise leaves no room on the same line). */
.select-with-default{display:flex; align-items:center; gap:6px; max-width:420px}
.select-with-default select{max-width:none; flex:1; min-width:0}
.default-star{
  background:none; border:none; cursor:pointer; font-size:15px; line-height:1;
  padding:2px 4px; color:var(--soft); flex:0 0 auto;
}
.default-star:hover{color:var(--ink)}
.default-star.is-default{color:var(--accent)}

/* app.js's makeRestoredHint -- shown once, right after a starred value
   silently overwrote the page's own server default, so that override is
   never invisible; clears itself the moment the field is touched (from
   then on it's your own live choice, not a leftover restore). */
.default-restored-hint{
  display:block; color:var(--accent); font-size:11.5px; margin-top:2px;
}

.job-log{margin-top:18px}
.job-row{display:flex; align-items:center; gap:10px; margin-bottom:8px}
.job-status{
  font-size:12px; padding:3px 10px; border-radius:999px; border:1px solid var(--line);
  text-transform:uppercase; letter-spacing:.03em; font-weight:600;
}
.status-running, .status-cancelling{color:var(--accent); border-color:var(--accent)}
.status-queued{color:var(--queued); border-color:var(--queued)}
.status-done{color:var(--up); border-color:var(--up)}
.status-failed{color:var(--down); border-color:var(--down)}
.status-cancelled{color:var(--amber); border-color:var(--amber)}
.job-label{color:var(--faint); font-size:12.5px}
.job-log-lines{
  background:#000; border:1px solid var(--line); border-radius:8px;
  padding:12px 14px; max-height:420px; overflow:auto; font-size:12.5px;
  font-family:ui-monospace,Menlo,Consolas,monospace; color:#c9d4e3;
  white-space:pre-wrap; word-break:break-all; margin:0;
}
.cmd-preview{
  display:inline-block; margin:10px 0 0 12px; padding:8px 12px; vertical-align:top;
  background:var(--panel2); border:1px solid var(--line); border-radius:7px;
  font-size:12px; font-family:ui-monospace,Menlo,Consolas,monospace;
  color:var(--soft); white-space:pre-wrap; word-break:break-all;
}

table{width:100%; border-collapse:collapse; margin-bottom:8px}
th{
  text-align:left; color:var(--faint); font-weight:500; font-size:11px;
  text-transform:uppercase; letter-spacing:.05em; padding:7px 9px;
  border-bottom:1px solid var(--line);
}
td{padding:8px 9px; border-bottom:1px solid var(--panel2); font-size:13px}
tr:hover td{background:var(--panel2)}
a{color:var(--accent)}
.faint{color:var(--faint)}
.warn-box{
  background:rgba(245,182,66,.08); border:1px solid var(--amber); border-radius:8px;
  padding:10px 14px; font-size:13px; margin-bottom:14px; color:var(--ink);
}
.pill{display:inline-block; font-size:11px; padding:2px 8px; border-radius:999px; border:1px solid var(--line)}

/* Interactive line charts (data-dv='line', wired by the DV_WIRE_LINE JS
   below) -- ported verbatim from lib/dashboard.py's own _CSS, since the
   app shares the exact same CSS variable names/values (--bg/--panel/
   --up/--down/etc, see :root above) as every standalone dashboard this
   project generates. First use: the Charts page's cumulative-return
   chart (build_multiline_chart_svg, lib/dashboard.py), same mechanism
   every predict/backtest/study dashboard already uses for its own
   charts -- kept in sync by hand since dashboard.py's own copy has to
   stay a self-contained inline string (those pages are standalone HTML
   files with no external CSS/JS), while this one is a real static asset. */
.dv-tip{position:fixed;display:none;z-index:50;background:var(--panel2);border:1px solid var(--line);
  border-radius:8px;padding:8px 10px;font-size:12px;pointer-events:none;
  box-shadow:0 8px 24px rgba(0,0,0,.4);max-width:260px}
.dv-tip-date{font-size:10.5px;color:var(--faint);font-family:ui-monospace,monospace;
  padding-bottom:5px;margin-bottom:4px;border-bottom:1px solid var(--line)}
.dv-tip-row{display:flex;align-items:baseline;gap:7px;padding:2px 0;white-space:nowrap}
.dv-tip-key{width:12px;height:2px;border-radius:1px;flex:none}
.dv-tip-val{font-variant-numeric:tabular-nums;font-weight:600;color:var(--ink)}
.dv-tip-lbl{color:var(--faint);font-size:11px}
.dv-crosshair{stroke:var(--faint);stroke-width:1;stroke-dasharray:3 3;display:none;pointer-events:none}
[data-dv-mark]{cursor:pointer}
[data-dv-mark].dv-hover,[data-dv-mark]:focus-visible{filter:brightness(1.3)}
svg[data-dv]{outline:none}

/* Universes tab accordion (app/templates/_universes_list.html,
   _universe_detail.html) -- a <details> per entity, summary laid out as a
   flex row (name, meta, actions pushed right via margin-left:auto on the
   last child) so Delete/confirm never wraps under the name. */
.uni-row{
  border:1px solid var(--line); border-radius:8px; padding:2px 12px;
  margin-bottom:8px; background:var(--panel2);
}
.uni-row > summary{
  display:flex; align-items:center; gap:10px; padding:9px 0; cursor:pointer;
  list-style:none;
}
.uni-row > summary::-webkit-details-marker{display:none}
.uni-row > summary::before{content:'▸'; color:var(--faint); font-size:11px}
.uni-row[open] > summary::before{content:'▾'}
.uni-name{font-weight:600}
.uni-actions{margin-left:auto; display:flex; align-items:center; gap:8px; white-space:nowrap}
.detail-body{padding:4px 0 14px}
.detail-sec{margin-bottom:18px}
.detail-sec h2{font-size:12.5px; text-transform:uppercase; letter-spacing:.04em; color:var(--soft); margin:0 0 8px}
.detail-sec .uni-row{margin-left:6px; margin-top:8px}

/* Info-hover: the explanatory blurb that used to sit as a permanent <p
   class="hint"> under every page/section heading, now a small ⓘ button
   next to the heading that reveals the same text in a floating popover on
   hover/focus -- frees up the vertical space the always-visible paragraph
   used to take (same goal as the earlier control-page spacing pass: get
   #job-panel's post-submit confirmation higher on the page). Pure CSS
   (:hover + :focus-within, no JS) so it also works via keyboard (Tab to
   the button, popover follows focus). */
.info-hover{position:relative; display:inline-block; margin-left:8px; vertical-align:middle}
.info-btn{
  background:none; border:1px solid var(--line); color:var(--soft);
  border-radius:50%; width:19px; height:19px; font-size:12px; line-height:1;
  padding:0; cursor:pointer; display:inline-flex; align-items:center; justify-content:center;
}
.info-btn:hover, .info-btn:focus{color:var(--ink); border-color:var(--accent); outline:none}
.info-pop{
  display:none; position:absolute; top:calc(100% + 6px); left:0; z-index:20;
  width:440px; max-width:min(80vw, 440px); background:var(--panel2); border:1px solid var(--line);
  border-radius:8px; padding:10px 14px; font-size:12.5px; line-height:1.5; color:var(--faint);
  box-shadow:0 8px 24px rgba(0,0,0,.35);
}
.info-hover:hover .info-pop, .info-hover:focus-within .info-pop{display:block}
