











/* ── The module background ───────────────────────────────────────────────────
   Painted directly on `.main`, not on a `z-index: -1` pseudo-element.
   The pseudo-element was the reason "there is no background": it sat in an
   isolated stacking context underneath `.shell` (which carries `z-index: 1`)
   and `body::after` (the fixed grid overlay at `z-index: 0`), and at 8–11%
   opacity over a light theme whatever survived was invisible anyway. A real
   background on a real element cannot be covered by anything, and the strength
   below is set to be seen rather than guessed at. */
.main {
  position: relative;
  background-color: var(--bg);
  background-repeat: no-repeat;
  transition: background-color 0.35s ease;}

/* Percentages are written out per theme rather than driven by a variable.
   `color-mix()` with a `var()` percentage is legal but brittle: if the variable
   fails to resolve for any reason the whole declaration is thrown away at
   computed-value time and the background silently disappears — which is the
   exact failure being fixed here, so it is not the place to be clever.

   Dark needs a glow, light needs a wash: 26% of a blue over near-black reads as
   light in the room; the same 26% over white barely registers. */
/* `background-attachment: fixed` is the important part. The previous version
   faded to nothing by 560px, so the colour existed only above the fold — scroll
   down, or open a long module like Diet & Nutrition, and there was no
   background at all. Anchoring the gradients to the viewport instead of the
   document means the tint is there wherever you are on the page, which is what
   makes it read as a theme rather than a header decoration.

   The third layer is a flat wash across the whole column, so even the far
   bottom of a long page is tinted rather than plain --bg. */
:root .main,
:root[data-theme='dark'] .main {
  background-image:
    radial-gradient(1200px 620px at 2% 0%,
      color-mix(in srgb, var(--route-1) 30%, transparent), transparent 72%),
    radial-gradient(1000px 520px at 100% 4%,
      color-mix(in srgb, var(--route-2) 22%, transparent), transparent 70%),
    linear-gradient(180deg,
      color-mix(in srgb, var(--route-1) 8%, transparent),
      color-mix(in srgb, var(--route-2) 6%, transparent));
  background-attachment: fixed, fixed, fixed;}
:root[data-theme='light'] .main {
  background-image:
    radial-gradient(1200px 620px at 2% 0%,
      color-mix(in srgb, var(--route-1) 32%, transparent), transparent 72%),
    radial-gradient(1000px 520px at 100% 4%,
      color-mix(in srgb, var(--route-2) 24%, transparent), transparent 70%),
    linear-gradient(180deg,
      color-mix(in srgb, var(--route-1) 10%, transparent),
      color-mix(in srgb, var(--route-2) 8%, transparent));
  background-attachment: fixed, fixed, fixed;}

/* Cards sit ON the tint now, so they have to stay opaque and slightly lifted or
   the page turns to mush. That is handled in section 5 with the rest of the
   card styling — one `.card` rule in this file, not two.

   The header band takes the module colour as a solid gradient rather than a
   translucent panel over a blur: it is sticky, so it passes over the fixed
   background as you scroll, and anything translucent there shimmers. */
.topbar {
  position: sticky;
  top: 0;
  z-index: 30;
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--route-1) 16%, var(--bg)),
    color-mix(in srgb, var(--route-1) 7%, var(--bg)));
  border-bottom: 1px solid color-mix(in srgb, var(--route-1) 30%, var(--line));
  /* styles.css blurs whatever is behind this bar. The bar is opaque now, so the
     blur has nothing to do except cost a GPU pass on every scroll frame. */
  backdrop-filter: none;}
.topbar h2 { display: flex; align-items: center; gap: 9px;}
.topbar h2 > span:first-child {
  display: grid; place-items: center;
  width: 30px; height: 30px; border-radius: 9px;
  font-size: 15px;
  background: color-mix(in srgb, var(--route-1) 15%, transparent);
  border: 1px solid color-mix(in srgb, var(--route-1) 32%, transparent);}

/* `--sidebar-w` is switched to the rail width inside the min-width query below,
   with the rest of rail mode. */

/* ── Contrast ────────────────────────────────────────────────────────────────
   The v2 sidebar drew labels in `--fg-dim` on a background one shade off the
   content area, which in light mode meant grey-on-white with almost no edge
   between the two panels — legible in theory, invisible in practice. Three
   fixes, in order of how much they matter:

     1. The sidebar is now a distinctly different surface from the content, with
        a hard border, so the eye can find its edge.
     2. Labels are `--fg` at 12.5px/550, not `--fg-dim`. Nav labels are the most
        re-read text in the product and there is no reason to dim them.
     3. Group headings moved off `--faint`, which at 9px uppercase was the least
        readable text on the screen.
   ------------------------------------------------------------------------- */
.sidebar {
  background-color: var(--panel);
  background-image:
    radial-gradient(130% 40% at 0% 0%,
      color-mix(in srgb, var(--route-1) 22%, transparent), transparent 68%);
  background-repeat: no-repeat;
  border-right: 1px solid var(--line-2);
  color: var(--fg);
  display: flex;
  flex-direction: column;
  transition: width 0.22s cubic-bezier(0.22, 0.9, 0.3, 1), background-color 0.35s ease;
  overflow: hidden;}
/* White panel against the tinted content area, so the edge between the two is
   unmistakable. In dark mode `--panel` already does that job. */
:root[data-theme='light'] .sidebar {
  background-color: #ffffff;
  box-shadow: 1px 0 0 rgb(10 16 32 / 7%);}
.sb-nav {
  flex: 1 1 auto;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 4px 8px 16px;
  scrollbar-width: thin;}



:root[data-theme='light'] .brand-word {
  background-image: linear-gradient(100deg,
    var(--brand-ink-2) 0%, var(--brand-ink) 34%, var(--brand-ink-2) 68%, var(--brand-ink) 100%);}
:root[data-theme='light'] a,
:root[data-theme='light'] .sb-group:hover,
:root[data-theme='light'] .uf-count.filtered b,
:root[data-theme='light'] .sb-item.active .cnt { color: var(--brand-ink);}

/* ── Head: mark, wordmark, rail toggle ─────────────────────────────────── */
.sb-head { padding: 14px 12px; gap: 10px;}
.sb-title { font-size: 13.5px;}
.sb-sub { color: var(--muted); font-weight: 700; font-size: 9.5px;}
.sb-build { color: var(--muted);}
.sb-mark {
  width: 30px; height: 30px; flex: 0 0 30px;
  background: linear-gradient(135deg,
    color-mix(in srgb, var(--brand-1) 26%, transparent),
    color-mix(in srgb, var(--brand-2) 10%, transparent));
  border: 1px solid var(--brand-line);
  box-shadow: 0 0 18px -6px color-mix(in srgb, var(--brand-1) 55%, transparent);}

/* ── Tool strip: search + rail toggle ──────────────────────────────────── */
.au-sb-tools {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 10px 10px 8px;
  border-bottom: 1px solid var(--line);}
.au-sb-find {
  position: relative;
  flex: 1 1 auto;
  min-width: 0;}
.au-sb-find > span {
  position: absolute; left: 9px; top: 50%; transform: translateY(-50%);
  font-size: 11px; color: var(--faint); pointer-events: none;}
.au-sb-find input {
  width: 100%;
  padding: 7px 9px 7px 26px;
  font-size: 12px;
  font-family: inherit;
  color: var(--fg);
  background: color-mix(in srgb, var(--fg) 5%, transparent);
  border: 1px solid var(--line);
  border-radius: 8px;
  transition: border-color 0.15s ease, box-shadow 0.15s ease, background 0.15s ease;}
.au-sb-find input::placeholder { color: var(--faint);}
.au-sb-find input:focus {
  outline: none;
  background: var(--panel);
  border-color: color-mix(in srgb, var(--brand-1) 55%, transparent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--brand-1) 15%, transparent);}
.au-rail-btn {
  flex: 0 0 auto;
  width: 28px; height: 28px;
  display: grid; place-items: center;
  border-radius: 8px;
  border: 1px solid var(--line);
  background: color-mix(in srgb, var(--fg) 5%, transparent);
  color: var(--muted);
  font-size: 13px;
  cursor: pointer;
  transition: color 0.14s ease, border-color 0.14s ease, background 0.14s ease;}
.au-rail-btn:hover {
  color: var(--brand-1);
  border-color: var(--brand-line);
  background: var(--brand-soft);}

/* ── Groups ────────────────────────────────────────────────────────────── */
.sb-group-wrap { margin-bottom: 2px;}
/* Group headings: bigger, bolder, and in --muted rather than --faint. At 9px
   uppercase with 0.14em tracking, --faint was roughly 2.5:1 against the panel —
   below the 3:1 floor for large text and well below the 4.5:1 for small. */
.sb-group {
  padding: 10px 9px 6px;
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.11em;
  color: var(--muted);
  border-radius: 7px;
  transition: color 0.14s ease, background 0.14s ease;}
.sb-group:hover { background: color-mix(in srgb, var(--fg) 5%, transparent); color: var(--fg);}
.sb-group .chev {
  display: inline-block;
  font-size: 9px;
  opacity: 0.9;
  transition: transform 0.18s cubic-bezier(0.22, 0.9, 0.3, 1);}
.gcount {
  font-size: 9px;
  font-weight: 700;
  padding: 1px 6px;
  border-radius: 99px;
  background: color-mix(in srgb, var(--fg) 9%, transparent);
  color: var(--muted);}

/* Collapse animates instead of snapping. app.js re-renders the whole nav, so
   this is a height transition on a grid row rather than a class dance. */
.sb-items {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 0.24s cubic-bezier(0.22, 0.9, 0.3, 1);}
.sb-group-wrap.open > .sb-items { grid-template-rows: 1fr;}
.sb-items-inner { overflow: hidden; min-height: 0;}

/* ── Items ─────────────────────────────────────────────────────────────── */
/* Full-strength label colour. `--fg-dim` on nav items was the single biggest
   readability problem in the screenshot. */
.sb-item {
  padding: 8px 9px;
  border-radius: 8px;
  gap: 10px;
  font-size: 12.5px;
  font-weight: 550;
  color: var(--fg);}
.sb-item:hover {
  background: color-mix(in srgb, var(--fg) 8%, transparent);
  color: var(--fg);
  transform: translateX(2px);}
.sb-item .ic {
  width: 21px; height: 21px; flex: 0 0 21px;
  display: grid; place-items: center;
  font-size: 13px;
  border-radius: 6px;
  background: color-mix(in srgb, var(--fg) 8%, transparent);
  transition: background 0.14s ease, box-shadow 0.14s ease;}
.sb-item:hover .ic { background: color-mix(in srgb, var(--brand-1) 20%, transparent);}
.sb-item .cnt {
  font-size: 9.5px;
  font-weight: 750;
  padding: 1.5px 6px;
  border-radius: 99px;
  background: color-mix(in srgb, var(--fg) 10%, transparent);
  color: var(--muted);}

.sb-item.active {
  background: linear-gradient(90deg,
    color-mix(in srgb, var(--brand-1) 20%, transparent),
    color-mix(in srgb, var(--brand-1) 4%, transparent) 90%);
  color: var(--fg);
  font-weight: 660;}
.sb-item.active::before {
  content: '';
  position: absolute; left: 0; top: 4px; bottom: 4px;
  width: 3px;
  border-radius: 3px;
  background: linear-gradient(180deg, var(--brand-1), var(--brand-2));
  box-shadow: 0 0 14px -1px color-mix(in srgb, var(--brand-1) 80%, transparent);}
.sb-item.active .ic {
  background: linear-gradient(135deg, var(--brand-1), var(--brand-2));
  box-shadow: 0 0 14px -4px color-mix(in srgb, var(--brand-1) 90%, transparent);}
.sb-item.active .cnt {
  background: var(--brand-soft);
  color: var(--brand-1);}

/* ── Pinning ───────────────────────────────────────────────────────────── */
.au-pin {
  flex: 0 0 auto;
  width: 18px; height: 18px;
  display: grid; place-items: center;
  border: 0; background: none; padding: 0;
  border-radius: 5px;
  font-size: 10px;
  line-height: 1;
  color: var(--faint);
  opacity: 0;
  cursor: pointer;
  transition: opacity 0.14s ease, color 0.14s ease, transform 0.14s ease;}
.sb-item:hover .au-pin { opacity: 0.55;}
.au-pin:hover { opacity: 1 !important; color: var(--brand-1); transform: scale(1.2);}
.au-pin.on { opacity: 1; color: var(--brand-1);}

.au-pinned .sb-group { color: var(--brand-1);}
.au-pinned .sb-item .ic { background: color-mix(in srgb, var(--brand-1) 13%, transparent);}

/* ── No-match state for the nav filter ─────────────────────────────────── */
.au-nav-none {
  padding: 18px 12px;
  text-align: center;
  font-size: 11px;
  color: var(--faint);}
.sb-group-wrap.au-hide, .sb-item.au-hide { display: none !important;}

/* ── Rail mode ───────────────────────────────────────────────────────────────
   Wrapped in a min-width query rather than being undone again lower down. On a
   phone the sidebar is already an off-canvas drawer that covers the screen —
   collapsing it to a 66px strip of icons there would be a worse drawer, not a
   smaller one. So below 901px the rail simply does not exist, and there is
   nothing to revert. */
@media (min-width: 1025px) {

:root[data-nav='rail'] { --sidebar-w: var(--rail-w);}

:root[data-nav='rail'] .sb-title,
:root[data-nav='rail'] .sb-sub,
:root[data-nav='rail'] .au-sb-find,
:root[data-nav='rail'] .sb-item > span:nth-of-type(2),
:root[data-nav='rail'] .sb-item .cnt,
:root[data-nav='rail'] .au-pin,
:root[data-nav='rail'] .gname,
:root[data-nav='rail'] .gcount,
:root[data-nav='rail'] .sb-group .chev,
:root[data-nav='rail'] .sb-user > div,
:root[data-nav='rail'] .sb-build,
:root[data-nav='rail'] #auAll,
:root[data-nav='rail'] #palBtn { display: none !important;}

:root[data-nav='rail'] .sb-head,
:root[data-nav='rail'] .au-sb-tools { justify-content: center; padding-inline: 0;}
:root[data-nav='rail'] .sb-nav { padding-inline: 9px;}
:root[data-nav='rail'] .sb-group {
  height: 1px; padding: 0; margin: 9px 6px;
  background: var(--line); overflow: hidden;}
/* Every group is open in rail mode — there is nothing to collapse when all you
   can see is icons, and styles.css hides closed groups with `visibility`. */
:root[data-nav='rail'] .sb-items { grid-template-rows: 1fr !important; visibility: visible !important;}
:root[data-nav='rail'] .sb-item {
  justify-content: center;
  padding: 8px 0;}
:root[data-nav='rail'] .sb-item .ic { width: 26px; height: 26px; flex-basis: 26px; font-size: 14px;}
:root[data-nav='rail'] .sb-item.active::before { left: -5px;}
:root[data-nav='rail'] .sb-user { justify-content: center; padding: 6px;}

/* The rail is not a memory test: every icon carries its label as a native
   `title`, set by enhance.js.
   A CSS ::after tooltip was the obvious choice and it does not work here — it
   has to overflow the sidebar horizontally, and it sits inside `.sb-items-inner`
   (`overflow: hidden`, required by the group-collapse animation) nested inside
   `.sb-nav` (`overflow-y: auto`, which makes it a clipping container too). The
   tooltip would be sliced off at the rail's edge. The native one is drawn by the
   browser, outside the document, and cannot be clipped. */
:root[data-nav='rail'] .sb-item { position: relative;}

/* The one thing the rail does need: room for the active marker. */
:root[data-nav='rail'] .sb-items-inner { padding-left: 5px;}

}   /* end @media (min-width: 1025px) — rail mode
       1025px, not 901px. styles.css turns the sidebar into an off-canvas drawer
       at 1024px and below; with rail mode starting at 901px there was a 124px
       band where the sidebar was simultaneously a drawer and a 66px icon rail,
       which is neither. The two breakpoints now meet exactly. */

/* ════════════════════════════════════════════════════════════════════════════
   2 · PROFIT AND LOSS COLOUR
   ════════════════════════════════════════════════════════════════════════════
   One rule, applied by enhance.js to every table, card and figure in the
   product: green is money gained, red is money lost, and a zero is neither.
   The weight change matters as much as the hue — colour alone fails for the
   ~8% of men with a red/green deficiency, so gains also carry ▲ and losses ▼. */

/* Weight as well as hue. A red that is the same weight as the text beside it
   reads as a typo; a red that is heavier reads as a number that matters. */
.pnl-pos { color: var(--up) !important; font-weight: 750 !important;}
.pnl-neg { color: var(--down) !important; font-weight: 750 !important;}
.pnl-warn { color: var(--warn) !important; font-weight: 750 !important;}
.pnl-zero { color: var(--muted) !important;}

/* The app's own two classes get the same treatment, so a cell coloured by
   app.js and one coloured here are indistinguishable. */
.pos { color: var(--up) !important; font-weight: 750;}
.neg { color: var(--down) !important; font-weight: 750;}

/* The directional glyph. Small, and it never wraps away from its number. */
.pnl-pos::before,
.pnl-neg::before {
  font-size: 0.76em;
  margin-right: 3px;
  opacity: 0.85;
  display: inline-block;
  transform: translateY(-0.5px);}
.pnl-pos::before { content: '▲';}
.pnl-neg::before { content: '▼';}
.pnl-warn::before { content: '●'; font-size: 0.6em;}
/* Cells that already print their own arrow, or where a glyph would be noise. */
.pnl-noglyph::before { content: none !important;}

/* Cell-level tint: enough to skim a column of two hundred rows and see the
   shape of the book without reading a single figure. Raised from 7–8%, which
   was invisible on both themes and therefore not worth having. */
td.pnl-cell-pos  { background: color-mix(in srgb, var(--up) 14%, transparent) !important;}
td.pnl-cell-neg  { background: color-mix(in srgb, var(--down) 15%, transparent) !important;}
td.pnl-cell-warn { background: color-mix(in srgb, var(--warn) 15%, transparent) !important;}

/* A left edge on losses. Colour alone is the weakest of the available signals,
   and this one survives greyscale printing and every form of colour blindness. */
td.pnl-cell-neg  { box-shadow: inset 2.5px 0 0 var(--down);}
td.pnl-cell-warn { box-shadow: inset 2.5px 0 0 var(--warn);}

/* Totals rows read as a verdict, so they get the strongest treatment. */
tfoot td.pnl-cell-pos, tfoot td.pnl-cell-neg { font-weight: 850;}
tfoot .pnl-pos, tfoot .pos { color: var(--up) !important;}
tfoot .pnl-neg, tfoot .neg { color: var(--down) !important;}

/* Status chips that name a bad outcome. A grey "Overdue" is the one thing on
   the page you most needed to notice. */
.chip.chip-danger {
  background: color-mix(in srgb, var(--down) 18%, transparent) !important;
  border-color: color-mix(in srgb, var(--down) 42%, transparent) !important;
  color: var(--down) !important;
  font-weight: 750;}
.chip.chip-warn {
  background: color-mix(in srgb, var(--warn) 18%, transparent) !important;
  border-color: color-mix(in srgb, var(--warn) 42%, transparent) !important;
  color: var(--warn) !important;
  font-weight: 750;}

/* KPI and stat figures. */
.kpi.is-pos .kpi-val, .stat.is-pos .v { color: var(--up);}
.kpi.is-neg .kpi-val, .stat.is-neg .v { color: var(--down);}
.kpi.is-neg { border-color: color-mix(in srgb, var(--down) 32%, var(--line));}
.kpi.is-pos { border-color: color-mix(in srgb, var(--up) 26%, var(--line));}

/* A legend, printed once per view so the convention is never a guess. */
.au-legend {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  font-size: 10.5px;
  color: var(--muted);
  padding: 4px 0;}
.au-legend i { font-style: normal; display: inline-flex; align-items: center; gap: 4px; font-weight: 700;}
.au-legend .up { color: var(--up);}
.au-legend .dn { color: var(--down);}
.au-legend .wn { color: var(--warn);}

/* ════════════════════════════════════════════════════════════════════════════
   3 · UNIVERSAL FILTER BAR
   ════════════════════════════════════════════════════════════════════════════
   The schema-driven module pages already had per-column filters. The twelve
   hand-written views — Crypto, Strategies, Zerodha, Money, Bots, FIRE and the
   rest — had none, which is where most of the rows actually live. enhance.js
   injects this bar above every table in the product; these are its styles. */

.uf-bar {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  padding: 9px 14px;
  border-bottom: 1px solid var(--line);
  background: color-mix(in srgb, var(--route-1) 4%, var(--panel-2, var(--card)));}
.uf-bar .spacer { flex: 1 1 auto;}

.uf-search { position: relative; flex: 1 1 190px; max-width: 300px; min-width: 150px;}
.uf-search > span {
  position: absolute; left: 10px; top: 50%; transform: translateY(-50%);
  font-size: 11px; color: var(--faint); pointer-events: none;}
.uf-search input {
  width: 100%;
  padding: 6px 10px 6px 28px;
  font-size: 12px;
  font-family: inherit;
  color: var(--fg);
  background: color-mix(in srgb, var(--fg) 5%, transparent);
  border: 1px solid var(--line);
  border-radius: 8px;}
.uf-search input:focus {
  outline: none;
  border-color: color-mix(in srgb, var(--brand-1) 55%, transparent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--brand-1) 14%, transparent);}

.uf-btn {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 5px 11px;
  font-size: 11.5px;
  font-weight: 620;
  font-family: inherit;
  color: var(--fg-dim);
  background: color-mix(in srgb, var(--fg) 5%, transparent);
  border: 1px solid var(--line);
  border-radius: 8px;
  cursor: pointer;
  white-space: nowrap;
  transition: all 0.14s ease;}
.uf-btn:hover { color: var(--fg); border-color: var(--line-2); transform: translateY(-1px);}

/* The screen-capture group when it rides inside a view's own tab strip.
   The strip is a flex row whose spacer pushes the action buttons right, so the
   group only has to keep its own three together and not wrap mid-way on a
   narrow window. */
.uf-shot-inline {
  display: inline-flex; align-items: center; gap: 6px;
  flex: 0 0 auto;
  margin-left: 6px;}
@media (max-width: 720px) {
  /* On a phone the strip already wraps; let the group wrap with it rather than
     force a sideways scroll on the whole toolbar. */
  .uf-shot-inline { flex-wrap: wrap; margin-left: 0;}
}
/* In the grouped cockpit strip the capture buttons ride the actions row
   (.tab-bar-end), which is already right-aligned — so they need no pushing of
   their own. Only the fallback case, where a view has no actions row, does. */
.tabs2 > [data-shot-bar] { margin-left: auto; align-self: flex-end;}

/* The chart card's own capture pair. It lives in the card heading beside the
   title, stays quiet until the card is hovered, and is always visible on touch
   where there is no hover to reveal it. */
.card-head [data-chart-bar] {
  margin-left: auto; opacity: 0; transition: opacity .15s;}
.card:hover .card-head [data-chart-bar],
.card:focus-within .card-head [data-chart-bar] { opacity: 1;}
@media (hover: none) {
  .card-head [data-chart-bar] { opacity: 1;}
}
.uf-btn.on {
  color: #0a0d14;
  background: linear-gradient(135deg, var(--brand-1), var(--brand-2));
  border-color: transparent;}
:root[data-palette='indigo'] .uf-btn.on,
:root[data-palette='royal'] .uf-btn.on,
:root[data-palette='crimson'] .uf-btn.on { color: #fff;}

.uf-count { font-size: 11px; color: var(--muted); font-variant-numeric: tabular-nums;}
.uf-count b { color: var(--fg); font-weight: 700;}
.uf-count.filtered b { color: var(--brand-1);}

/* Active-filter pills, so you always know why a table is short. */
.uf-pill {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 3px 6px 3px 9px;
  font-size: 10.5px;
  font-weight: 620;
  border-radius: 99px;
  color: var(--brand-1);
  background: var(--brand-soft);
  border: 1px solid var(--brand-line);}
.uf-pill button {
  border: 0; background: none; padding: 0 1px;
  color: inherit; cursor: pointer; font-size: 12px; line-height: 1;
  opacity: 0.7;}
.uf-pill button:hover { opacity: 1;}

/* The per-column control row, sticky beneath the header. */
tr.uf-row th {
  position: sticky;
  top: 34px;
  z-index: 4;
  padding: 5px 8px 6px;
  background: color-mix(in srgb, var(--brand-1) 7%, var(--card));
  border-bottom: 1px solid var(--line);}
.uf-in {
  width: 100%;
  min-width: 54px;
  padding: 4px 7px;
  font-size: 11px;
  font-family: inherit;
  color: var(--fg);
  background: var(--panel, var(--card));
  border: 1px solid var(--line);
  border-radius: 6px;}
.uf-in:focus {
  outline: none;
  border-color: color-mix(in srgb, var(--brand-1) 60%, transparent);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--brand-1) 16%, transparent);}
.uf-range { display: flex; gap: 4px;}
.uf-range .uf-in { min-width: 0;}

/* Sortable headers on the hand-written tables. */
th.uf-sortable { cursor: pointer; user-select: none; white-space: nowrap;}
th.uf-sortable:hover { color: var(--brand-1);}
th.uf-sortable .uf-arrow { opacity: 0.3; margin-left: 4px; font-size: 9px;}
th.uf-sortable:hover .uf-arrow { opacity: 0.7;}
th.uf-sortable.uf-on { color: var(--brand-1);}
th.uf-sortable.uf-on .uf-arrow { opacity: 1;}

/* Nothing matched. Never a blank rectangle. */
.uf-empty td {
  padding: 34px 20px !important;
  text-align: center;
  color: var(--muted);
  background: none !important;}
.uf-empty .big { font-size: 26px; opacity: 0.45; display: block; margin-bottom: 8px;}
.uf-empty b { color: var(--fg); display: block; margin-bottom: 3px; font-size: 13px;}

/* ════════════════════════════════════════════════════════════════════════════
   4 · DASHBOARD
   ════════════════════════════════════════════════════════════════════════════ */

/* A KPI card is a link. It should say so — cursor, lift, and a chevron that
   appears on hover. The "how this is calculated" panel now lives behind its own
   button, because a card that both navigates and expands on the same click can
   only ever do one of them well. */
.kpi[data-link]:not([data-link='']) { cursor: pointer;}
.kpi { position: relative; transition: transform 0.18s cubic-bezier(0.22, 0.9, 0.3, 1), box-shadow 0.18s ease, border-color 0.18s ease;}
.kpi[data-link]:not([data-link='']):hover {
  transform: translateY(-3px);
  box-shadow: 0 14px 34px -14px color-mix(in srgb, var(--route-1) 60%, rgb(0 0 0 / 60%));
  border-color: color-mix(in srgb, var(--brand-1) 40%, var(--line));}
.kpi[data-link]:not([data-link=''])::after {
  content: '↗';
  position: absolute;
  right: 12px; bottom: 10px;
  font-size: 12px;
  color: var(--brand-1);
  opacity: 0;
  transform: translate(-3px, 3px);
  transition: opacity 0.16s ease, transform 0.16s ease;
  pointer-events: none;
  z-index: 4;}
.kpi[data-link]:not([data-link='']):hover::after { opacity: 0.9; transform: translate(0, 0);}

.au-basis-btn {
  position: absolute;
  top: 9px; right: 9px;
  z-index: 6;
  width: 20px; height: 20px;
  display: grid; place-items: center;
  border-radius: 50%;
  border: 1px solid var(--line-2);
  background: color-mix(in srgb, var(--fg) 8%, transparent);
  color: var(--muted);
  font-size: 10px;
  font-weight: 700;
  cursor: pointer;
  opacity: 0.5;
  transition: all 0.14s ease;}
.au-basis-btn:hover { opacity: 1; color: var(--brand-1); border-color: var(--brand-line); background: var(--brand-soft);}
.kpi.show-basis .au-basis-btn { opacity: 1; color: var(--brand-1);}

/* Anything the app marks as a link should look like one. */
[data-link]:not([data-link='']):not(.kpi) { cursor: pointer;}
tr.clickable:hover td { background: color-mix(in srgb, var(--brand-1) 7%, transparent);}

/* ════════════════════════════════════════════════════════════════════════════
   5 · SHARED POLISH
   ════════════════════════════════════════════════════════════════════════════ */

/* Opaque and lifted. The module tint now runs the full height of the page
   behind these, and a translucent card over a coloured field reads as a smear
   rather than a panel. */
.card {
  border-radius: 14px;
  border: 1px solid var(--line);
  background:
    linear-gradient(180deg, color-mix(in srgb, var(--fg) 2%, transparent), transparent 90px),
    var(--card);
  background-color: var(--card);
  box-shadow: 0 1px 2px rgb(0 0 0 / 6%), 0 10px 26px -20px rgb(0 0 0 / 50%);}
.card-head {
  padding: 13px 16px;
  background: color-mix(in srgb, var(--fg) 2.5%, transparent);}
.card-head h3 { font-size: 13px; letter-spacing: -0.015em;}

/* Rows: quieter zebra, clearer hover, no jitter. */
table.tbl tbody tr { transition: background 0.12s ease;}
table.tbl tbody tr:nth-child(even) td { background: color-mix(in srgb, var(--fg) 2%, transparent);}
table.tbl tbody tr:hover td { background: color-mix(in srgb, var(--brand-1) 9%, transparent);}
/* !important, to match the tint rules above — without it the hover state would
   simply lose and the row would not respond to the cursor at all. */
table.tbl tbody tr:hover td.pnl-cell-pos  { background: color-mix(in srgb, var(--up) 22%, transparent) !important;}
table.tbl tbody tr:hover td.pnl-cell-neg  { background: color-mix(in srgb, var(--down) 24%, transparent) !important;}
table.tbl tbody tr:hover td.pnl-cell-warn { background: color-mix(in srgb, var(--warn) 24%, transparent) !important;}
table.tbl td { padding: 8px 13px; border-bottom: 1px solid color-mix(in srgb, var(--line) 60%, transparent);}
table.tbl tbody tr.row-selected td { background: var(--brand-soft) !important;}

/* ── Form controls must not move when focused ────────────────────────────────
   styles.css has `.input:focus { transform: translateY(-1px) }` with a 160ms
   transition. On a text box that is a harmless flourish. On a `<select>` it is
   the bug behind "the selection is not being done, it flips every time":
   clicking a select focuses it, the element then animates 1px upward, and
   Chrome on Windows dismisses the native dropdown when its anchor moves
   underneath it. You click, the list flashes open, and it is gone.

   A transform on a `<select>` also establishes a containing block, which is
   enough on its own to misplace the popup. There is no version of this effect
   worth a dropdown that will not stay open. */
.input:focus,
select.input:focus,
select:focus,
input:focus,
textarea:focus { transform: none !important;}

/* The focus ring does the same job with no layout cost. */
.input:focus, select:focus {
  border-color: var(--brand-1);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--brand-1) 20%, transparent);
  outline: none;}

/* Selects need to look like selects — the default arrow is easy to lose against
   a tinted background. */
select.input { cursor: pointer;}

/* Focus is visible everywhere, once, in the accent colour. */
:where(a, button, [tabindex]):focus-visible {
  outline: 2px solid var(--brand-1);
  outline-offset: 2px;
  border-radius: 6px;}

/* Skeletons instead of a spinner on a blank page. */
.au-skeleton {
  border-radius: 10px;
  background: linear-gradient(90deg,
    color-mix(in srgb, var(--fg) 5%, transparent) 25%,
    color-mix(in srgb, var(--fg) 10%, transparent) 37%,
    color-mix(in srgb, var(--fg) 5%, transparent) 63%);
  background-size: 400% 100%;
  animation: auShimmer 1.4s ease-in-out infinite;}
@keyframes auShimmer { 0% { background-position: 100% 50%; } 100% { background-position: 0 50%; } }

/* Command palette — it is the primary way to move around, so it should look
   like the product's front door. */
.pal-back { backdrop-filter: blur(8px); background: rgb(3 5 9 / 55%);}
.pal {
  border-radius: 16px;
  border: 1px solid var(--line-2);
  box-shadow: 0 32px 80px -24px rgb(0 0 0 / 70%), var(--glow);
  overflow: hidden;}
.pal-row { border-radius: 9px; transition: background 0.1s ease;}
.pal-row .pal-ic { width: 22px; text-align: center;}

/* ── Diagnostics overlay (Ctrl+Shift+D) ─────────────────────────────────── */
#auDiag {
  position: fixed; inset: 0; z-index: 9999;
  display: grid; place-items: center;
  background: rgb(3 5 9 / 62%);
  backdrop-filter: blur(6px);
  padding: 4vh 4vw;}
.au-diag-box {
  width: min(880px, 100%);
  max-height: 92vh;
  display: flex;
  flex-direction: column;
  background: var(--panel);
  border: 1px solid var(--line-2);
  border-radius: 14px;
  box-shadow: 0 32px 80px -24px rgb(0 0 0 / 70%);
  overflow: hidden;}
.au-diag-head {
  display: flex; align-items: center; gap: 8px;
  padding: 12px 14px;
  border-bottom: 1px solid var(--line);
  color: var(--fg);}
.au-diag-pre {
  margin: 0;
  padding: 14px;
  overflow: auto;
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 11.5px;
  line-height: 1.6;
  color: var(--fg-dim);
  white-space: pre;
  tab-size: 2;}

@media (prefers-reduced-motion: reduce) {
  .main, .sidebar, .sb-items, .kpi, .uf-btn { transition: none !important;}
  .kpi[data-link]:not([data-link='']):hover { transform: none;}
}
:root[data-motion='off'] .main,
:root[data-motion='off'] .sidebar,
:root[data-motion='off'] .sb-items,
:root[data-motion='off'] .kpi { transition: none !important;}
:root[data-motion='off'] .au-skeleton { animation: none;}

/* ── Small screens ───────────────────────────────────────────────────────────
   The rest of the phone and tablet work lives in mobile.css, which loads after
   this file. Only the things that belong to rules defined above are here. */
@media (max-width: 1024px) {
  /* Rail mode is scoped to wide screens, so nothing to undo — the drawer keeps
     its full width whatever the stored preference says. Hide its toggle too:
     a control that does nothing is worse than no control. */
  .au-rail-btn { display: none;}
  .uf-bar { padding: 8px 10px;}
  .uf-search { max-width: none; flex-basis: 100%;}
  .uf-count { order: 99;}
  .topbar h2 > span:first-child { width: 26px; height: 26px; font-size: 13px;}
  /* iOS and several Android browsers either ignore `background-attachment:
     fixed` or repaint it on every scroll frame. Scrolling it with the page is
     the cheaper, more predictable behaviour on a phone. */
  .main { background-attachment: scroll, scroll, scroll !important;}
}


/* â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•
   V7 PHASE 3 â€” MOTION AND HIERARCHY
   ---------------------------------------------------------------------------
   This block only became possible after Phase 1. app.js used to rebuild whole
   subtrees with innerHTML on every state change, so an element never survived
   long enough to be transitioned â€” animations restarted mid-flight or never ran
   at all. render.js now morphs the DOM instead, so elements persist and plain
   CSS transitions do the work.

   Two rules govern everything below:
     1. Motion explains a change; it never decorates one. Every animation here
        answers "what just happened" or "what is connected to what".
     2. Performance first. Only `transform` and `opacity` are animated â€” both
        run on the compositor and never trigger layout. Nothing animates width,
        height, top or left.
   â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â• */

/* â”€â”€ View transition â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
   Route changes fade the new view up rather than snapping it in. Applied to the
   container, not to each child, so it costs one composited layer regardless of
   how many rows the view holds â€” `library` renders 1,390 elements and still
   animates as a single surface. */
@keyframes au-view-in {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: none; }
}
#view.au-entering {
  animation: au-view-in var(--dur-base) var(--ease-out) both;}

/* â”€â”€ KPI cards: staggered entry â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
   --au-i is set per card by app.js. Capped at 12: beyond about ten items a
   stagger stops reading as sequence and starts reading as lag. */
@keyframes au-card-in {
  from { opacity: 0; transform: translateY(10px) scale(.985); }
  to   { opacity: 1; transform: none; }
}
.au-entering .kpi {
  animation: au-card-in var(--dur-slow) var(--ease-spring) both;
  animation-delay: calc(min(var(--au-i, 0), 12) * var(--stagger));}

/* â”€â”€ Card hierarchy â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
   The problem this solves: every dashboard card carried identical weight, so
   "Contacts 0" competed with a four-crore net worth. Attention should follow
   magnitude, not source order.

   .is-hero  â€” the figures the dashboard exists to show.
   .is-quiet â€” cards whose value is zero. Still present, still readable, but
               they stop shouting. Nothing is hidden: a zero is information, it
               just is not headline information. Hovering restores it fully. */
.kpi.is-hero {
  background:
    radial-gradient(120% 140% at 0% 0%, color-mix(in srgb, var(--acc, var(--brand-1)) 14%, transparent) 0%, transparent 58%),
    var(--card);
  border-color: color-mix(in srgb, var(--acc, var(--brand-1)) 34%, var(--line));}
.kpi.is-hero .kpi-val {
  font-size: clamp(22px, 2.4vw, 30px);
  letter-spacing: -0.02em;}

.kpi.is-quiet {
  opacity: .6;
  transition: opacity var(--dur-base) var(--ease-out),
              transform var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) ease;}
.kpi.is-quiet .kpi-val { font-weight: 600; color: var(--muted);}
.kpi.is-quiet:hover { opacity: 1;}

/* â”€â”€ Label truncation â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
   "COLLECTED THIS MOâ€¦" told nobody anything. Two lines, then ellipsis. */
.kpi-lbl {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  white-space: normal;
  line-height: 1.25;}

/* â”€â”€ Number bump â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
   countUp() already added .bump when a figure finished counting, but nothing
   styled it, so the count simply stopped. A brief scale marks the landing. */
@keyframes au-bump {
  0%   { transform: none; }
  38%  { transform: scale(1.055); }
  100% { transform: none; }
}
.kpi-val.bump, .stat .v.bump {
  animation: au-bump var(--dur-slow) var(--ease-spring);
  transform-origin: left center;}

/* â”€â”€ Skeleton loading â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
   A spinner says "something is happening". A skeleton says "a table is coming
   and it is about this big", which stops the layout jumping when data lands. */
@keyframes au-shimmer { to { background-position-x: -200%; } }
.au-skel {
  background: linear-gradient(90deg,
    var(--panel-2) 40%,
    color-mix(in srgb, var(--fg) 7%, var(--panel-2)) 50%,
    var(--panel-2) 60%) 0 0 / 200% 100%;
  border-radius: 7px;
  animation: au-shimmer 1.25s linear infinite;}
.au-skel-row  { height: 34px; margin-bottom: 8px;}
.au-skel-card { height: 104px;}
.au-skel-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); gap: 12px; margin-bottom: 18px;}

/* â”€â”€ Table rows â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
   Row hover keyed to the module accent, so the highlight belongs to the screen
   you are on rather than being a generic grey. */
.tbl tbody tr { transition: background-color var(--dur-fast) ease;}
.tbl tbody tr:hover { background: color-mix(in srgb, var(--acc, var(--brand-1)) 7%, transparent);}

/* â”€â”€ Buttons: press feedback â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
   Depressing on :active is the cheapest confirmation that a click registered,
   and it removes the "did that work?" pause before the response arrives. */
.btn, .uf-btn, .icon-btn, .tab, .mtab, .chip {
  transition: transform var(--dur-instant) var(--ease-out),
              background-color var(--dur-fast) ease,
              border-color var(--dur-fast) ease,
              color var(--dur-fast) ease;}
.btn:active, .uf-btn:active, .icon-btn:active { transform: translateY(1px) scale(.985);}

/* â”€â”€ Sidebar â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
   The active marker slides between items instead of teleporting, which shows
   which section you came from. */
.sb-item { transition: background-color var(--dur-fast) ease, color var(--dur-fast) ease;}
.sb-item.active::before { transition: transform var(--dur-base) var(--ease-spring);}

/* â”€â”€ Alerts â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
   Alerts sit above the cards and arrive first, so they get their own short
   entry rather than sharing the card stagger. */
@keyframes au-alert-in {
  from { opacity: 0; transform: translateX(-6px); }
  to   { opacity: 1; transform: none; }
}
.au-entering .alert {
  animation: au-alert-in var(--dur-base) var(--ease-out) both;
  animation-delay: calc(min(var(--au-i, 0), 6) * var(--stagger));}

/* â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•
   MOTION OFF
   The switch in Settings and the OS-level reduced-motion preference must both
   silence everything above. Animations are neutralised rather than shortened:
   a 1ms animation still fires events and still costs a frame.
   â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â• */
@media (prefers-reduced-motion: reduce) {
  #view.au-entering,
  .au-entering .kpi,
  .au-entering .alert,
  .kpi-val.bump,
  .stat .v.bump,
  .au-skel { animation: none !important;}
  .btn:active, .uf-btn:active, .icon-btn:active { transform: none !important;}
  .kpi.is-quiet { opacity: .6;}
}

:root[data-motion='off'] #view.au-entering,
:root[data-motion='off'] .au-entering .kpi,
:root[data-motion='off'] .au-entering .alert,
:root[data-motion='off'] .kpi-val.bump,
:root[data-motion='off'] .stat .v.bump,
:root[data-motion='off'] .au-skel { animation: none !important;}

:root[data-motion='off'] .btn:active,
:root[data-motion='off'] .uf-btn:active,
:root[data-motion='off'] .icon-btn:active { transform: none !important;}


/* ── Command palette: record results ──────────────────────────────────────
   Global search returns records as well as modules, so a row now carries a
   second line saying WHY it matched — "Sector: Pharma", "Folder: hdfc bank
   loan docs". Without it every result from the same module looks identical. */
.pal-lbl { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.pal-sub {
  font-size: 10.5px;
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.pal-row.pal-record .pal-ic { opacity: .75; }

/* ═══════════════════════════════ Text size ═══════════════════════════════
   A−− through A++, applied with `zoom` on the root.

   Not a root font-size: these stylesheets are written in px throughout, so
   changing the root size would move the type and leave every padding, gap,
   icon and table cell exactly where it was — which reads as broken, not
   larger. `zoom` scales the layout with the text, which is what someone
   asking for bigger text on a large monitor is actually asking for. */
:root[data-fontsize='xs'] { zoom: 0.85;}
:root[data-fontsize='sm'] { zoom: 0.925;}
:root[data-fontsize='md'] { zoom: 1;}
:root[data-fontsize='lg'] { zoom: 1.075;}
:root[data-fontsize='xl'] { zoom: 1.15;}

.fs-row { display: flex; gap: 4px; padding: 2px 7px 6px;}
.fs-btn {
  flex: 1; display: flex; align-items: baseline; justify-content: center; gap: 1px;
  padding: 5px 0; border: 1px solid var(--line); border-radius: 7px;
  background: transparent; color: var(--muted); cursor: pointer; font-weight: 700;
  line-height: 1; transition: background .12s, color .12s, border-color .12s;}
.fs-btn sup { font-size: .62em; font-weight: 800; letter-spacing: -.04em;}
.fs-btn:hover { color: var(--fg); border-color: var(--brand-1);}
.fs-btn.on { background: var(--brand-1); border-color: var(--brand-1); color: #fff;}

/* ══════════════════════════ The account button ═══════════════════════════
   The sign-out control carried `background: rgba(255,255,255,.12)` and
   `color:#fff` as inline styles. On the dark theme that is a faint white pill
   on a dark panel; on the LIGHT theme it is white-on-white and effectively
   invisible — which is why it could not be found. Tokens, so it follows the
   theme like everything else. */
.sb-whoami {
  flex: 1; min-width: 0; display: flex; flex-direction: column; align-items: flex-start;
  gap: 1px; padding: 2px 4px; margin: -2px 0; border: 1px solid transparent;
  border-radius: 7px; background: transparent; cursor: pointer; text-align: left;}
.sb-whoami:hover { background: color-mix(in srgb, var(--fg) 7%, transparent); border-color: var(--line);}
.sb-whoami .nm, .sb-whoami .rl { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;}
.sb-whoami .nm { font-size: 12px; font-weight: 600; color: var(--fg);}
.sb-whoami .rl { font-size: 9px; color: var(--muted); text-transform: uppercase; letter-spacing: .08em;}
.sb-whoami::after {
  content: '✏'; position: absolute; right: 40px; opacity: 0;
  font-size: 10px; transition: opacity .12s;}
.sb-user { position: relative;}
.sb-whoami:hover::after { opacity: .55;}

.sb-signout {
  flex: 0 0 auto; background: var(--panel-2, color-mix(in srgb, var(--fg) 8%, transparent));
  border: 1px solid var(--line); color: var(--fg); opacity: .85;}
.sb-signout:hover { background: var(--a-rose-s, color-mix(in srgb, #f43f5e 16%, transparent));
  border-color: #f43f5e; color: #f43f5e; opacity: 1;}

/* ════════════════════ Needs your attention — collapsible ═════════════════ */
.alert-head { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; flex-wrap: wrap;}
.alert-toggle {
  display: flex; align-items: center; gap: 8px; flex: 1; min-width: 0;
  padding: 3px 5px; margin-left: -5px; border: 0; border-radius: 8px;
  background: transparent; color: inherit; cursor: pointer; text-align: left;}
.alert-toggle:hover { background: color-mix(in srgb, var(--fg) 6%, transparent);}
.alert-toggle h3 { font-size: 14px; margin: 0;}
.alert-toggle .chev { font-size: 10px; color: var(--muted); width: 10px;}

/* A read alert stays visible but stops shouting — it should not compete with
   the net-worth figure it was pushing down the page. */
.alert.is-read { opacity: .5; filter: saturate(.5);}
.alert.is-read:hover { opacity: .85;}
.alert { position: relative;}
.alert-ack {
  margin-left: auto; flex: 0 0 auto; width: 22px; height: 22px; border-radius: 6px;
  border: 1px solid var(--line); background: transparent; color: var(--muted);
  cursor: pointer; font-size: 11px; line-height: 1; opacity: 0; transition: opacity .12s;}
.alert:hover .alert-ack, .alert-ack:focus-visible { opacity: 1;}
.alert-ack:hover { color: var(--fg); border-color: var(--brand-1);}

/* ══════════════════════ Paper — strip the decoration ═════════════════════
   The token block in tokens.css makes Paper white and black. These rules
   remove the things a token cannot: gradients, glass, glow, the aurora
   backdrop and the coloured accent washes on cards.

   Scoped entirely to [data-theme='paper'] so Dark and Light are untouched.
   Nothing here declares a token — tokens.css remains the only place that does. */
:root[data-theme='paper'] #aurora { display: none;}
:root[data-theme='paper'] .kpi,
:root[data-theme='paper'] .card,
:root[data-theme='paper'] .stat,
:root[data-theme='paper'] .banner,
:root[data-theme='paper'] .sidebar,
:root[data-theme='paper'] .topbar,
:root[data-theme='paper'] .modal,
:root[data-theme='paper'] .sb-items,
:root[data-theme='paper'] .palette-menu,
:root[data-theme='paper'] .bell-panel {
  background: var(--card); background-image: none;
  backdrop-filter: none; -webkit-backdrop-filter: none;
  box-shadow: none; border-color: var(--line);}
:root[data-theme='paper'] .sb-items,
:root[data-theme='paper'] .modal,
:root[data-theme='paper'] .palette-menu,
:root[data-theme='paper'] .bell-panel { box-shadow: 0 2px 10px rgb(0 0 0 / 12%);}

/* The accent wash that tints each card by module colour, and the sheen that
   sweeps across a KPI on entry. Both are pure decoration. */
:root[data-theme='paper'] .kpi::before,
:root[data-theme='paper'] .kpi .shine,
:root[data-theme='paper'] .card::before { display: none;}
:root[data-theme='paper'] .kpi { border-left: 1px solid var(--line);}
:root[data-theme='paper'] .brand-word,
:root[data-theme='paper'] .hero h1,
:root[data-theme='paper'] .sb-title {
  background: none; -webkit-text-fill-color: currentColor; color: var(--fg);}
:root[data-theme='paper'] .hero { background: var(--card); background-image: none;}

/* Chips and buttons go outline-only rather than filled. */
:root[data-theme='paper'] .chip { background: transparent; border: 1px solid var(--line); color: var(--fg-dim);}
:root[data-theme='paper'] .btn.primary { background: var(--fg); border-color: var(--fg); color: #fff;}

/* And the one thing that must NOT flatten: gains and losses keep their colour,
   in tables, totals and KPI deltas alike. */
:root[data-theme='paper'] .pos, :root[data-theme='paper'] .kpi-delta.pos { color: var(--up) !important;}
:root[data-theme='paper'] .neg, :root[data-theme='paper'] .kpi-delta.neg { color: var(--down) !important;}
:root[data-theme='paper'] .chip.up { border-color: var(--up); color: var(--up);}
:root[data-theme='paper'] .chip.down { border-color: var(--down); color: var(--down);}

/* ── Report theme ─────────────────────────────────────────────────────────
   The structural half, for the same reason Paper needs one: tokens can restate
   a colour but cannot remove a gradient, a glass panel, a glow or the aurora
   backdrop, and cannot put a coloured rule down the left edge of a stat card.

   Scoped entirely to [data-theme='report']. Nothing here declares a token. */
:root[data-theme='report'] #aurora { display: none;}
:root[data-theme='report'] .kpi,
:root[data-theme='report'] .card,
:root[data-theme='report'] .stat,
:root[data-theme='report'] .banner,
:root[data-theme='report'] .topbar,
:root[data-theme='report'] .modal,
:root[data-theme='report'] .sb-items,
:root[data-theme='report'] .palette-menu,
:root[data-theme='report'] .bell-panel {
  background: var(--card); background-image: none;
  backdrop-filter: none; -webkit-backdrop-filter: none;
  border: 1px solid var(--line); box-shadow: var(--shadow);}
:root[data-theme='report'] .kpi::before,
:root[data-theme='report'] .kpi .shine,
:root[data-theme='report'] .card::before { display: none;}

/* The signature of the look: a 3px coloured rule down the left edge of every
   stat tile, cycling through the accent set the way a reporting dashboard
   colour-codes its KPI row. :nth-child rather than a class because the markup
   emits plain .stat elements and this is presentation only. */
:root[data-theme='report'] .stat {
  border-left: 3px solid var(--brand-1); border-radius: 0 var(--r-md) var(--r-md) 0;}
:root[data-theme='report'] .stat:nth-child(5n+2) { border-left-color: #ea580c;}
:root[data-theme='report'] .stat:nth-child(5n+3) { border-left-color: #dc2626;}
:root[data-theme='report'] .stat:nth-child(5n+4) { border-left-color: #059669;}
:root[data-theme='report'] .stat:nth-child(5n+5) { border-left-color: #0284c7;}
:root[data-theme='report'] .stat .l {
  font-size: 9.5px; font-weight: 750; letter-spacing: .12em; text-transform: uppercase;
  color: var(--muted);}

/* The hero band keeps its gradient — in this theme it IS the navy banner the
   look is built around — but flattened to two close navies rather than the
   three-stop brand sweep. */
:root[data-theme='report'] .hero {
  background: linear-gradient(100deg, #1e3a8a 0%, #1c4fa8 55%, #2563eb 100%);
  border: 0; color: #fff;}
:root[data-theme='report'] .hero h1,
:root[data-theme='report'] .hero .sub { color: #fff; background: none; -webkit-text-fill-color: #fff;}

/* Sidebar stays navy so the chrome reads as one piece with the hero. */
:root[data-theme='report'] .sidebar {
  background: #16233d; background-image: none; border-right: 1px solid #22304d;}
:root[data-theme='report'] .sidebar,
:root[data-theme='report'] .sidebar .sb-item,
:root[data-theme='report'] .sidebar .sb-group { color: #c7d2e4;}
:root[data-theme='report'] .sidebar .sb-title,
:root[data-theme='report'] .sidebar .brand-word {
  color: #fff; background: none; -webkit-text-fill-color: #fff;}
:root[data-theme='report'] .sidebar .sb-item:hover { background: rgb(255 255 255 / 7%); color: #fff;}
:root[data-theme='report'] .sidebar .sb-item.active { background: #1d4ed8; color: #fff;}

/* Tabs render as the filled navy pills from the reference, not underlines. */
:root[data-theme='report'] .tab {
  border-radius: 7px; border: 1px solid transparent; color: var(--muted); font-weight: 650;}
:root[data-theme='report'] .tab.active {
  background: var(--brand-1); color: #fff; border-color: var(--brand-1);}

:root[data-theme='report'] .btn.primary {
  background: var(--brand-1); border-color: var(--brand-1); color: #fff; background-image: none;}

/* Gains and losses keep their colour here too. */
:root[data-theme='report'] .pos, :root[data-theme='report'] .kpi-delta.pos { color: var(--up) !important;}
:root[data-theme='report'] .neg, :root[data-theme='report'] .kpi-delta.neg { color: var(--down) !important;}

/* ═══════════════════════════ Movement over time ══════════════════════════ */
.mv-row {
  margin-bottom: 18px; padding: 12px 14px; border: 1px solid var(--line);
  border-radius: 12px; background: var(--panel);}
.mv-head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; margin-bottom: 10px;}
.mv-head b { font-size: 13px;}
.mv-cells { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px;}
.mv-cell { padding: 8px 10px; border-radius: 9px; background: var(--panel-2); border: 1px solid var(--line);}
.mv-cell .l { font-size: 9px; font-weight: 750; letter-spacing: .1em; text-transform: uppercase; color: var(--faint);}
.mv-cell .v { font-size: 17px; font-weight: 700; margin: 2px 0 1px;}
.mv-cell.is-empty .v { font-weight: 500;}

/* TradingView frame — fills the modal, keeps a usable aspect on any screen. */
.tv-wrap { width: 100%; height: min(68vh, 560px); min-height: 340px; background: var(--panel-2);}
.tv-wrap iframe { width: 100%; height: 100%; border: 0; display: block;}

/* ═══════════════════════════════════ Panels ══════════════════════════════ */
.pnl { border: 1px solid var(--line); border-radius: 12px; background: var(--panel);
  margin-bottom: 14px; overflow: hidden;}
.pnl-head { display: flex; align-items: center; gap: 8px; padding: 2px 10px 2px 4px;}
.pnl-toggle { flex: 1; min-width: 0; display: flex; align-items: center; gap: 9px;
  padding: 10px 8px; border: 0; background: transparent; color: inherit; cursor: pointer;
  text-align: left; border-radius: 9px;}
.pnl-toggle:hover { background: color-mix(in srgb, var(--fg) 5%, transparent);}
.pnl-toggle .chev { width: 10px; font-size: 10px; color: var(--muted);}
.pnl-title { font-size: 13.5px; font-weight: 700;}
.pnl-body { padding: 0 14px 14px;}
/* `grid-template-rows` rather than display:none so the fold can animate and so
   the DOM patcher still sees the children between renders. */
.pnl.is-shut .pnl-body { display: none;}
.pnl-grip { cursor: grab; color: var(--faint); font-size: 15px; padding: 0 2px; user-select: none;}
.pnl.is-widget:active .pnl-grip { cursor: grabbing;}
.pnl.is-dragging { opacity: .45; outline: 2px dashed var(--brand-1); outline-offset: -2px;}
.pnl-hide { opacity: 0; transition: opacity .12s;}
.pnl:hover .pnl-hide { opacity: .7;}
.pnl-hide:hover { opacity: 1; color: #f43f5e; border-color: #f43f5e;}
.pnl-restore { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; margin: 4px 0 16px;}
[data-motion='off'] .pnl { transition: none;}

/* A widget whose content already has its own card header: no second frame. */
.pnl.is-bare { position: relative; border: 0; background: none; overflow: visible; padding: 0;}
.pnl.is-bare > .pnl-grip-float, .pnl.is-bare > .pnl-hide-float {
  position: absolute; top: 6px; z-index: 3; opacity: 0; transition: opacity .12s;}
.pnl.is-bare > .pnl-grip-float { right: 34px;}
.pnl.is-bare > .pnl-hide-float { right: 8px;}
.pnl.is-bare:hover > .pnl-grip-float, .pnl.is-bare:hover > .pnl-hide-float { opacity: .75;}
#dashWidgets .kpi-grid, #dashWidgets .chart-grid { margin-top: 0;}

/* A symbol that opens on TradingView. Underlined on hover only — every row has
   one, and a table of permanent blue links is unreadable. */
.tv-link { color: inherit; text-decoration: none; border-bottom: 1px dotted var(--line-2);}
.tv-link:hover { color: var(--brand-1); border-bottom-color: var(--brand-1);}

/* Payment QR */
.qr-box { display: flex; gap: 16px; align-items: center; flex-wrap: wrap;
  padding: 14px; border: 1px solid var(--line); border-radius: 12px; background: var(--panel-2);}
.qr-svg { flex: 0 0 auto; line-height: 0; background: #fff; padding: 8px; border-radius: 8px;}
.qr-svg svg { width: 190px; height: 190px; display: block;}
.qr-note { flex: 1; min-width: 200px;}

/* Editable meal cells — visibly editable without shouting about it. */
.mealcell { cursor: text; border-radius: 6px; transition: background .12s, box-shadow .12s;}
.mealcell:hover { background: color-mix(in srgb, var(--brand-1) 7%, transparent);}
.mealcell:focus { outline: 0; background: var(--panel-2);
  box-shadow: inset 0 0 0 2px var(--brand-1); cursor: text;}

/* ══════════════════════════════════════════════════════════════════════════
   Nifty 500 pre-breakout heat map  (#strategies/heatmap)

   Two ramps, and the choice between them is the whole readability story:

   .hm-q1..q7  SEQUENTIAL — one hue, light to dark. For magnitudes that have no
               natural zero (readiness, distance from the 52-week high).

               GREEN since 2026-08-07 (was blue). Every metric that uses this
               ramp is "more is better" — readiness, ATR-to-pivot negated so
               nearest is darkest, and %-from-52-week-high where closest to the
               high is darkest. So green carries the same meaning it does on the
               diverging ramp's positive arm: favourable. Blue said nothing.
               The two ramps do not collide, because a sequential metric has no
               sign to confuse with — there is no red on this scale at all.
   .hm-d1..d7  DIVERGING — red arm, neutral grey middle, GREEN arm. For anything
               measured against the index, where the sign is the point.

   The green arm was blue until 2026-08-07, on the reasoning that green-vs-red is
   the single worst pair for the common colour-vision deficiencies and this view
   asks you to compare ~500 cells at a glance. The owner asked for green/red —
   their screen, their call — so it is now the default, with two things kept from
   the old reasoning rather than thrown away:

     1. The arms differ strongly in LIGHTNESS as well as hue, so the ramp still
        reads as a ramp in greyscale or with red-green CVD. That is what makes a
        red/green scale survivable at all; hue alone would not.
     2. The blue arm is still one click away — the "Colour-blind safe" toggle on
        the heat map sets [data-hmpalette='cvd'] and restores it exactly.

   The midpoint stays neutral grey so "no difference" looks like nothing.

   Seven classes, not more: past roughly seven bins adjacent shades stop being
   separable and the map turns into texture.
   ══════════════════════════════════════════════════════════════════════════ */
/* Text flips to white between q5 and q6, not at the midpoint: green is an
   inherently luminous hue, so a mid-green that looks "dark" still carries enough
   luminance that black text beats white on it. Picked against measured relative
   luminance rather than by eye — q5 is L=0.30 (black reads 7:1), q6 is L=0.16
   (white reads 5:1). Guessing the crossover is how heat maps end up with one
   unreadable band in the middle. */
.hm-q1{background:#e3f6ea;color:#0b0b0b;--hm-ink:#0b0b0b} .hm-q2{background:#c3ecd3;color:#0b0b0b;--hm-ink:#0b0b0b}
.hm-q3{background:#97dcb2;color:#0b0b0b;--hm-ink:#0b0b0b} .hm-q4{background:#5cc48c;color:#0b0b0b;--hm-ink:#0b0b0b}
.hm-q5{background:#2ea86a;color:#0b0b0b;--hm-ink:#0b0b0b} .hm-q6{background:#1a7f4e;color:#fff;--hm-ink:#fff}
.hm-q7{background:#0c4f31;color:#fff;--hm-ink:#fff}
/* red arm: deep red -> red -> light red */
.hm-d1{background:#8f1616;color:#fff;--hm-ink:#fff}    .hm-d2{background:#d92d2d;color:#fff;--hm-ink:#fff}
.hm-d3{background:#f8b4b4;color:#0b0b0b;--hm-ink:#0b0b0b} .hm-d4{background:#f0efec;color:#52514e;--hm-ink:#52514e}
/* green arm: light green -> green -> deep green */
.hm-d5{background:#a5e0b5;color:#0b0b0b;--hm-ink:#0b0b0b} .hm-d6{background:#17a34a;color:#0b0b0b;--hm-ink:#0b0b0b}
.hm-d7{background:#0f5132;color:#fff;--hm-ink:#fff}
/* "No reading for this name" — muted on purpose, but --ink-3 against
   --surface-2 is 2.43:1, which is not muted, it is illegible. --ink-2 still
   reads as secondary next to a coloured tile while staying above AA: the tile
   carries a real symbol the user may want to find, and a name they cannot read
   is the same as a name that is not there. */
.hm-na{background:var(--surface-2);color:var(--ink-2);--hm-ink:var(--ink-2)}

[data-theme='dark'] .hm-q1{background:#0c4f31;color:#fff;--hm-ink:#fff} [data-theme='dark'] .hm-q2{background:#14663f;color:#fff;--hm-ink:#fff}
[data-theme='dark'] .hm-q3{background:#1a7f4e;color:#fff;--hm-ink:#fff} [data-theme='dark'] .hm-q4{background:#2ea86a;color:#0b0b0b;--hm-ink:#0b0b0b}
[data-theme='dark'] .hm-q5{background:#4cc487;color:#0b0b0b;--hm-ink:#0b0b0b} [data-theme='dark'] .hm-q6{background:#86dcae;color:#0b0b0b;--hm-ink:#0b0b0b}
[data-theme='dark'] .hm-q7{background:#c3ecd3;color:#0b0b0b;--hm-ink:#0b0b0b}
/* Dark inverts the lightness structure: the EXTREMES glow, the inner bins recede,
   which is what keeps a dark heat map from reading as a solid block. */
[data-theme='dark'] .hm-d1{background:#f87171;color:#0b0b0b;--hm-ink:#0b0b0b} [data-theme='dark'] .hm-d2{background:#c93c3b;color:#fff;--hm-ink:#fff}
[data-theme='dark'] .hm-d3{background:#7a2a2a;color:#fff;--hm-ink:#fff}    [data-theme='dark'] .hm-d4{background:#383835;color:#c3c2b7;--hm-ink:#c3c2b7}
[data-theme='dark'] .hm-d5{background:#14532d;color:#fff;--hm-ink:#fff}    [data-theme='dark'] .hm-d6{background:#17a34a;color:#0b0b0b;--hm-ink:#0b0b0b}
[data-theme='dark'] .hm-d7{background:#86efac;color:#0b0b0b;--hm-ink:#0b0b0b}

/* ── Colour-blind-safe alternative: the original blue ramps ────────────────
   Must restore BOTH scales. Swapping only the diverging one would leave the
   sequential map green, which is the scale covering ~500 tiles at once — i.e.
   the toggle would look like it worked while fixing the smaller half. */
[data-hmpalette='cvd'] .hm-q1{background:#cde2fb;color:#0b0b0b;--hm-ink:#0b0b0b}
[data-hmpalette='cvd'] .hm-q2{background:#b7d3f6;color:#0b0b0b;--hm-ink:#0b0b0b}
[data-hmpalette='cvd'] .hm-q3{background:#9ec5f4;color:#0b0b0b;--hm-ink:#0b0b0b}
[data-hmpalette='cvd'] .hm-q4{background:#6da7ec;color:#0b0b0b;--hm-ink:#0b0b0b}
[data-hmpalette='cvd'] .hm-q5{background:#3987e5;color:#0b0b0b;--hm-ink:#0b0b0b}
[data-hmpalette='cvd'] .hm-q6{background:#256abf;color:#fff;--hm-ink:#fff}
[data-hmpalette='cvd'] .hm-q7{background:#0d366b;color:#fff;--hm-ink:#fff}
[data-theme='dark'][data-hmpalette='cvd'] .hm-q1{background:#184f95;color:#fff;--hm-ink:#fff}
[data-theme='dark'][data-hmpalette='cvd'] .hm-q2{background:#1c5cab;color:#fff;--hm-ink:#fff}
[data-theme='dark'][data-hmpalette='cvd'] .hm-q3{background:#256abf;color:#fff;--hm-ink:#fff}
[data-theme='dark'][data-hmpalette='cvd'] .hm-q4{background:#3987e5;color:#0b0b0b;--hm-ink:#0b0b0b}
[data-theme='dark'][data-hmpalette='cvd'] .hm-q5{background:#5598e7;color:#0b0b0b;--hm-ink:#0b0b0b}
[data-theme='dark'][data-hmpalette='cvd'] .hm-q6{background:#86b6ef;color:#0b0b0b;--hm-ink:#0b0b0b}
[data-theme='dark'][data-hmpalette='cvd'] .hm-q7{background:#cde2fb;color:#0b0b0b;--hm-ink:#0b0b0b}
[data-hmpalette='cvd'] .hm-d1{background:#b52c2b;color:#fff;--hm-ink:#fff}
[data-hmpalette='cvd'] .hm-d2{background:#e34948;color:#0b0b0b;--hm-ink:#0b0b0b}
[data-hmpalette='cvd'] .hm-d3{background:#f3aead;color:#0b0b0b;--hm-ink:#0b0b0b}
[data-hmpalette='cvd'] .hm-d4{background:#f0efec;color:#52514e;--hm-ink:#52514e}
[data-hmpalette='cvd'] .hm-d5{background:#b7d3f6;color:#0b0b0b;--hm-ink:#0b0b0b}
[data-hmpalette='cvd'] .hm-d6{background:#3987e5;color:#0b0b0b;--hm-ink:#0b0b0b}
[data-hmpalette='cvd'] .hm-d7{background:#184f95;color:#fff;--hm-ink:#fff}
[data-theme='dark'][data-hmpalette='cvd'] .hm-d1{background:#e66767;color:#0b0b0b;--hm-ink:#0b0b0b}
[data-theme='dark'][data-hmpalette='cvd'] .hm-d2{background:#c93c3b;color:#fff;--hm-ink:#fff}
[data-theme='dark'][data-hmpalette='cvd'] .hm-d3{background:#7a2a2a;color:#fff;--hm-ink:#fff}
[data-theme='dark'][data-hmpalette='cvd'] .hm-d5{background:#1c5cab;color:#fff;--hm-ink:#fff}
[data-theme='dark'][data-hmpalette='cvd'] .hm-d6{background:#3987e5;color:#0b0b0b;--hm-ink:#0b0b0b}
[data-theme='dark'][data-hmpalette='cvd'] .hm-d7{background:#86b6ef;color:#0b0b0b;--hm-ink:#0b0b0b}

/* One filter row above everything it scopes — never per-card filters. */
.hm-bar { display:flex; flex-wrap:wrap; gap:10px 16px; align-items:flex-end;
  padding:12px 14px; border:1px solid var(--line); border-radius:var(--radius-sm);
  background:var(--surface); margin-bottom:14px;}
.hm-f { display:flex; flex-direction:column; gap:4px;}
.hm-f > label { font-size:10.5px; letter-spacing:.05em; text-transform:uppercase; color:var(--ink-3);}
.hm-f select, .hm-f input[type=search] { font:inherit; font-size:13px; padding:5px 8px;
  border-radius:8px; border:1px solid var(--line-2); background:var(--surface); color:var(--ink);}
.hm-f input[type=range] { width:118px; accent-color:var(--brand-1);}
.hm-chk { display:flex; align-items:center; gap:6px; font-size:12.5px; color:var(--ink-2); cursor:pointer;}

.hm-group { margin-bottom:14px;}
.hm-ghead { display:flex; align-items:baseline; gap:8px; flex-wrap:wrap;
  margin:0 0 6px; padding-bottom:4px; border-bottom:1px solid var(--line);}
.hm-gname { font-size:12px; font-weight:650; color:var(--ink);}
.hm-gmeta { font-size:11px; color:var(--ink-3);}
/* 2px gap between fills rather than a border on each cell — a drawn border
   round every tile reads as a grid of boxes instead of a field of colour. */
/* 92px, not 78px. At 78px a ten-character NSE symbol (KARURVYSYA, NATIONALUM,
   BAJAJFINSV) had to wrap mid-word onto a second line inside a 41px tile, and
   the reported "letters not readable" was that: the glyphs were legible, the
   WORD was not. The tiles stay dense enough to read the field as a field. */
.hm-cells { display:grid; grid-template-columns:repeat(auto-fill,minmax(100px,1fr)); gap:2px;}
/* Tiles are <a> elements (each one opens its TradingView chart), so the anchor
   defaults have to be undone: the tile's colour is the metric, and a link
   underline would read as part of the data. */
/* NO `color` here — and that omission is load-bearing.
   These tiles became <a> elements when each one was made to open its TradingView
   chart, and the anchor reset originally carried `color:inherit` to kill the
   default link blue. But `.hm-cell` and `.hm-q5` have the SAME specificity and
   this rule sits ~80 lines later, so `inherit` quietly beat every per-bin text
   colour above and the tile labels went near-invisible on the mid-greens.
   Every tile always carries exactly one of .hm-q1..q7 / .hm-d1..d7 / .hm-na, and
   each of those sets a colour chosen against its own background, so the link
   blue can never show through and `inherit` is not needed to suppress it. */
.hm-cell { position:relative; border-radius:5px; padding:5px 5px 4px; min-height:46px; overflow:hidden;
  display:block; text-decoration:none; cursor:pointer;}
/* THE TILE'S TEXT COLOUR, AND WHY IT IS WRITTEN LIKE THIS.
   The comment above turns out to have been wrong on its own terms. Each bin
   does set an ink chosen against its own background — but a single-class
   selector was NOT enough to deliver it. The tiles are <a href> elements, and
   an anchor rule elsewhere in the cascade outranked `.hm-q7`, so every tile
   inherited the same brand gold instead of its own colour. On the pale bins
   that still happened to read; on the dark greens it was #927735 on #0c4f31 —
   2.17:1, effectively invisible — and filtering to "min readiness 70" shows
   ONLY those dark bins, which is why the map read as blank rather than dim.
   Each bin now mirrors its colour into --hm-ink, and this rule delivers it.
   `.hm-cell.hm-cell` repeats the class deliberately: it costs nothing, changes
   no colour, and lifts specificity above any anchor rule so the ink cannot be
   silently overridden again. The children are named explicitly too, so nothing
   inside a tile can inherit from further up the tree. */
.hm-cell.hm-cell,
.hm-cell .hm-sym, .hm-cell .hm-val, .hm-cell .hm-trend, .hm-cell .hm-flag {
  color: var(--hm-ink, #0b0b0b);
}
.hm-cell:hover { filter:brightness(1.08); text-decoration:none;}
.hm-cell:focus-visible { outline:2px solid var(--brand-1); outline-offset:1px;}
/* 12px is the floor at which these read at arm's length; 10.5px was under it,
   and every bin's colour pair already clears WCAG AA (worst 5.01:1), so the
   complaint was never contrast — measure before repainting.
   `word-break:break-all` is gone with it: breaking BHARTIARTL as BHARTIA/RTL is
   what made a legible glyph an illegible name. A symbol that still cannot fit
   is ellipsised, which reads as "there is more" instead of as a typo. */
.hm-cell .hm-sym { font-size:12px; font-weight:700; line-height:1.15; letter-spacing:-.2px;
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;}
.hm-cell .hm-val { font-size:11.5px; font-variant-numeric:tabular-nums; opacity:1;}
.hm-cell .hm-flag { position:absolute; top:2px; right:3px; font-size:9px;}
/* Direction of the score, as a glyph. Colour already carries the metric, so the
   trend cannot borrow it — and a rising 70 and a decaying 70 are opposite
   situations that the tile otherwise draws identically. */
.hm-cell .hm-trend { font-size:9px; opacity:.85;}
/* Holdings and watchlist are marked by ring + glyph, never colour: colour is
   already fully committed to the metric. */
.hm-cell.held  { box-shadow:0 0 0 2px var(--surface), 0 0 0 3.5px var(--ink);}
.hm-cell.watch { box-shadow:0 0 0 2px var(--surface), 0 0 0 3.5px var(--ink-3);}
/* Reserve the glyph's corner. The flag is absolutely positioned, so without this
   a long symbol (KARURVYSYA, NATIONALUM) runs underneath it and reads as clipped. */
.hm-cell.held .hm-sym, .hm-cell.watch .hm-sym { padding-right:11px;}

.hm-strip { overflow-x:auto;}
.hm-strip table { border-collapse:separate; border-spacing:2px;}
.hm-strip th.rowh { text-align:left; font-size:11.5px; font-weight:500; color:var(--ink-2);
  padding-right:8px; white-space:nowrap;}
.hm-strip td.cell { width:22px; height:18px; border-radius:3px; padding:0;}
.hm-strip th.colh { font-size:9.5px; color:var(--ink-3); font-weight:400;
  writing-mode:vertical-rl; height:46px;}
.hm-strip td.delta { font-size:11px; color:var(--ink-2); padding-left:8px;
  white-space:nowrap; font-variant-numeric:tabular-nums;}

/* Scale legend — mandatory wherever colour carries a continuous value. */
.hm-legend { display:flex; align-items:center; flex-wrap:wrap; margin-top:12px;}
.hm-legend .sw { width:32px; height:12px;}
.hm-legend .sw:first-of-type { border-radius:3px 0 0 3px;}
.hm-legend .sw:last-of-type { border-radius:0 3px 3px 0;}
.hm-legend .lab { font-size:11px; color:var(--ink-3); margin:0 8px;}
/* ── Drill-through affordance ─────────────────────────────────────────────
   A stat that leads somewhere has to look like it does, or nobody clicks it.
   Applied only to `.stat.is-drill`, so the boxes with nothing behind them are
   left alone rather than promising a destination they do not have. */
.stat.is-drill { cursor: pointer; position: relative; transition: border-color var(--dur-fast) var(--ease-out), transform var(--dur-fast) var(--ease-out);}
.stat.is-drill::after {
  content: '›'; position: absolute; top: 8px; right: 10px;
  font-size: 15px; line-height: 1; color: var(--faint); opacity: .55;}
.stat.is-drill:hover { border-color: var(--brand-1); transform: translateY(-1px);}
.stat.is-drill:hover::after { opacity: 1; color: var(--brand-1);}
[data-motion='off'] .stat.is-drill:hover { transform: none;}

/* ── Frosted sub-windows ────────────────────────────────────────────────────
   The modal panel itself becomes translucent and blurs whatever is behind it,
   so a form opened over a table still shows where you were.

   Translucency is applied to the PANEL, not to the text. Two rules keep it
   readable: the blur is strong enough that the page underneath reads as
   texture rather than as competing content, and the head/foot bars stay more
   opaque than the body so the title and the buttons never sit over a moving
   pattern. A form you cannot read is not a nicer form.

   backdrop-filter is not universal, so @supports gates it: where it is
   missing the panel stays fully opaque and nothing looks broken.

   The paper and report themes opt out below — both exist to be printed, and a
   translucent panel prints as a grey smear. */
@supports ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .modal-back {
    background: rgb(3 5 9 / 46%);
    backdrop-filter: blur(10px) saturate(120%);
    -webkit-backdrop-filter: blur(10px) saturate(120%);
  }
  .modal {
    background: color-mix(in srgb, var(--panel) 82%, transparent);
    backdrop-filter: blur(22px) saturate(150%);
    -webkit-backdrop-filter: blur(22px) saturate(150%);
    box-shadow: 0 28px 70px -18px rgb(0 0 0 / 55%), 0 0 0 1px rgb(255 255 255 / 6%) inset;
  }
  .modal-head, .modal-foot {
    background: color-mix(in srgb, var(--panel-2) 88%, transparent);
  }
  /* Inputs must stay solid — a translucent field over a busy table is the one
     place this effect actively costs legibility. */
  .modal .input, .modal input, .modal select, .modal textarea, .modal .tbl {
    background-color: var(--surface, var(--panel));
  }
}

/* Print-oriented themes keep flat, opaque panels. */
:root[data-theme='paper'] .modal,
:root[data-theme='report'] .modal,
:root[data-theme='paper'] .modal-back,
:root[data-theme='report'] .modal-back {
  backdrop-filter: none; -webkit-backdrop-filter: none;
  background: var(--panel);
}
:root[data-theme='paper'] .modal-back,
:root[data-theme='report'] .modal-back { background: rgb(15 23 42 / 55%);}

/* ══════════════════════════ the weekly ABP review sheet ══════════════════════
   Deliberately NOT themed.

   This one table is printed, pasted into a mail and read beside the circle's own
   sheet, so it keeps the circle's colours in every theme: cyan headings, peach
   for the two target columns, pale blue for the two provisioning columns, a
   yellow total row and red for a shortfall. Every rule therefore sets its own
   foreground as well as its background — inheriting the dark theme's near-white
   text onto a peach cell is how a "themed" version of this table becomes
   unreadable on exactly the day somebody projects it in a review. */
.abp { border-collapse: collapse; width: 100%; }
.abp th, .abp td { border: 1px solid #7f9db9; padding: 6px 9px; }
.abp thead th {
  background: #00b0f0; color: #002b3a; font-weight: 800;
  text-align: center; line-height: 1.25;
}
.abp thead th.warn-head { color: #c00000; }
.abp tbody td { background: #fff; color: #111; }
.abp tbody tr:nth-child(even) td { background: #f4f8fb; }
.abp td.c-tgt { background: #fbcfad; color: #3a2000; font-weight: 600; }
.abp tbody tr:nth-child(even) td.c-tgt { background: #f7c39c; }
.abp td.c-prov { background: #dce6f1; color: #10243a; }
.abp tbody tr:nth-child(even) td.c-prov { background: #d2dfee; }
.abp td.c-short { background: #fbcfad; color: #c00000; font-weight: 700; }
.abp td.c-short.is-ach { color: #c00000; font-weight: 800; letter-spacing: .2px; }
.abp td.c-blank { background: #f0f0f0; color: #888; font-style: italic; }
.abp tfoot td {
  background: #ff0; color: #000; font-weight: 800; border-color: #7f9db9;
}
.abp tfoot td.c-short { background: #ff0; color: #c00000; }
/* DE CFA subtotals sit in the tfoot, above the grand total: the review reads
   the ranked BBMs, then group against group, then the circle. They must NOT
   look like the grand total — the yellow band means "this is the circle", and
   two yellow bands would invite adding them to it, which is the double count
   this whole area of the codebase exists to prevent. So they get their own
   quieter blue band and a rule under the last one to close the block. */
.abp tfoot tr.abp-grp td {
  background: #dce6f1; color: #10243a; font-weight: 700;
}
.abp tfoot tr.abp-grp td.c-short { background: #cfdcea; color: #c00000; }
/* The rule goes on the TOP of the grand total rather than the bottom of the
   last subtotal: `:last-of-type` counts every `tr` in the tfoot, so it lands on
   the grand total and never on a subtotal at all. */
.abp tfoot tr.abp-grp + tr:not(.abp-grp) td { border-top: 3px solid #00799e; }
.abp .abp-rank {
  display: inline-block; min-width: 22px; text-align: center;
  border-radius: 4px; background: #00b0f0; color: #002b3a; font-weight: 800;
}
.abp .abp-rank.top { background: #00b050; color: #fff; }
.abp .abp-rank.bottom { background: #c00000; color: #fff; }
.abp .abp-sub { color: #55677a; font-size: 11px; }

/* ── the week-wise deck ──────────────────────────────────────────────────────
   Four blocks of three columns, which without a rule between them read as one
   long smear of numbers. The heavy left border is what makes "W2 target" and
   "W1 shortfall" stop being neighbours. The week in progress is tinted so the
   eye lands on it first, and a week that has not begun is greyed so nobody
   reads its empty achievement as a failure. */
.abp td.wk-sep, .abp th.wk-sep { border-left: 3px solid #00799e; }
.abp thead th.wk-grp { background: #0091c8; color: #fff; letter-spacing: .3px; }
.abp thead th.wk-grp.is-now { background: #00b050; color: #fff; }
.abp thead th.wk-grp.is-ahead { background: #8fa7b5; color: #f3f8fb; }
.abp td.wk-ahead { background: #f0f0f0; color: #999; }
.abp tbody tr:nth-child(even) td.wk-ahead { background: #eaeaea; }
.abp td.wk-ok { color: #00701f; font-weight: 700; }

/* Which estate an OLT belongs to. Four classes, one badge each — the word
   alone is not enough, because "BBNL" and "BharatNet" look like the same thing
   in a table and are not. */
.olt-own {
  display: inline-block; padding: 1px 7px; border-radius: 4px;
  font-size: 11px; font-weight: 800; letter-spacing: .3px;
  background: #dce6f1; color: #10243a;
}
.olt-own.tip { background: #d9f2e3; color: #00701f; }
.olt-own.bbnl { background: #fbcfad; color: #a33d00; }
.olt-own.baf { background: #e7dcf5; color: #4b2a7b; }

/* ── DGP leads ───────────────────────────────────────────────────────────────
   The ageing pills. Four bands, and the colour is the whole point: a lead that
   has waited a month is not a slightly worse version of one raised yesterday,
   it is a different problem. Each band also carries its own text colour, so the
   table survives being pasted into a mail as a picture. */
.dgp-band {
  display: inline-block; min-width: 30px; padding: 1px 6px; margin: 0 2px 0 0;
  border-radius: 4px; font-size: 11px; font-weight: 800; text-align: center;
  background: #d9f2e3; color: #00701f;
}
.dgp-band.d8_15 { background: #fff3cd; color: #8a6100; }
.dgp-band.d16_30 { background: #fbcfad; color: #a33d00; }
.dgp-band.d31 { background: #c00000; color: #fff; }
.dgp-bands { margin-top: 3px; }
/* Our two districts, inside the circle-wide table. */
.abp tbody tr.dgp-ours td { background: #d9f2e3; color: #06331a; font-weight: 700; }
.abp tbody tr.dgp-ours td.c-tgt { background: #bfe8cd; }

/* The lead that has waited longest is the one about to become a complaint, so
   it is marked on the row rather than left to its position at the top: filter
   the list to one BBM and "first" means something else, but the row carrying
   the longest wait still says so. Everything already past thirty days sits on a
   softer tint behind it. Mixed against the surface, so both themes hold.

   Written against `table.tbl tbody tr` and not `.tbl tr`, because the even-row
   stripe a few hundred lines up is that specific and would otherwise take back
   every second highlighted row — which is exactly what it did on the first
   attempt. */
table.tbl tbody tr.dgp-aged td { background: color-mix(in srgb, var(--down) 7%, var(--surface)); }
table.tbl tbody tr.dgp-oldest td { background: color-mix(in srgb, var(--down) 16%, var(--surface)); }
/* The frozen first column paints its own background at a higher specificity,
   so the highlight has to be said again there or the row starts striped. */
table.tbl.freeze-first tbody tr.dgp-aged td:first-child {
  background: color-mix(in srgb, var(--down) 7%, var(--surface));
}
table.tbl.freeze-first tbody tr.dgp-oldest td:first-child {
  background: color-mix(in srgb, var(--down) 16%, var(--surface));
  box-shadow: inset 3px 0 0 var(--down), 1px 0 0 var(--line);
}
.dgp-tag {
  display: inline-block; margin-left: 5px; padding: 0 5px; border-radius: 4px;
  background: var(--down); color: #fff; font-size: 9.5px; font-weight: 800;
  letter-spacing: .4px; text-transform: uppercase; vertical-align: middle;
}

/* ── OLT commissioning ───────────────────────────────────────────────────────
   A gate is Yes, WIP or No and nothing else matters about it, so it is a
   three-colour chip rather than a word to be read. The blocker pills use the
   same three, deliberately: on a row where media is No, the media blocker is
   the same red, and the eye connects them without a legend. */
.op-gate {
  display: inline-block; padding: 1px 7px; border-radius: 4px;
  font-size: 10.5px; font-weight: 800; letter-spacing: .2px;
  background: #f0f0f0; color: #666;
}
.op-gate.yes { background: #d9f2e3; color: #00701f; }
.op-gate.wip { background: #fff3cd; color: #8a6100; }
.op-gate.no { background: #fbcfad; color: #a33d00; }
.op-block {
  display: inline-block; min-width: 20px; margin: 0 2px 0 0; padding: 1px 6px;
  border-radius: 4px; font-size: 10.5px; font-weight: 800; text-align: center;
  background: #dce6f1; color: #10243a;
}
/* Ordered as the work has to happen: no media, then no approval, then no IP. */
.op-block.mediafeasibility { background: #c00000; color: #fff; }
.op-block.media { background: #fbcfad; color: #a33d00; }
.op-block.eofficeapproval { background: #fff3cd; color: #8a6100; }
.op-block.ipallotment { background: #dce6f1; color: #10243a; }
.op-block.installation { background: #d9f2e3; color: #00701f; }
/* Locations the registry and the franchisee both failed to place. Greyed, not
   hidden — an unplaced site is one nobody is answerable for. */
.abp tbody tr.op-unplaced td { background: #f0f0f0; color: #555; font-style: italic; }
.op-bar {
  display: inline-block; height: 9px; border-radius: 3px; vertical-align: middle;
  background: var(--up); min-width: 0;
}
.op-bar.plan { background: color-mix(in srgb, var(--ink-3) 35%, transparent); }

/* The export-only bar carries no filters, so it should not look like a control
   strip — just three buttons sitting quietly above the table. */
.uf-bar-slim { padding: 6px 14px; background: transparent; border-bottom: 0; }

/* ── figures, in every module ────────────────────────────────────────────────
   A number in a table is the thing the eye is hunting for, so it is set to be
   found: centred under a centred heading, bold, and a step larger than the
   words beside it. `.num` is the class every view already marks a numeric cell
   with — some 480 of them across the app — so this one rule reaches every
   module and no screen has to be edited to opt in.

   Cells that also carry `.tiny` or `.micro` keep their size. Those are dates,
   codes and footnotes riding in a numeric column, and enlarging them would
   push the actual figures out of a dense row.

   The selectors are as specific as the right-aligning rules they overrule
   (styles.css, theme.css); this file is loaded after both, so equal
   specificity is enough and neither of them has to change. */
table.tbl td.num, table.tbl thead th.num, table.tbl tfoot td.num,
table.abp td.num, table.abp thead th.num, table.abp tfoot td.num {
  text-align: center;
}
table.tbl td.num, table.abp td.num { font-weight: 700; }
table.tbl td.num:not(.tiny):not(.micro),
table.abp td.num:not(.tiny):not(.micro) {
  font-size: var(--fs-lg); line-height: 1.25;
}

/* ═══════════════════════════════ foldable sections + page jumper ════════════
   Added with equipSections() in enhance.js. The richest screens had grown to
   ten screens of scrolling — the CM BTS tab is fourteen cards, the tallest of
   them 1,734px — and sticky table headers do not help when the problem is the
   number of tables rather than the length of one. Every card folds by its
   heading, and any view with four or more of them gets a table of contents.

   Kept here rather than in a sixth stylesheet: aureus.css is the layer that
   already re-skins components product-wide, and tokens.css is for tokens only.
   ========================================================================== */

.card > .card-head { position: relative; }

.card-fold {
  background: none; border: 0; cursor: pointer; padding: 2px 4px; margin-left: 6px;
  color: var(--ink-3, #94a3b8); font-size: 12px; line-height: 1;
  border-radius: 6px; transition: color .15s, background .15s;
}
.card-fold:hover { color: var(--ink, #0f172a); background: var(--surface-2, rgba(0,0,0,.05)); }
.card > .card-head[data-foldable]:hover .card-fold { color: var(--brand-1); }

/* Folding hides everything except the heading. `display:none` rather than a
   height transition on purpose: these cards contain tables of several hundred
   rows, and animating that is a long janky frame for no information. */
.card.folded > *:not(.card-head) { display: none !important; }
.card.folded { padding-bottom: 0; }
.card.folded > .card-head { border-bottom: 0; margin-bottom: 0; }

/* ── the page's own table of contents ─────────────────────────────────────────
   A DRAWER, and closed until asked for. The earlier version sat open over the
   right of every long page at 35% opacity, and a translucent panel still takes
   the click: card buttons and the right-hand columns of wide tables could be
   read and not pressed. So the panel is off-screen behind a slim tab, peeks
   while the pointer is on that tab, and when PINNED the content is given the
   width rather than covered. `pointer-events` is the load-bearing line: a
   closed drawer must not be able to swallow a click, whatever its opacity. */
.sec-nav {
  position: fixed; right: 0; top: 50%; transform: translate(calc(100% + 12px), -50%);
  z-index: 41; display: flex; flex-direction: column;
  max-height: 78vh; padding: 8px; width: 234px;
  background: color-mix(in srgb, var(--surface) 96%, transparent);
  border: 1px solid var(--line); border-right: 0;
  border-radius: 12px 0 0 12px;
  box-shadow: 0 10px 34px -16px rgb(0 0 0 / 55%);
  backdrop-filter: blur(10px);
  opacity: 0; pointer-events: none;
  transition: transform .2s ease, opacity .18s ease;
}
.sec-nav.peek, .sec-nav.pinned { transform: translate(0, -50%); opacity: 1; pointer-events: auto; }
.sec-nav-list { overflow-y: auto; display: flex; flex-direction: column; gap: 1px; min-height: 0; }

.sec-nav-head { display: flex; align-items: center; gap: 6px; padding: 1px 3px 6px; }
.sec-nav-t {
  font-size: 9px; font-weight: 750; letter-spacing: .12em; text-transform: uppercase;
  color: var(--ink-3, #94a3b8); flex: 1;
}
.sec-nav-x {
  border: 0; background: none; cursor: pointer; color: var(--ink-3);
  font-size: 12px; line-height: 1; padding: 2px 5px; border-radius: 6px;
}
.sec-nav-x:hover { background: var(--surface-2); color: var(--ink); }

.sec-nav a {
  display: flex; align-items: flex-start; gap: 7px; padding: 5px 8px; border-radius: 7px;
  font-size: 11.5px; line-height: 1.3; color: var(--ink-2, #475569);
  text-decoration: none; border-left: 2px solid transparent;
}
/* The title wraps rather than being cut: a truncated "Zumba fees billed vs
   colle…" is the one row you cannot identify. */
.sec-nav .sec-nav-l { display: block; overflow-wrap: anywhere; }
.sec-nav .sec-nav-i {
  flex: none; width: 15px; text-align: right; font-size: 9.5px; padding-top: 1px;
  color: var(--ink-3); font-variant-numeric: tabular-nums;
}
.sec-nav a:hover { background: var(--surface-2); color: var(--ink); }
.sec-nav a.on {
  background: color-mix(in srgb, var(--brand-1) 12%, transparent);
  color: var(--ink); font-weight: 650; border-left-color: var(--brand-1);
}
.sec-nav a.on .sec-nav-i { color: var(--brand-1); }
.sec-nav-all {
  margin-top: 6px; padding: 5px 8px; border-radius: 7px; cursor: pointer;
  background: var(--surface-2); border: 1px solid var(--line);
  color: var(--ink-2); font-size: 11px; font-weight: 650;
}
.sec-nav-all:hover { color: var(--ink); border-color: var(--line-2); }

/* The tab: 22px of edge against a 234px panel, and the only thing that stays
   over the page. It carries the section count, so it says what it opens. */
.sec-tab {
  position: fixed; right: 0; top: 50%; transform: translateY(-50%);
  z-index: 42; display: flex; flex-direction: column; align-items: center; gap: 3px;
  width: 22px; padding: 9px 0; cursor: pointer;
  background: color-mix(in srgb, var(--surface) 94%, transparent);
  border: 1px solid var(--line); border-right: 0; border-radius: 8px 0 0 8px;
  color: var(--ink-3); font-size: 10px; line-height: 1;
  box-shadow: 0 6px 18px -12px rgb(0 0 0 / 50%);
  transition: color .15s ease, background .15s ease, right .2s ease;
}
.sec-tab:hover, .sec-tab.on { color: var(--brand-1); background: var(--surface); }
.sec-tab .sec-tab-ic { font-size: 12px; font-weight: 700; }
.sec-tab .sec-tab-n { font-size: 9px; font-weight: 700; font-variant-numeric: tabular-nums; }
.sec-tab.on { right: 234px; }

/* Pinned: the page narrows and keeps all of itself visible, so no figure and no
   button ever sits underneath the panel. */
:root[data-secnav="pinned"] .content { padding-right: 252px; }
:root[data-secnav="pinned"] #netFlash { padding-right: 240px; }

/* Narrow screens have no room for a rail beside the content, and a phone's
   scroll is cheap. The folding still applies — that is the half that matters
   on a small screen. */
@media (max-width: 1100px) {
  .sec-nav, .sec-tab { display: none; }
  :root[data-secnav="pinned"] .content,
  :root[data-secnav="pinned"] #netFlash { padding-right: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .sec-nav, .sec-tab { transition: none; }
}
:root[data-motion='off'] .sec-nav, :root[data-motion='off'] .sec-tab { transition: none; }

/* ════════════════════════════════════════════════════════════════════════
   Markets, calendar and trading analytics
   ────────────────────────────────────────────────────────────────────────
   Everything below is theme-token only — no literal colours except the two
   semantic greens and reds that already exist as --up / --down — so the four
   themes keep working without a fifth set of overrides.
   ════════════════════════════════════════════════════════════════════════ */

/* ── index tiles ─────────────────────────────────────────────────────── */
.ix-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: 12px;
}
.ix-tile {
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: 12px 14px;
  background: var(--surface-2);
  position: relative;
  overflow: hidden;
}
/* A hairline in the direction of the move, down the leading edge. Colour on
   the number alone is easy to miss in a row of four; an edge is peripheral. */
.ix-tile::before {
  content: '';
  position: absolute; inset: 0 auto 0 0; width: 3px;
  background: var(--line-2);
}
.ix-tile.is-up::before   { background: var(--up); }
.ix-tile.is-down::before { background: var(--down); }
.ix-tile.is-empty { opacity: .72; border-style: dashed; }
.ix-name { font-size: 11px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--ink-2); }
.ix-last { font-size: 24px; font-weight: 800; line-height: 1.15; margin-top: 3px; font-variant-numeric: tabular-nums; }
.ix-chg  { font-size: 12.5px; font-weight: 700; margin-bottom: 4px; font-variant-numeric: tabular-nums; }
.ix-chg span { font-weight: 600; opacity: .85; }
.ix-note { margin-top: 4px; line-height: 1.35; }
/* Where the index sits inside its own 52-week range. */
.ix-range {
  height: 4px; border-radius: 99px; background: var(--line-2);
  margin-top: 7px; overflow: hidden;
}
.ix-range > span { display: block; height: 100%; border-radius: 99px; background: var(--brand-1); }

/* ── month calendar ──────────────────────────────────────────────────── */
.cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 6px; }
.cal-head {
  font-size: 10.5px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  color: var(--ink-3); text-align: center; padding: 4px 0;
}
.cal-cell {
  border: 1px solid var(--line);
  border-radius: 10px;
  min-height: 88px;
  padding: 6px 7px;
  background: var(--surface);
  display: flex; flex-direction: column; gap: 3px;
  position: relative;
}
.cal-cell.is-blank { border: none; background: none; }
/* OPEN days carry the weight — they are the ones with a decision on them, and
   a calendar where every square looks the same is a wall of numbers. Closed
   days are dimmed and struck through so the eye skips them. */
.cal-cell.is-open   { background: var(--surface); }
.cal-cell.is-closed { background: var(--bg-2); border-style: dashed; opacity: .78; }
.cal-cell.is-closed .cal-day { text-decoration: line-through; color: var(--ink-3); }
.cal-cell.is-special { border-color: var(--warn); background: var(--warn-bg); opacity: 1; }
.cal-cell.is-today { border-color: var(--brand-1); box-shadow: 0 0 0 2px color-mix(in srgb, var(--brand-1) 22%, transparent); }
.cal-day { font-size: 15px; font-weight: 800; font-variant-numeric: tabular-nums; line-height: 1; }
.cal-cell.is-open .cal-day { color: var(--ink); }
.cal-state {
  font-size: 9px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  color: var(--ink-3);
}
.cal-cell.is-open .cal-state { color: var(--up); }
.cal-cell.is-closed .cal-state { color: var(--down); }
.cal-cell.is-special .cal-state { color: var(--warn); }
.cal-why { font-size: 10px; color: var(--ink-2); line-height: 1.25; }
.cal-ev {
  font-size: 10px; line-height: 1.25; border-radius: 5px; padding: 2px 5px;
  background: var(--info-bg); color: var(--info);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.cal-ev.is-result { background: var(--warn-bg); color: var(--warn); font-weight: 700; }
.cal-ev.is-macro  { background: color-mix(in srgb, var(--brand-1) 14%, transparent); color: var(--brand-1); font-weight: 700; }
.cal-more { font-size: 9.5px; color: var(--ink-3); }

/* A results date inside the next week, in the events table. */
tr.is-hot > td { background: color-mix(in srgb, var(--warn) 9%, transparent); }

/* The event badge that rides on a holdings row. */
.ev-badge {
  display: inline-flex; align-items: center; gap: 3px;
  font-size: 10px; font-weight: 700; border-radius: 6px; padding: 1px 5px;
  border: 1px solid transparent; white-space: nowrap;
}
.ev-badge.is-result { background: var(--warn-bg); color: var(--warn); border-color: color-mix(in srgb, var(--warn) 34%, transparent); }
.ev-badge.is-action { background: var(--info-bg); color: var(--info); border-color: color-mix(in srgb, var(--info) 30%, transparent); }

@media (max-width: 720px) {
  .cal-cell { min-height: 62px; padding: 4px 5px; }
  .cal-why, .cal-ev { display: none; }
  .ix-last { font-size: 20px; }
}

/* Six-column performance tables. The generic .chart-grid packs four across a
   wide screen, which is right for a donut and wrong for a table — three of the
   six columns end up behind a scrollbar. These get half the width each. */
.tbl-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(520px, 1fr));
  gap: 16px;
}
@media (max-width: 900px) { .tbl-grid { grid-template-columns: 1fr; } }

/* The facts under an index tile: label left, value right, one pair per row.
   A four-column table cannot fit inside a 230px tile without truncating a
   price mid-number. */
.ix-facts {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 2px 10px;
  margin-top: 9px;
  font-size: 11px;
  align-items: baseline;
}
.ix-facts > span:nth-child(odd) { color: var(--ink-3); }
.ix-facts > span:nth-child(even) { font-weight: 700; font-variant-numeric: tabular-nums; }

/* A stat tile whose number opens the rows behind it. The affordance has to be
   visible without shouting: a count nobody realises is clickable is the same as
   a count that is not. */
.stat.is-drill { cursor: pointer; transition: border-color .15s, transform .15s, background .15s; }
.stat.is-drill:hover,
.stat.is-drill:focus-visible {
  border-color: color-mix(in srgb, var(--brand-1) 55%, transparent);
  background: color-mix(in srgb, var(--brand-1) 6%, transparent);
  transform: translateY(-1px);
  outline: none;
}
.stat.is-drill .drill-hint { color: var(--brand-1); font-weight: 800; opacity: .55; }
.stat.is-drill:hover .drill-hint { opacity: 1; }

/* ── The OLT drill-down ─────────────────────────────────────────────────────
   Opened from any OLT IP in the cockpit. The IP is the key that ties eight
   separate imports together, so these styles serve one modal that every OLT
   table links into rather than a screen of its own. */
.olt-ip { color: var(--brand-1); text-decoration: none; border-bottom: 1px dotted currentColor; }
.olt-ip:hover { text-decoration: none; border-bottom-style: solid; }

/* Auto-fit rather than a fixed column count: this modal is read on a phone as
   often as on a desk, and the fields it holds vary by OLT (an uninspected one
   has no physical block at all). */
.olt-grid {
  display: grid; gap: 10px 18px; margin: 12px 0;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
}
.olt-two { display: grid; gap: 12px; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); margin: 12px 0; }
.olt-box { border: 1px solid var(--line); border-radius: var(--r-md, 8px); padding: 10px 12px; background: var(--surface-2, transparent); }

/* The three direction-of-travel readings sit together: they are only meaningful
   against each other — sales falling while availability also falls is a
   different conversation from sales falling on a healthy OLT. */
.olt-trend { display: flex; flex-wrap: wrap; gap: 18px; margin: 10px 0 4px; }
.olt-trend > div { font-size: 13px; }

.stat-row { display: grid; gap: 10px; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); margin: 14px 0; }

/* Printing the modal is the PDF route — there is no PDF library in the ERP and
   the browser's own print-to-PDF renders this better than one would. So on
   print everything except the modal is removed, and the modal is unpinned from
   its overlay so it flows across pages instead of being clipped to the
   viewport.

   Only in the app itself. The print documents enhance.js writes (the PDF and
   Print buttons) load these stylesheets to keep the app's look, and they carry
   no modal — this rule hid every child of their body and printed a blank page.
   They mark their root [data-print-doc]; the modal rules skip that document. */
@media print {
  :root:not([data-print-doc]) body > *:not(.modal-back) { display: none !important; }
  :root:not([data-print-doc]) .modal-back { position: static !important; background: none !important; display: block !important; }
  .modal { max-height: none !important; max-width: none !important; width: 100% !important;
    box-shadow: none !important; border: 0 !important; }
  .modal-body { max-height: none !important; overflow: visible !important; }
  .modal-foot, .modal-head button, [data-modal-close] { display: none !important; }
  .alert { break-inside: avoid; }
}

/* Paged FTTH tables (applyFtthPagers in app.js). Rows off the current page stay
   in the table so the filter, the sort and CSV / Excel still see them; the
   exports lift the class for the length of the read (withAllRows, enhance.js). */
tr.pg-off { display: none; }
table.pg-all tr.pg-off { display: table-row; }

/* ═══════════════════ The live network board (Teevra Network Glance) ═══════════════════
   Drawn above the page content by paintNetFlash() only while a network element
   has been down past the alert threshold, so it is never furniture. It flashes
   because it competes with whatever the user actually came to the ERP to do —
   and it stops flashing for anyone who has turned animation off or asked their
   OS for reduced motion, where a steady red bar carries the same message. */
.net-flash {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  margin: 10px 18px 0; padding: 9px 14px; cursor: pointer;
  border: 1px solid var(--down); border-left: 5px solid var(--down);
  border-radius: var(--r-md, 10px);
  background: color-mix(in srgb, var(--down) 14%, var(--panel, transparent));
  animation: nf-pulse 1.7s ease-in-out infinite;
}
.net-flash:hover { background: color-mix(in srgb, var(--down) 22%, var(--panel, transparent)); }
@keyframes nf-pulse {
  0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--down) 55%, transparent); }
  50%      { box-shadow: 0 0 0 7px color-mix(in srgb, var(--down) 0%, transparent); }
}
.net-flash .nf-dot {
  width: 10px; height: 10px; border-radius: 50%; background: var(--down); flex: none;
  animation: nf-blink 1s steps(2, start) infinite;
}
@keyframes nf-blink { 50% { opacity: .25; } }
.net-flash .nf-head {
  font-weight: 800; letter-spacing: .04em; text-transform: uppercase;
  font-size: 12px; color: var(--down);
}
.net-flash .nf-el { font-weight: 700; font-size: 12px; }
.net-flash .nf-worst, .net-flash .nf-stale { font-size: 11px; color: var(--muted, inherit); }
.net-flash .nf-stale { border: 1px dashed var(--line); border-radius: 6px; padding: 1px 6px; }
.net-flash .nf-who { display: flex; gap: 7px; flex-wrap: wrap; align-items: center; }
.net-flash .nf-bbm {
  display: inline-flex; align-items: center; gap: 5px; font-size: 11px;
  padding: 2px 8px; border-radius: 999px;
  border: 1px solid color-mix(in srgb, var(--down) 45%, var(--line));
  background: color-mix(in srgb, var(--down) 8%, transparent);
}
.net-flash .nf-bbm a { text-decoration: none; font-variant-numeric: tabular-nums; }
.net-flash .nf-bbm .nf-n {
  font-weight: 800; font-size: 10px; padding: 0 5px; border-radius: 999px;
  background: var(--down); color: #fff;
}
.net-flash .nf-go { font-size: 11px; font-weight: 750; white-space: nowrap; }
:root[data-motion='off'] .net-flash,
:root[data-motion='off'] .net-flash .nf-dot { animation: none !important; }
@media (prefers-reduced-motion: reduce) {
  .net-flash, .net-flash .nf-dot { animation: none !important; }
}
@media (max-width: 720px) {
  .net-flash { margin: 8px 10px 0; gap: 8px; }
  .net-flash .nf-worst, .net-flash .nf-go { display: none; }
}

/* The Network Glance board on the Teevra tab: one ribbon per element class. */
.ng-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 200px), 1fr)); gap: 12px; }
.ng-card {
  border: 1px solid var(--line); border-radius: var(--r-md, 10px); padding: 11px 13px;
  background: var(--panel-2, transparent); border-top: 3px solid var(--up);
}
.ng-card.bad { border-top-color: var(--down); background: color-mix(in srgb, var(--down) 7%, var(--panel-2, transparent)); }
.ng-card .ng-top { display: flex; align-items: center; gap: 7px; font-weight: 700; font-size: 12px; }
.ng-card .ng-tot { font-size: 22px; font-weight: 800; line-height: 1.25; font-variant-numeric: tabular-nums; }
.ng-card .ng-split { display: flex; gap: 10px; font-size: 11px; }
.ng-bar { height: 5px; border-radius: 999px; background: var(--down); overflow: hidden; margin: 7px 0 5px; }
.ng-bar > i { display: block; height: 100%; background: var(--up); }

/* A figure you can open. Tiles and ribbons that drill get a pointer, a quiet
   arrow that firms up on hover, and the card they point at flashes once when
   you arrive — so a click that scrolls does not look like a click that failed. */
.tv-tile { transition: transform .12s ease, box-shadow .12s ease; }
.tv-tile:hover { transform: translateY(-2px); box-shadow: 0 6px 18px rgba(0, 0, 0, .13); }
.tv-tile:focus-visible { outline: 2px solid var(--brand-1); outline-offset: 2px; }
.tv-arrow { opacity: .4; font-weight: 700; }
.tv-tile:hover .tv-arrow { opacity: 1; }
.tv-flash { animation: tv-flash 1.8s ease-out 1; }
@keyframes tv-flash {
  0%, 100% { box-shadow: none; }
  15%      { box-shadow: 0 0 0 3px color-mix(in srgb, var(--brand-1) 70%, transparent); }
}
:root[data-motion='off'] .tv-tile { transition: none; }
:root[data-motion='off'] .tv-flash { animation: none; outline: 2px solid var(--brand-1); }
@media (prefers-reduced-motion: reduce) {
  .tv-tile { transition: none; }
  .tv-flash { animation: none; outline: 2px solid var(--brand-1); }
}

/* WhatsApp reach buttons. Brand green so the eye finds it without reading —
   this is the control that gets pressed while walking to a meeting. The ↗
   fallback beside it stays quiet: it is for the machine without the app. */
.wa-btn {
  background: #25d366 !important; border-color: #1da851 !important; color: #06301a !important;
  font-weight: 700;
}
.wa-btn:hover { background: #1ebe5b !important; }
.wa-alt { padding-inline: 7px !important; opacity: .65; }
.wa-alt:hover { opacity: 1; }
.wa-ic { text-decoration: none; font-size: 13px; line-height: 1; }

/* An ETF code that opens its TradingView chart.
   Underlined on hover only: in a table of forty rows, forty underlines is
   noise, and the code already reads as the row's identity. The dotted rule
   says "there is something here" without competing with the figures beside
   it, and the arrow appears on hover so the destination is never a surprise. */
.sma-tv {
  color: inherit;
  text-decoration: none;
  border-bottom: 1px dotted currentColor;
  border-bottom-color: color-mix(in srgb, currentColor 35%, transparent);
  cursor: pointer;
}
.sma-tv:hover {
  color: #2962ff;                 /* TradingView's own blue */
  border-bottom-style: solid;
  border-bottom-color: #2962ff;
}
.sma-tv:hover::after { content: " ↗"; font-size: .85em; opacity: .8; }
:root[data-theme="dark"] .sma-tv:hover,
:root:not([data-theme="light"]) .sma-tv:hover { color: #5b8dff; border-bottom-color: #5b8dff; }

/* An action control is interface, not data — so for the length of one export
   read (withDataOnly in enhance.js) the table drops them and the reader keeps
   the number instead of the word "WhatsApp". Without this the green button was
   the most coloured thing in the BBM cell, the picture painted the whole cell
   WhatsApp-green with the name buried inside it, and the CSV carried
   "💬 WhatsApp ↗ 📞 Call" where the mobile number belonged.
   Kept to .tbl on purpose: this must never reach a control the user is looking
   at, only one being read from.
   A .lnk-num is NOT a control: it is the figure itself, made clickable to open
   the records behind it (Clarity pending orders, the CM site counts). Hiding it
   exported those tables with every number blank. */
.tbl.ex-data-only :is(button:not(.lnk-num), .btn, .wa-ic, input, select,
  a[href^="tel:"], a[href^="mailto:"], a[href^="whatsapp:"], a[href*="wa.me"],
  a[href*="api.whatsapp.com"]) { display: none !important; }
.net-flash .wa-ic { filter: saturate(1.2); }

/* The floating capture dock: always in reach, out of the way until opened. */
.uf-dock { position: fixed; right: 16px; bottom: 16px; z-index: 900; display: flex; flex-direction: column; align-items: flex-end; gap: 8px;}
.uf-dock-tog { width: 44px; height: 44px; border-radius: 50%; border: 1px solid var(--line-2, #cbd5e1); background: var(--card, #fff); color: var(--fg, #111); font-size: 20px; cursor: pointer; box-shadow: 0 4px 14px rgba(15,23,42,.25);}
.uf-dock-btns { display: none; flex-direction: column; gap: 6px; padding: 8px; border-radius: 12px; background: var(--card, #fff); border: 1px solid var(--line-2, #cbd5e1); box-shadow: 0 6px 20px rgba(15,23,42,.25);}
.uf-dock.open .uf-dock-btns { display: flex;}
.uf-dock-btns .uf-btn { justify-content: flex-start; white-space: nowrap;}
@media print { .uf-dock { display: none !important;}}
