:root {
  --bg: #10151c; --panel: #1a212b; --panel2: #212a36; --line: #2e3947;
  --text: #dbe4ee; --muted: #8a97a7; --accent: #d9a441; --accent2: #298edb;
  --brand: #298edb; /* brand sky blue — wordmark, fund-card tickers, active tab */
  --pos: #57b47a; --neg: #d0656b; --radius: 10px;
  font-size: 15px;
}
* { box-sizing: border-box; }
body { margin: 0; background: var(--bg); color: var(--text);
  font-family: "Segoe UI", system-ui, -apple-system, sans-serif; line-height: 1.45; }
a { color: var(--accent2); text-decoration: none; }
a:hover { text-decoration: underline; }
/* One predictable keyboard-focus language across ordinary links, controls and
   clickable cards. Component rules may refine it, but never remove it. */
:where(a, button, input, select, textarea, summary):focus-visible {
  outline: 2px solid var(--accent2); outline-offset: 2px;
}
.skip-link { position: fixed; top: .75rem; left: .75rem; z-index: 1000;
  padding: .55rem .8rem; border-radius: 6px; background: #f4f0e6; color: #06152e;
  font-weight: 700; transform: translateY(calc(-100% - 1.5rem)); }
.skip-link:focus { transform: translateY(0); }
h1 { font-size: 1.5rem; margin: .2rem 0 1rem; }
h2 { font-size: 1.15rem; margin: 1.4rem 0 .6rem; }
.small { font-size: .85rem; } .muted { color: var(--muted); }
.pos { color: var(--pos); } .neg { color: var(--neg); }

/* Chrome is ink navy (the brand palette); page surfaces stay neutral. The
   surface vars are re-scoped rather than each rule edited, so everything
   inside the nav — dropdown panels, the search box, the mobile ☰ sheet —
   picks up the ink family from the same rules it always used. */
.topnav { --bg: #061128; --panel: #0a1a39; --panel2: #0e2450; --line: #1d3a6c;
  --text: #c9d4e8; --muted: #93a5c6;
  display: flex; align-items: center; gap: 1.2rem; padding: .55rem 1.2rem;
  background: var(--panel); border-bottom: 1px solid #14305e; flex-wrap: wrap; }
.brand { font-weight: 700; color: var(--accent); font-size: 1.05rem; }
.brand .brandicon { width: 20px; height: 20px; vertical-align: -4px;
  margin-right: .4rem; }

/* --- the mark's cells, and the motion they share with the chat indicator ---
 * One idea in two places: the 2×2 mosaic of cells IS the logo (the sign-in
 * field compressed to four tiles, navies and skies checkerboarded), and those cells
 * flipping over is what "working" looks like — the same motion the sign-in
 * field runs. So the animation is defined once here and worn by both the tile
 * in the nav and the .spin glyph in chat.
 *
 * A cell "flips" by squashing about its own vertical axis — scaleX (not
 * width) so it runs on the compositor. Negative delays start the four
 * mid-cycle and walk the flip clockwise around the mark, or the first frame
 * would be four cells at rest and the motion would look like it began late. */
@keyframes tileflip {
  0%, 100% { transform: scaleX(1); }
  50%      { transform: scaleX(.08); }
}
.brandicon .bmtile { fill: #0a1a39; }
.brandicon .bmedge { fill: none; stroke: #1869b4; stroke-width: 1.5; }
.brandicon .bc { transform-box: fill-box; transform-origin: center; }
.brandicon .bc1 { fill: #102d5e; }
.brandicon .bc2 { fill: #298edb; }
.brandicon .bc3 { fill: #6db3e8; }
.brandicon .bc4 { fill: #153a78; }
/* The stagger lives on the cells themselves and the RUNNING rule sets only
   the longhands — the `animation` shorthand would reset animation-delay back
   to 0 and all four would move as one block. Nor is this a paused animation:
   a negative delay on a paused animation freezes it mid-cycle, so the resting
   mark would be a squashed cell instead of the logo. With no animation-name
   at rest, the mark is exactly the drawn logo. Delay order walks clockwise:
   cells are drawn TL, TR, BL, BR. */
.brandicon .bc1 { animation-delay: 0s; }
.brandicon .bc2 { animation-delay: -.29s; }
.brandicon .bc4 { animation-delay: -.58s; }
.brandicon .bc3 { animation-delay: -.86s; }
/* It moves while you point at it, and only then. The same cells flipping ARE
   the DataChat working indicator, but that motion belongs where the work is —
   the chat panel and the DataChat button — not on the site logo, which is on
   every page of the app. */
.brand:hover .bc, .brand:focus-visible .bc {
  animation-name: tileflip; animation-duration: 1.15s;
  animation-timing-function: ease-in-out; animation-iteration-count: infinite; }
/* Wordmark: cream "Bond", sky "Grid" — the nav-sized echo of the sign-in
   lockup, whose navy tiles carry cream letters and sky tiles ink ones. */
.wordmark { letter-spacing: .005em; }
.wm-bond { color: #f4f0e6; }
.wm-grid { color: #298edb; }
.brand:hover { text-decoration: none; }
.brand:hover .wm-bond { color: #fffbef; }
.brand:hover .wm-grid { color: #55a7e6; }
.navlinks { display: flex; gap: .9rem; flex-wrap: wrap; }
.navlinks a { color: var(--text); }
.navlinks a:hover { color: #f4f0e6; }
.navlinks a[aria-current="page"] { color: var(--brand);
  box-shadow: inset 0 -2px 0 var(--brand); }
.navsep { color: var(--line); }

/* --- admin nav menus ---
 * <details>/<summary>, so the menus open with no script and no inline handler
 * (CSP). name="adminnav" makes them mutually exclusive natively; app.js adds
 * outside-click and Esc close on top. The topnav gets its own stacking context
 * so an open menu paints over the page — below the drawers (z 70/80). */
.topnav { position: relative; z-index: 60; }
.navmenu { position: relative; }
.navmenu > summary { list-style: none; cursor: pointer; color: var(--text);
  user-select: none; white-space: nowrap; }
.navmenu > summary::-webkit-details-marker { display: none; }
.navmenu > summary::after { content: "▾"; margin-left: .25rem; color: var(--muted);
  font-size: .8em; }
.navmenu > summary:hover, .navmenu[open] > summary { color: var(--brand); }
/* the group holding the current page is underlined with an inset shadow rather
   than a border, so marking it cannot change the nav's height */
.navmenu.active > summary { color: var(--brand); box-shadow: inset 0 -2px 0 var(--brand); }
.navlevel.on { color: var(--brand); box-shadow: inset 0 -2px 0 var(--brand); }
.navlevel .ic { margin-right: .28rem; opacity: .85; }
.navmenu > summary:focus-visible { outline: 2px solid var(--accent2); outline-offset: 2px; }
.navmenu-items { position: absolute; top: calc(100% + .55rem); left: -.4rem; z-index: 61;
  min-width: 15rem; display: flex; flex-direction: column;
  background: var(--panel2); border: 1px solid var(--line); border-radius: var(--radius);
  padding: .35rem; box-shadow: 0 10px 26px rgba(0, 0, 0, .55); }
.navmenu-items a { display: flex; flex-direction: column; gap: .05rem;
  padding: .35rem .55rem; border-radius: 6px; color: var(--text); line-height: 1.25; }
.navmenu-items a .small { font-size: .78rem; }
.navmenu-items a:hover { background: var(--panel); text-decoration: none; }
.navmenu-items a.on { color: var(--brand); background: var(--panel); }

/* --- admin section strip ---
 * Sits above the page body on every /admin page: names the area you are in and
 * carries its sibling pages, so moving inside an area never goes through the
 * nav menu. */
.adminbar { display: flex; flex-wrap: wrap; align-items: baseline; gap: .1rem .9rem;
  margin: -.3rem 0 1rem; padding-bottom: .5rem; border-bottom: 1px solid var(--line);
  font-size: .88rem; }
.adminbar-group { color: var(--muted); text-transform: uppercase;
  letter-spacing: .08em; font-size: .74rem; margin-right: .3rem; }
.adminbar a { color: var(--muted); }
.adminbar a:hover { color: var(--text); text-decoration: none; }
.adminbar a.on { color: var(--brand); box-shadow: inset 0 -2px 0 var(--brand); }
/* One glyph per admin page, the same in the strip and in the nav menu, so the
   page you want is found by shape on the second visit rather than by reading
   six labels. The icon never replaces the label — it sits beside it. */
.adminbar .ic, .navmenu-items .ic { margin-right: .34rem; opacity: .85; }

/* in-page section jumps for the long stacked pages (quality) */
.jumps { display: flex; flex-wrap: wrap; gap: .3rem .5rem; margin: 0 0 1.1rem; }
.jumps a { font-size: .8rem; padding: .18rem .55rem; border-radius: 999px;
  border: 1px solid var(--line); color: var(--muted); background: var(--panel); }
.jumps a:hover { color: var(--text); border-color: var(--accent2); text-decoration: none; }
.navcheck, .navtoggle { display: none; } /* mobile-only menu toggle (see bottom) */
.navuser { margin-left: auto; display: flex; gap: .8rem; align-items: center; }
.navsearch input { background: var(--bg); border: 1px solid var(--line); color: var(--text);
  border-radius: 6px; padding: .25rem .6rem; width: 15rem; }
.linkbtn { background: none; border: none; color: var(--accent2); cursor: pointer;
  font: inherit; padding: 0; }
.linkbtn:hover { text-decoration: underline; }

/* Both drawers push the page in from the right rather than covering it. The
   inset rides on --drawer-inset (set by the body.*-open rules further down) and
   is spent as padding, not margin: a margin of `auto` cannot be interpolated,
   so the page used to snap to its narrow width the instant the class landed
   while the drawer was still sliding. Padding animates, so the reflow now runs
   on the same curve as the panel. */
.content { --drawer-inset: 0px; max-width: 2400px; margin: 0 auto;
  padding: 1.4rem calc(1.4rem + var(--drawer-inset)) 3rem 1.4rem;
  transition: padding-right .34s cubic-bezier(.22, .75, .3, 1); }

/* slim, surface-matched scrollbars everywhere (Firefox + WebKit) */
* { scrollbar-width: thin; scrollbar-color: #3a4757 transparent; }
*::-webkit-scrollbar { width: 9px; height: 9px; }
*::-webkit-scrollbar-track { background: transparent; }
*::-webkit-scrollbar-thumb { background: #3a4757; border-radius: 999px;
  border: 2px solid transparent; background-clip: padding-box; }
*::-webkit-scrollbar-thumb:hover { background-color: #4c5c74; }
*::-webkit-scrollbar-corner { background: transparent; }

.cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 1rem; }
.card { background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 1rem 1.1rem; color: var(--text); }
a.card:hover { border-color: var(--accent2); text-decoration: none; }
.fund-ticker { font-size: 1.35rem; font-weight: 700; color: var(--brand); }
.fund-name { color: var(--muted); font-size: .85rem; min-height: 2.4em; }
.card dl { margin: .6rem 0 0; }
.card dl div { display: flex; justify-content: space-between; padding: .12rem 0; }
.card dt { color: var(--muted); }
.card dd { margin: 0; font-variant-numeric: tabular-nums; }
.admin-master .card > span { display: block; }
.admin-master .card .sub { margin-top: .25rem; }
.admin-master-cols { align-items: start; }
.admin-summary-card { display: block; }

table.data { border-collapse: collapse; width: 100%; font-size: .9rem; }
table.data th, table.data td { padding: .4rem .6rem; border-bottom: 1px solid var(--line);
  text-align: left; white-space: nowrap; }
table.data td.num, table.data th.num { text-align: right; font-variant-numeric: tabular-nums; }
table.data thead th { color: var(--muted); font-weight: 600; position: sticky; top: 0;
  background: var(--bg); }
/* --panel2, not --panel: .tablewrap paints itself --panel, so a --panel hover
   inside one was invisible — every table in a wrapper had a hover state that
   did nothing. --panel2 is lighter than both surfaces, so it reads on either. */
table.data tbody tr:hover { background: var(--panel2); }
/* The column a range grid is currently showing (app.js markRangeColumn). A
   translucent tint, not a solid one: cells paint above rows, so this still
   reads as the lit column over the row-hover background. */
table.data td.rangehit { background: rgba(41, 142, 219, .14); }
/* The header stays OPAQUE — it is sticky, and a translucent one would let the
   scrolled rows through. #142637 is that same tint flattened onto --bg. */
table.data thead th.rangehit { background: #142637; color: var(--text); }
.tablewrap { overflow-x: auto; border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--panel); padding: 0 .2rem; -webkit-overflow-scrolling: touch; }

form label { display: block; margin: .7rem 0 .2rem; color: var(--muted); font-size: .9rem; }
input[type=text], input[type=password], input[type=search], input[type=number],
input[type=email], input[type=date], select, textarea {
  width: 100%; max-width: 26rem; background: var(--bg); color: var(--text);
  border: 1px solid var(--line); border-radius: 6px; padding: .45rem .6rem; font: inherit; }
textarea { max-width: 100%; font-family: ui-monospace, monospace; }
/* Primary actions are brand sky with ink text (5:1); amber (--accent) is a
   data-emphasis color only, never an action. */
button, .btn { background: #298edb; color: #06152e; border: none; border-radius: 6px;
  padding: .5rem 1rem; font: inherit; font-weight: 600; cursor: pointer; margin-top: .8rem; }
button.secondary { background: var(--panel2); color: var(--text); border: 1px solid var(--line); }
.alert { background: #3a2530; border: 1px solid #7a3b48; color: #f0c2c8;
  border-radius: 6px; padding: .6rem .9rem; }
.notice { background: #23303c; border: 1px solid #33506a; color: #cfe2f3;
  border-radius: 6px; padding: .6rem .9rem; }

/* .login-box is now the error page's frame only — sign-in has its own
   full-bleed treatment below. */
.login-box { max-width: 22rem; margin: 12vh auto; background: var(--panel);
  border: 1px solid var(--line); border-radius: var(--radius); padding: 1.6rem 1.8rem; }
.login-box h1 { color: var(--accent); }
.login-box input { max-width: 100%; }
.login-box button { width: 100%; }

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

/* ── Sign-in: BondGrid tile lockup over the flipping mosaic (mosaic.js) ────
   Full-bleed, so the page opts out of the .content gutter. Everything here is
   scoped under .signin / .login-page and touches no other page. */
.login-page .content { max-width: none; margin: 0; padding: 0; }
.login-page .content > .alert { margin: 1rem 1.2rem 0; }

.signin { position: relative; overflow: hidden; isolation: isolate;
  min-height: 100vh; min-height: 100svh;
  background:
    radial-gradient(100% 75% at 18% 10%, rgba(41,142,219,.12) 0%, rgba(41,142,219,0) 55%),
    linear-gradient(165deg, #0b2148 0%, #0a1a39 50%, #061128 100%); }
.mosaiccanvas { position: absolute; inset: 0; width: 100%; height: 100%;
  display: block; z-index: 0; }
/* Sits between the field and the type: pulls the middle down just enough for
   the lockup and card to hold contrast over a hot tile, plus a vignette. */
.signin-veil { position: absolute; inset: 0; z-index: 1; pointer-events: none;
  background:
    radial-gradient(70% 55% at 50% 48%, rgba(6,11,22,.5) 0%, rgba(6,11,22,0) 72%),
    radial-gradient(120% 100% at 50% 50%, rgba(3,7,16,0) 45%, rgba(3,7,16,.72) 100%); }

.signin-grid { position: relative; z-index: 2; min-height: 100vh;
  min-height: 100svh; max-width: 1180px; margin: 0 auto;
  padding: 2.5rem 1.6rem; display: grid; gap: 2.5rem 4rem; align-content: center;
  align-items: center; grid-template-columns: 1fr; justify-items: center; }
@media (min-width: 900px) {
  .signin-grid { grid-template-columns: minmax(0, 1fr) 24rem;
    justify-items: stretch; }
  .signin-mark { justify-self: end; }
  .signin-card { justify-self: start; }
}

/* The lockup: a 4×2 block of mosaic tiles, one letter per tile — BOND
   stepping up through the navies, GRID on the brand-sky ramp — sized to sit
   on the backdrop's bigger flipping tiles. The navy tiles carry cream
   letters, the sky tiles ink ones (the nav wordmark echoes that split). */
.gm-block { display: grid; grid-template-columns: repeat(4, max-content);
  gap: .14em; margin: 0; line-height: 1;
  font-size: clamp(2.2rem, 7.2vw, 4.2rem); font-weight: 800;
  font-family: system-ui, 'Segoe UI', Helvetica, Arial, sans-serif; }
.gm-tile { display: grid; place-items: center; width: 1.5em; height: 1.5em;
  border-radius: .16em; }
.gm-b0 { color: #f4f0e6; background: #0d2751; }
.gm-b1 { color: #f4f0e6; background: #102d5e; }
.gm-b2 { color: #f4f0e6; background: #12336b; }
.gm-b3 { color: #f4f0e6; background: #153a78; }
.gm-g0 { color: #06152e; background: #1869b4; }
.gm-g1 { color: #06152e; background: #298edb; }
.gm-g2 { color: #06152e; background: #45a0e2; }
.gm-g3 { color: #06152e; background: #6db3e8; }
.gm-g0, .gm-g1, .gm-g2, .gm-g3 { box-shadow: 0 0 20px rgba(41, 142, 219, .3); }

/* Opener: the tiles pop in — BOND first, GRID overlapping it — timed with
   the field's opening cascade in mosaic.js, and the card arrives last. */
@keyframes gm-pop { from { opacity: 0; transform: scale(.4); }
  60% { opacity: 1; transform: scale(1.08); }
  to { opacity: 1; transform: none; } }
@keyframes gm-rise { from { opacity: 0; transform: translateY(1.2rem); }
  to { opacity: 1; transform: none; } }
.gm-tile { animation: gm-pop .5s cubic-bezier(.2, .9, .3, 1.2) both; }
.gm-b0 { animation-delay: .15s; }
.gm-b1 { animation-delay: .27s; }
.gm-b2 { animation-delay: .39s; }
.gm-b3 { animation-delay: .51s; }
.gm-g0 { animation-delay: .33s; }
.gm-g1 { animation-delay: .45s; }
.gm-g2 { animation-delay: .57s; }
.gm-g3 { animation-delay: .69s; }
.signin-card { animation: gm-rise .8s ease-out 1.5s both; }

.signin-card { width: 100%; max-width: 24rem;
  background: rgba(8, 20, 46, .74); border: 1px solid rgba(41, 142, 219, .30);
  border-radius: 16px; padding: 1.5rem 1.7rem 1.8rem;
  box-shadow: 0 24px 64px rgba(0, 0, 0, .55), inset 0 1px 0 rgba(244, 240, 230, .10);
  backdrop-filter: blur(14px) saturate(120%);
  -webkit-backdrop-filter: blur(14px) saturate(120%); }
.signin-card h2 { margin: 0 0 .3rem; font-size: 1.15rem; color: #f4f0e6;
  letter-spacing: .01em; }
.signin-card label { color: #a8bcd8; }
.signin-card input { max-width: 100%; background: rgba(4, 12, 28, .72);
  border-color: rgba(41, 142, 219, .35); color: #eaf3fa; }
.signin-card input:focus-visible { outline: 2px solid #298edb; outline-offset: 1px;
  border-color: #298edb; }
.signin-card button { width: 100%; margin-top: 1.2rem;
  box-shadow: 0 6px 18px rgba(15, 62, 123, .45); }
.signin-card button:hover { background: #45a0e2; }
.signin-card .alert { margin: .9rem 0 0; }

@media (prefers-reduced-motion: reduce) {
  .gm-tile, .signin-card {
    animation: none; opacity: 1; transform: none; }
}

/* --- form cards (admin edit pages) ---
 * A twenty-field form as one column of labels, each input capped at 26rem, is
 * a thin ribbon down a wide screen with no grouping: identity, permissions and
 * budget all look alike. These are cards across the width instead — one per
 * decision — and the inputs fill their card. It is still ONE form with one
 * Save; only the layout is grouped. */
.formgrid { display: grid; grid-template-columns: repeat(auto-fit, minmax(21rem, 1fr));
  gap: 1.1rem; align-items: start; margin: .4rem 0 1.2rem; }
.formgrid .span2 { grid-column: 1 / -1; }
fieldset.formcard { min-width: 0; margin: 0; padding: .2rem 1.1rem 1.1rem;
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius); }
fieldset.formcard legend { padding: 0 .4rem; color: var(--muted); font-size: .74rem;
  font-weight: 600; text-transform: uppercase; letter-spacing: .07em; }
.formcard :is(input[type=text], input[type=password], input[type=email],
  input[type=number], input[type=date], select, textarea) { max-width: 100%; }
.formcard > p:last-child { margin-bottom: 0; }
/* A checkbox and its explanation line up as one row, so the wrapped help text
   indents under the label instead of sliding back under the box. */
.formcard label.check { display: flex; align-items: baseline; gap: .55rem; }
.formcard label.check input { width: auto; flex: none; }
.formactions { display: flex; align-items: center; gap: 1.1rem; margin: 0 0 1rem; }
.formactions button { margin-top: 0; }
.secondarylink { color: var(--muted); }

.filterbar { display: flex; gap: .7rem; align-items: end; flex-wrap: wrap; margin: .6rem 0 1rem; }
.filterbar label { margin: 0; }
.filterbar input, .filterbar select { max-width: 12rem; }
.filterbar button { margin-top: 0; }

.kpis { display: grid; grid-template-columns: repeat(auto-fit, minmax(9.5rem, 1fr));
  gap: 1rem; margin-bottom: 1rem; }
.kpi { background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius);
  padding: .7rem 1.1rem; min-width: 0; }
.kpi .label { color: var(--muted); font-size: .8rem; }
.kpi .value { font-size: 1.25rem; font-weight: 700; font-variant-numeric: tabular-nums; }

/* --- live issuer-equity return ---
 * An inline cell the stream rewrites in place. Fixed min-width and tabular
 * figures so a value going −0.4% -> +12.35% changes no column geometry: these
 * sit inside tables of bond data, and a table that reflows on every tick would
 * be unusable.
 *
 * A return that is not currently live is DIMMED and keeps its title ("no update
 * since 14:03:22", "feed offline"). That is the whole point of the state: a
 * frozen last value must never present itself as the present. */
.eqret { font-variant-numeric: tabular-nums; display: inline-block;
  min-width: 4.4em; text-align: right; transition: background-color .45s ease-out;
  border-radius: 3px; }
/* Dimmed where the number is not current. NOT at_close: a closing return is a
 * finished, correct figure — dimming it would read as "degraded" when it is the
 * most authoritative number of the day. pre_open and none carry no value at all,
 * so there is nothing to dim. */
.eqret[data-quote-state="stale"],
.eqret[data-quote-state="closed"],
.eqret[data-quote-state="offline"] { opacity: .55; }

/* Tick flash — on this cell only. The nav and the rest of the chrome never
 * animate for a price move. */
.eqret.tick-up { background-color: color-mix(in srgb, var(--pos) 22%, transparent); }
.eqret.tick-down { background-color: color-mix(in srgb, var(--neg) 22%, transparent); }
@media (prefers-reduced-motion: reduce) {
  .eqret { transition: none; }
  .eqret.tick-up, .eqret.tick-down { background-color: transparent; }
}

/* --- status row (monitoring pages) ---
 * A verdict per section, each a jump link to it. Deliberately not .kpi cards:
 * these carry a judgement, not a measurement, so the colour is the content and
 * the number is the footnote. The colour rides on a left rule rather than the
 * text, so a red cell is still legible and colour is never the only signal —
 * the wording says "2 late" either way. */
.statusverdict { margin-left: .6rem; font-size: .8rem; font-weight: 400;
  vertical-align: middle; }
.statusverdict::before { content: "● "; }
.statusverdict.warn { color: var(--accent); }
.statusrow { display: grid; grid-template-columns: repeat(auto-fit, minmax(10.5rem, 1fr));
  gap: .6rem; margin: 0 0 1.4rem; }
.statuscell { display: block; padding: .5rem .75rem; min-width: 0;
  background: var(--panel); border: 1px solid var(--line); border-left-width: 3px;
  border-radius: 6px; color: var(--text); }
.statuscell:hover { background: var(--panel2); text-decoration: none; }
.statuscell .statuslabel { display: block; color: var(--muted); font-size: .78rem; }
.statuscell .statusnote { display: block; font-size: .95rem; }
.statuscell.pos { border-left-color: var(--pos); }
.statuscell.warn { border-left-color: var(--accent); }
.statuscell.warn .statusnote { color: var(--accent); }
.statuscell.neg { border-left-color: var(--neg); }
.statuscell.neg .statusnote { color: var(--neg); }
.statuscell.muted { border-left-color: var(--muted); }
.statuscell.muted .statusnote { color: var(--muted); }

.chartbox { background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 1rem; margin: .8rem 0; }
.chartbox canvas { width: 100% !important; }
.toggle { display: inline-flex; border: 1px solid var(--line); border-radius: 6px;
  overflow: hidden; margin: 0 0 .6rem; }
.toggle a { padding: .3rem .8rem; color: var(--text); }
.toggle a.active { background: var(--accent2); color: #101820; }
.badge { display: inline-block; padding: .1rem .5rem; border-radius: 999px;
  font-size: .75rem; border: 1px solid var(--line); }
.badge.NEW { color: var(--pos); border-color: var(--pos); }
.badge.DROPPED { color: var(--neg); border-color: var(--neg); }
.badge.INCREASED { color: #9ec97a; }
.badge.DECREASED { color: #d09a65; }
.badge.UNCHANGED { color: var(--muted); }
/* Reporting session (BMO/AMC) — metadata, not a signal: deliberately uncolored
   so it never reads as good/bad next to surprise and Δ-leverage values. */
.badge.tod { padding: 0 .3rem; font-size: .68rem; letter-spacing: .02em;
  color: var(--muted); vertical-align: baseline; }
.pager { display: flex; gap: .6rem; margin: .8rem 0; align-items: center; }

.inline-form { display: inline; }

/* --- row actions (admin) ---
 * Every admin row ends in the same three controls, so they are icons rather
 * than a run of comma-separated words: the shape is recognised before the text
 * is read, and dropping the words is what let the Users table lose its
 * horizontal scrollbar. The label is not decoration — it lives on title +
 * aria-label, so the control still announces itself and still has a tooltip.
 * Destructive actions only turn red on hover/focus, so a table of rows is not
 * a wall of red. */
.ic { width: 1.05em; height: 1.05em; vertical-align: -.16em; fill: none;
  stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; }
.rowacts { display: flex; align-items: center; gap: .3rem; }
/* The action cell centres against a two- or three-line row rather than
   sticking to the top edge with the text. */
table.data.fit td:has(.rowacts) { vertical-align: middle; }
.rowacts .inline-form { display: inline-flex; }
/* Colour is the affordance: a row action sits on a real surface and carries the
   action blue from the moment you see it. Grey-until-hover read as disabled,
   which is exactly wrong for the controls a settings table exists for.
   Destructive stays red at rest too — you should know which one the bin is
   before your pointer is on it, not after. */
a.iconbtn, button.iconbtn { display: inline-flex; align-items: center; justify-content: center;
  width: 1.9rem; height: 1.9rem; padding: 0; margin: 0; border-radius: 7px; font: inherit;
  border: 1px solid var(--line); background: var(--panel2); color: var(--accent2);
  cursor: pointer; transition: background .12s, border-color .12s, color .12s; }
a.iconbtn:hover, a.iconbtn:focus-visible,
button.iconbtn:hover, button.iconbtn:focus-visible {
  color: #7cbcee; background: rgba(41, 142, 219, .18); border-color: var(--accent2);
  text-decoration: none; }
a.iconbtn.danger, button.iconbtn.danger { color: var(--neg); }
a.iconbtn.danger:hover, a.iconbtn.danger:focus-visible,
button.iconbtn.danger:hover, button.iconbtn.danger:focus-visible {
  color: #e79398; background: rgba(208, 101, 107, .18); border-color: var(--neg); }

/* --- state chips (admin tables) ---
 * The admin grids had become a wall of small grey text: org, teams, roles,
 * active, chat and budget source all arrived as muted .85rem, so nothing led
 * and a DISABLED account looked exactly like a live one. A chip is for the few
 * cells that hold a STATE rather than a value — the colour is the answer, and
 * the word still says it for anyone who cannot see the colour.
 * Deliberately not .badge: those variants are named for data movements
 * (NEW / DROPPED / INCREASED) and already mean something on the fund pages. */
.chip { display: inline-block; padding: .06rem .52rem; border-radius: 999px;
  font-size: .74rem; font-weight: 600; line-height: 1.6; white-space: nowrap;
  border: 1px solid var(--line); background: var(--panel2); color: var(--muted); }
.chip + .chip { margin-left: .28rem; }
.chip.ok   { color: #7ecb9b; border-color: rgba(87, 180, 122, .45); background: rgba(87, 180, 122, .14); }
.chip.warn { color: #d9a662; border-color: rgba(201, 133, 0, .5); background: rgba(201, 133, 0, .12); }
.chip.bad  { color: #e5949a; border-color: rgba(208, 101, 107, .5); background: rgba(208, 101, 107, .15); }
.chip.info { color: #7cbcee; border-color: rgba(41, 142, 219, .45); background: rgba(41, 142, 219, .14); }
.chiprow { display: flex; flex-wrap: wrap; gap: .28rem; }
.chiprow .chip + .chip { margin-left: 0; }
/* An icon used inline in prose or a heading, not as a control. */
.ic.lede { color: var(--muted); margin-right: .35rem; }
/* An icon inside a solid button rides on the button's ink, not the muted grey. */
.btn .ic, button .ic { margin-right: .4rem; vertical-align: -.18em; stroke-width: 2.2; }

/* Tables that must FIT rather than scroll: cells wrap instead of forcing the
   wrapper into a horizontal scroll. Only for tables whose columns are prose
   (names, teams, roles) — number grids stay nowrap so columns stay aligned. */
table.data.fit th, table.data.fit td { white-space: normal; }
table.data.fit td.num, table.data.fit th.num, table.data.fit .nowrap { white-space: nowrap; }
table.data.fit { table-layout: auto; }
/* Wide number grids that must fit: HEADERS stack onto several lines while the
   data cells keep nowrap, so each column shrinks to its widest value instead
   of its widest label. */
table.data.thstack thead th { white-space: normal; line-height: 1.2; vertical-align: bottom; }
/* Rows carry two lines in several columns, so they need vertical room and a
   top edge to line up on — middle-alignment made a one-line cell float against
   a three-line neighbour. */
table.data.fit td { padding-top: .55rem; padding-bottom: .55rem; vertical-align: top; }
table.data.fit td .sub { display: block; color: var(--muted); font-size: .8rem;
  margin-top: .1rem; }
table.data.fit td.lead, table.data.fit td .lead { font-weight: 600; }
/* Boolean columns: one glyph, centred, so a yes/no column costs its header's
   width and nothing more. Centring is what makes a column of ticks scannable
   as a column rather than as ragged text. */
table.data th.mid, table.data td.mid { text-align: center; }
/* Column titles set apart from the data by treatment rather than by another
   shade of grey — the same small-caps the admin strip uses for its group name,
   so the two read as one system. */
table.data.fit thead th { font-size: .74rem; text-transform: uppercase;
  letter-spacing: .06em; padding-top: .6rem; padding-bottom: .5rem; }
/* Banding, because these rows are two and three lines tall: without it the eye
   loses which sub-line belongs to which account halfway across. Hover follows,
   so it still wins over the band. */
table.data.fit tbody tr:nth-child(even) { background: rgba(255, 255, 255, .022); }
table.data.fit tbody tr:hover { background: var(--panel2); }

.gridbar { display:flex; gap:.6rem; margin:.6rem 0; }
.gridbar input { max-width: 14rem; }
.gridbar button { margin-top: 0; padding:.3rem .8rem; }
th.sortable { cursor: pointer; user-select: none; }
th.sortable[data-dir=asc]::after { content: " \2191"; }
th.sortable[data-dir=desc]::after { content: " \2193"; }

.chart-lg { height: 340px; } .chart-md { height: 280px; } .chart-sm { height: 220px; }
/* charts.js sizes a chart-auto box to its BAR COUNT, but only for hbar — every
   other kind left the box at auto height, and a canvas with maintainAspectRatio
   off then grew to whatever height it was measured at (the DataChat turns chart
   filled the viewport). This is the floor under that: the JS inline height still
   wins for hbar. */
.chart-auto { height: 260px; }
.cols2 { display: grid; grid-template-columns: 1fr 1fr; gap: 1.4rem; align-items: start; }
@media (max-width: 900px) { .cols2 { grid-template-columns: 1fr; } }
.cols3 { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 1.4rem;
  align-items: start; }
@media (max-width: 1100px) { .cols3 { grid-template-columns: 1fr; } }

/* --- mover/contributor cards (fund overview) --- */
.cards4 { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 1rem;
  align-items: start; }
@media (max-width: 1250px) { .cards4 { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
/* one card per row through tablet-portrait widths — two-up crushes the name
   column into illegibility on an iPad held vertically */
@media (max-width: 1000px) { .cards4 { grid-template-columns: 1fr; } }
.movercard h3 { margin: 0 0 .4rem; font-size: .95rem; color: var(--muted);
  font-weight: 600; }
.movercard .tablewrap { max-height: 21rem; overflow-y: auto; overflow-x: hidden; }
/* equalizeRows floors each mover card to the tallest card in its row; the
   card hands that spare height to its table so the panel border still ends at
   the card's bottom edge (no cap here — the row is already bounded by the
   12-row card that sets it) */
.cards4 > .card { display: flex; flex-direction: column; }
.cards4 > .card > .tablewrap { flex: 1 1 auto; max-height: none; }
.movercard table.data { font-size: .82rem; table-layout: fixed; width: 100%; }
/* the fixed grid takes widths from the header row: numeric columns hold a
   tight fixed width so the figures sit close together, and the name column
   (no width set) absorbs ALL the leftover space */
.movercard table.data th, .movercard table.data td { padding: .3rem .45rem;
  overflow: hidden; text-overflow: ellipsis; }
.movercard table.data th:nth-child(2), .movercard table.data td:nth-child(2) { width: 4.2rem; }
.movercard table.data th:nth-child(3), .movercard table.data td:nth-child(3) { width: 4.6rem; }
.movercard table.data th:nth-child(4), .movercard table.data td:nth-child(4) { width: 4.9rem; }
/* Contributors drill card: the Industry column (curated industry, sector
   fallback) reads as secondary next to the ticker */
#drilltable td.ind { color: var(--muted); font-size: .82rem; }
/* ticker-less issuer rollups show the raw issuer name (nothing to link);
   cap it so a long name can never stretch a ranking table. .issname is the
   same cap for the grey issuer name that rides beside a linked ticker. */
table.data .noticker, table.data .issname { display: inline-block; max-width: 18ch;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  vertical-align: bottom; }

/* --- contribution analysis cards (fund historical) --- */
.contribcard h3 { margin: 0 0 .4rem; font-size: .95rem; color: var(--muted);
  font-weight: 600; }
.contribcard .tablewrap { max-height: 32rem; overflow-y: auto; }
.contribcard table.data { font-size: .84rem; }
.contribcard table.data th, .contribcard table.data td { padding: .3rem .45rem; }
/* the issuer cell is the only elastic one: the numbers stay in tight columns
   so the two cards read as one ranking side by side */
.contribcard table.data th.num, .contribcard table.data td.num { width: 4.9rem; }
.contribcard table.data td .small { margin-left: .35rem; }
/* the expanded security table sits inside the parent row: same columns, indented,
   and the sticky header of the outer table must stay above it */
.contribcard tr.dv-childwrap > td { padding: .1rem .3rem .5rem 2rem; }
.contribcard table.dv-subtable thead th { position: static; }

/* --- entity landing headers (funds / securities / issuers) --- */
/* Fund pages: title + tab bar stay pinned while the page scrolls. Child
   margins would collapse out of the wrapper and open see-through slivers at
   the viewport edge, so the wrapper carries the spacing itself. z-index sits
   above the in-flow tables (sticky theads are z:1) and below every popover
   (research-form 40+) and drawer (70+). Small screens keep the normal flow —
   the wrapped header would eat too much of the viewport there. */
.fundhead { position: sticky; top: 0; z-index: 30; background: var(--bg);
  padding-top: .3rem; margin-bottom: 1.1rem; }
.fundhead .entityhead { margin-top: 0; }
.fundhead .subtabs { margin-bottom: 0; }
@media (max-width: 700px) { .fundhead { position: static; } }
.entityhead { display: flex; align-items: center; gap: .9rem; flex-wrap: wrap; margin: .3rem 0 .4rem; }
.entityhead h1 { margin: 0; }
.tickerbadge { display: inline-block; padding: .15rem .7rem; border-radius: 8px;
  font-weight: 700; font-size: 1.15rem; background: var(--panel2);
  border: 1px solid var(--line); color: var(--text); }
/* Palette-slot classes (fund -> slot fixed in PHP FUND_SLOTS; the colors are
   the validated categorical slots in charts.js — keep the two lists in step). */
.tickerbadge.s0 { color: #3987e5; border-color: #3987e5; }
.tickerbadge.s1 { color: #d95926; border-color: #d95926; }
.tickerbadge.s2 { color: #199e70; border-color: #199e70; }
.tickerbadge.s3 { color: #c98500; border-color: #c98500; }
.tickerbadge.s4 { color: #d55181; border-color: #d55181; }
.tickerbadge.s5 { color: #008300; border-color: #008300; }
.tickerbadge.s6 { color: #9085e9; border-color: #9085e9; }
.tickerbadge.s7 { color: #e66767; border-color: #e66767; }
.entityhead .asof { margin-left: auto; color: var(--muted); font-size: .85rem;
  display: flex; gap: .4rem; align-items: center; }
.entityhead .asof input[type=date] { max-width: 10rem; padding: .25rem .5rem;
  font-size: .85rem; }
.entityhead .asof button { margin: 0; padding: .25rem .6rem; font-size: .85rem; }
.subtabs { display: flex; gap: .2rem; border-bottom: 1px solid var(--line);
  margin: .4rem 0 1.1rem; flex-wrap: wrap; }
.subtabs a { padding: .4rem .9rem; color: var(--muted); border-radius: 6px 6px 0 0;
  border-bottom: 2px solid transparent; }
.subtabs a:hover { color: var(--text); text-decoration: none; background: var(--panel); }
.subtabs a.active { color: var(--text); border-bottom-color: var(--brand);
  font-weight: 600; }
.fundchips { display: inline-flex; gap: .35rem; flex-wrap: wrap; vertical-align: middle; }
.fundchip { display: inline-block; padding: .05rem .5rem; border-radius: 999px;
  font-size: .75rem; font-weight: 600; border: 1px solid var(--line); color: var(--text); }
.fundchip.s0 { color: #3987e5; border-color: #3987e5; }
.fundchip.s1 { color: #d95926; border-color: #d95926; }
.fundchip.s2 { color: #199e70; border-color: #199e70; }
.fundchip.s3 { color: #c98500; border-color: #c98500; }
.fundchip.s4 { color: #d55181; border-color: #d55181; }
.fundchip.s5 { color: #008300; border-color: #008300; }
.fundchip.s6 { color: #9085e9; border-color: #9085e9; }
.fundchip.s7 { color: #e66767; border-color: #e66767; }

/* --- cross-filter state (filtering must never reshape the page) --- */
.xfilter-canvas { cursor: pointer; }
.xchip { display: inline-flex; gap: .4rem; align-items: baseline;
  padding: .3rem .8rem; border-radius: 999px; background: #2a3547;
  border: 1px solid var(--accent2); font-size: .85rem; }
/* the chip rides at the right edge of its section banner rather than in a
   reserved strip of its own — the strip was empty on every unfiltered page
   load and read as a stray gap above the cards */
.sectionbar .xchip { margin-left: auto; }
/* Theme-mention highlight in the transcript drawer (Thematics Who links):
   amber = data emphasis. The paragraph glows faintly; the quote itself is
   marked. */
.trbody p.mention-hl { border-left: 2px solid var(--accent);
  padding-left: .55rem; margin-left: -.65rem; }
.trbody mark { background: color-mix(in srgb, var(--accent) 28%, transparent);
  color: inherit; border-radius: 2px; padding: 0 .1rem; }

/* Show-all bottom tab (thematics cards): a full-width bar attached to the
   card's lower edge — reads as part of the card, not a floating control. */
button.morebar { display: block; width: calc(100% + .4rem); margin: .3rem -.2rem 0;
  background: var(--panel2); color: var(--muted); border: none;
  border-top: 1px solid var(--line); border-radius: 0 0 var(--radius) var(--radius);
  padding: .38rem .6rem; font-size: .82rem; text-align: center; cursor: pointer; }
button.morebar:hover, button.morebar:focus-visible { color: var(--text);
  background: var(--panel); }

.tablewrap.xfiltered { border-color: var(--accent2);
  box-shadow: 0 0 0 1px var(--accent2) inset; }
.tablewrap.fixed-rows { height: 27rem; overflow-y: auto; }
tr.row-hidden { display: none; }
tr.focused { background: #2a3a4e !important; box-shadow: inset 3px 0 0 var(--accent2); }
table[data-focus-rows] tbody tr { cursor: pointer; }

/* --- chart range buttons + JS mode toggles --- */
.rangebar { display: inline-flex; border: 1px solid var(--line); border-radius: 6px;
  overflow: hidden; margin: .2rem 0 0; }
.rangebar button, .rangebar a, .modebtns button { background: none; border: none;
  color: var(--muted); padding: .25rem .7rem; margin: 0; font: inherit; font-size: .85rem;
  cursor: pointer; font-weight: 600; border-radius: 0; }
.rangebar a:hover { text-decoration: none; color: var(--text); background: var(--panel); }
.rangebar button.active, .rangebar a.active, .modebtns button.active {
  background: var(--panel2); color: var(--text); }
.modebtns { display: inline-flex; border: 1px solid var(--line); border-radius: 6px;
  overflow: hidden; vertical-align: middle; }
/* --- section banners -------------------------------------------------------
   A fund tab is a stack of distinct places — Earnings, Watchlist, TRACE Flow —
   and same-weight headings ran them together into one long column of text.
   Each top-level area now wears a banner: an accent rail down its left edge, a
   wash that fades out to the right, and a hairline the block below sits
   against. .secbanner is the standalone heading; .sectionbar is the same
   banner carrying controls (mode toggle, range buttons, filter chip) beside
   the title. The rail colour keys off data-area so a block reads as the same
   kind of thing wherever it appears — the Overview's earnings block and the
   Earnings tab share a rail — using hues already validated for the #1a212b
   surface. The type scale sits on the TITLE, never on the bar: controls inside
   a .sectionbar inherit `font: inherit`, and a bar-level font-size grew the
   date inputs and mode links along with it. */
.secbanner, .sectionbar {
  --area: var(--accent2);
  margin: 1.9rem 0 .85rem; padding: .42rem .95rem .42rem .85rem;
  background: linear-gradient(90deg,
    color-mix(in srgb, var(--area) 15%, var(--panel2)) 0%,
    var(--panel) 46%, rgba(26, 33, 43, 0) 100%);
  border-left: 3px solid var(--area);
  border-bottom: 1px solid var(--line);
  border-radius: 3px 10px 0 0;
}
.secbanner, .sectionbar h2 { font-size: 1.18rem; font-weight: 600; color: #eaf3fa; }
/* the "— 2026-08-04" style qualifier beside a title is not part of the title */
.secbanner .muted, .sectionbar h2 .muted { font-weight: 400; }
.sectionbar { display: flex; align-items: center; gap: .8rem; flex-wrap: wrap; }
.sectionbar h2 { margin: 0; }
.secbanner[data-area="earnings"], .sectionbar[data-area="earnings"] { --area: var(--accent); }
.secbanner[data-area="watch"], .sectionbar[data-area="watch"] { --area: var(--ent-security); }
.secbanner[data-area="trace"], .sectionbar[data-area="trace"] { --area: var(--ent-issuer); }
/* Headings inside a grid column are card titles under an area banner, not
   areas of their own: lighter, tighter, no banner chrome. */
.colhead { font-size: 1rem; font-weight: 600; color: var(--text);
  margin: 0 0 .55rem; }
.colhead .muted { font-weight: 400; }
/* card title + its controls on one line (replaces the floated button that
   used to hang off the right of an h3) */
.cardhead { display: flex; align-items: baseline; gap: .8rem; flex-wrap: wrap;
  margin: 0 0 .6rem; }
.cardhead > :first-child { margin: 0; }
.cardhead .modebtns { margin-left: auto; align-self: center; }

/* --- working spinner (DataChat) --- */
/* The chat's "working" indicator: the logo's 2×2 mosaic, in currentColor so
   it reads on the muted working line and on the amber FAB alike, its cells
   flipping with the same @keyframes the nav mark uses — one visual idea, not
   two. Fixed em box: the glyph must not change size as the cells flip, or
   the line of text would jitter. Delay order walks clockwise (TL, TR, BR,
   BL); grid flow puts BL third, hence the swapped delays. */
.spin { display: inline-grid; grid-template-columns: repeat(2, .36em);
  grid-auto-rows: .36em; gap: .08em; margin-right: .45rem;
  vertical-align: -.12em; }
.spin i { background: currentColor; border-radius: .09em;
  animation: tileflip 1.15s ease-in-out infinite; }
.spin i:nth-child(1) { animation-delay: 0s; }
.spin i:nth-child(2) { animation-delay: -.29s; }
.spin i:nth-child(4) { animation-delay: -.58s; }
.spin i:nth-child(3) { animation-delay: -.86s; }

/* --- DataChat working card ---
   What the assistant is doing, while it is doing it: the steps it has already
   finished, then one live line — current step, elapsed seconds, Stop. The step
   list is capped at the last few by the client, and the card keeps a fixed
   width, so a twenty-step turn cannot grow a box that shoves the answer up the
   page. The phrases are the server's fixed table; nothing here can name a
   tool, a table or a query. */
.msg .bubble.workcard { max-width: min(100%, 30rem); width: 100%; }
.workcard .worksteps { list-style: none; margin: 0 0 .3rem; padding: 0;
  font-size: .85rem; color: var(--muted); }
.workcard .worksteps li { display: flex; align-items: baseline; gap: .4rem;
  line-height: 1.5; }
.workcard .worksteps .wi { flex: none; width: .8em; }
.workcard .worksteps li.ok .wi { color: var(--pos); }
.workcard .worksteps li.bad .wi { color: var(--neg); }
.workcard .worksteps .wn { min-width: 0; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
.workcard .worksteps .wt { margin-left: auto; padding-left: .5rem;
  font-variant-numeric: tabular-nums; opacity: .7; }
.workcard .worksteps li.more { opacity: .7; }
.workcard .workfoot { display: flex; align-items: center; gap: .5rem; margin: 0; }
.workcard .workfoot .worklabel { min-width: 0; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
.workcard .workfoot .worktime { margin-left: auto; font-size: .85rem;
  font-variant-numeric: tabular-nums; opacity: .7; }
.workcard .stopbtn { flex: none; margin: 0; padding: .1rem .6rem; font-size: .8rem; }

/* --- chat drawer --- */
.chatfab { position: fixed; right: 1.1rem;
  bottom: calc(1.1rem + env(safe-area-inset-bottom, 0px)); z-index: 60;
  border-radius: 999px; padding: .55rem 1.1rem; box-shadow: 0 8px 22px rgba(6,17,40,.55);
  margin: 0; background: #0f3e7b; color: #f4f0e6;
  border: 1px solid rgba(41, 142, 219, .5); }
.chatfab:hover { background: #1869b4; }
/* the FAB's bars take its cream-on-navy text colour for free (currentColor) */
.chatdrawer { position: fixed; top: 0; right: 0; bottom: 0;
  width: max(40vw, 340px); z-index: 70; background: var(--panel);
  border-left: 1px solid var(--line); box-shadow: -8px 0 24px rgba(0,0,0,.45);
  display: flex; flex-direction: column; padding: .8rem 1rem;
  transform: translateX(105%); transition: transform .18s ease; }
body.chat-w20 .chatdrawer { width: max(20vw, 300px); }
body.chat-open .chatdrawer { transform: none; }
body.chat-open .chatfab { display: none; }
/* push page content out from under the open drawer instead of covering it */
body.chat-open .content { --drawer-inset: calc(max(40vw, 340px) + 1rem); }
body.chat-open.chat-w20 .content { --drawer-inset: calc(max(20vw, 300px) + 1rem); }
/* the chat panel slides quicker than the entity drawer — while it is the only
   one open the page keeps its pace instead of trailing it */
body.chat-open:not(.ent-open) .content { transition-duration: .2s; }
@media (max-width: 900px) {
  body.chat-open .content { --drawer-inset: 0px; }
  .chatdrawer, body.chat-w20 .chatdrawer { width: min(420px, 94vw); }
}
.chatdrawer .drawerhead { display: flex; gap: .6rem; align-items: center;
  margin-bottom: .5rem; }
.chatdrawer .drawerhead .title { font-weight: 700; color: var(--accent); }
.chatdrawer .drawerhead select { flex: 1; max-width: none; padding: .25rem .4rem;
  font-size: .85rem; }
.chatdrawer .drawerhead button { margin: 0; padding: .25rem .6rem; font-size: .85rem; }
.chatdrawer .messages { flex: 1; }
.chatdrawer .msg .bubble { max-width: 88%; font-size: .9rem; }
.chatdrawer .chatform textarea { font-size: .9rem; }

/* --- entity drawer (security / issuer preview, opened from any browsing page)
       Same right-edge mechanics as the chat drawer: fixed panel, content
       pushed out from under it rather than covered. When both are open the
       entity drawer sits inboard of the chat drawer on a wide screen, and
       takes the edge (over it) where there is no room for two.

       Its surface is deliberately a step lighter than the page (--panel2 over
       --bg) so the drawer reads as a layer above what you were reading, and
       its left edge takes the entity color (violet issuer / cyan security). --- */
:root { --ent-issuer: #a880e8; --ent-security: #35c1d4; --ent-edge: var(--line); }
.entdrawer { position: fixed; top: 0; right: 0; bottom: 0;
  width: max(32vw, 400px); z-index: 80; background: var(--panel2);
  border-left: 3px solid var(--ent-edge); box-shadow: -14px 0 34px rgba(0,0,0,.5);
  display: flex; flex-direction: column; padding: .8rem 1rem;
  visibility: hidden; transform: translateX(105%);
  transition: transform .34s cubic-bezier(.22,.75,.3,1),
              right .34s cubic-bezier(.22,.75,.3,1), visibility .34s; }
.entdrawer.ent-issuer { --ent-edge: var(--ent-issuer); }
.entdrawer.ent-security { --ent-edge: var(--ent-security); }
body.ent-open .entdrawer { transform: none; visibility: visible; }
body.ent-open .content { --drawer-inset: calc(max(32vw, 400px) + 1rem); }
/* both drawers open with no room to sit side by side: the entity drawer covers
   the chat drawer, so the content clears whichever of the two is wider */
body.chat-open.ent-open .content { --drawer-inset: calc(max(40vw, 340px) + 1rem); }
body.chat-open.chat-w20.ent-open .content { --drawer-inset: calc(max(32vw, 400px) + 1rem); }
@media (min-width: 1500px) {
  /* only while OPEN: shifting a closed drawer inboard would slide its own
     off-screen parking spot into view, next to the chat drawer */
  body.chat-open.ent-open .entdrawer { right: max(40vw, 340px); }
  body.chat-open.chat-w20.ent-open .entdrawer { right: max(20vw, 300px); }
  body.chat-open.ent-open .content {
    --drawer-inset: calc(max(40vw, 340px) + max(32vw, 400px) + 1rem); }
  body.chat-open.chat-w20.ent-open .content {
    --drawer-inset: calc(max(20vw, 300px) + max(32vw, 400px) + 1rem); }
}
@media (prefers-reduced-motion: reduce) {
  .entdrawer, .chatdrawer, .content { transition: none; }
  /* The flips stop, but nothing disappears: the mark stays the logo and the
     chat glyph stays a legible 2×2 mosaic beside the step it is on, which
     is the text that actually carries the liveness. Selectors mirror the
     running ones — a plain `.brandicon .bc` here would lose on specificity. */
  .brand:hover .bc, .brand:focus-visible .bc,
  .spin i { animation-name: none; }
}
@media (max-width: 900px) {
  body.ent-open .content { --drawer-inset: 0px; }
  .entdrawer { width: min(460px, 96vw); }
}
@media (max-width: 560px) {
  .entdrawer { width: 100vw; padding: .6rem .7rem; }
  body.ent-open { overflow: hidden; } /* don't scroll the page behind it */
}
.entdrawer .drawerhead { display: flex; gap: .5rem; align-items: center;
  margin-bottom: .4rem; border-bottom: 1px solid var(--ent-edge); padding-bottom: .5rem; }
.entdrawer .drawerhead .title { font-weight: 700; color: var(--text); flex: 1;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* "Issuer:" / "Security:" ahead of the name, in the entity color — with the
   tinted head band and the drawer's edge, which kind you are on is readable
   before the name is */
.entdrawer .drawerhead .entkind { font-weight: 700; color: var(--ent-edge);
  white-space: nowrap; flex: none; }
.entdrawer.ent-issuer .drawerhead, .entdrawer.ent-security .drawerhead {
  background: color-mix(in srgb, var(--ent-edge) 11%, transparent);
  margin: -.8rem -1rem .4rem; padding: .55rem 1rem .5rem; }
@media (max-width: 560px) { /* matches the drawer's tighter padding */
  .entdrawer.ent-issuer .drawerhead, .entdrawer.ent-security .drawerhead {
    margin: -.6rem -.7rem .4rem; padding: .45rem .7rem .5rem; }
}
.entdrawer .drawerhead button { margin: 0; padding: .25rem .6rem; font-size: .85rem; }
.entdrawer .drawerhead a { padding: .25rem .4rem; color: var(--muted); }
.entdrawer .drawerhead a:hover { color: var(--text); text-decoration: none; }
.entdrawer .drawerbody { flex: 1; overflow-y: auto; overscroll-behavior: contain;
  scrollbar-gutter: stable; }
.entdrawer.loading .drawerbody { opacity: .45; transition: opacity .12s ease .08s; }

/* --- release drawer (latest earnings press release) ---------------------
   Takes the right edge as the OUTERMOST drawer: opening it slides the entity
   drawer inboard so the release sits beside the numbers it explains rather
   than covering them. Amber edge marks it as a document, not an entity.
   Where there is no room for two (<=900px) it simply covers what is there. --- */
.reldrawer { position: fixed; top: 0; right: 0; bottom: 0;
  width: max(32vw, 400px); z-index: 82; background: var(--panel2);
  border-left: 3px solid var(--accent); box-shadow: -14px 0 34px rgba(0,0,0,.5);
  display: flex; flex-direction: column; padding: .8rem 1rem;
  visibility: hidden; transform: translateX(105%);
  transition: transform .34s cubic-bezier(.22,.75,.3,1), visibility .34s; }
body.rel-open .reldrawer { transform: none; visibility: visible; }
body.rel-open .content { --drawer-inset: calc(max(32vw, 400px) + 1rem); }
/* release + entity: the entity drawer parks one release-width inboard, and the
   page clears both. Only while OPEN — shifting a closed drawer would slide its
   off-screen parking spot into view. */
body.rel-open.ent-open .entdrawer { right: max(32vw, 400px); }
body.rel-open.ent-open .content {
  --drawer-inset: calc(max(32vw, 400px) * 2 + 1rem); }
@media (prefers-reduced-motion: reduce) { .reldrawer { transition: none; } }
@media (max-width: 900px) {
  body.rel-open .content, body.rel-open.ent-open .content { --drawer-inset: 0px; }
  body.rel-open.ent-open .entdrawer { right: 0; } /* no room — release covers it */
  .reldrawer { width: min(460px, 96vw); }
}
@media (max-width: 560px) {
  .reldrawer { width: 100vw; padding: .6rem .7rem; }
  body.rel-open { overflow: hidden; }
}
.reldrawer .drawerhead { display: flex; gap: .5rem; align-items: center;
  margin: -.8rem -1rem .4rem; padding: .55rem 1rem .5rem;
  border-bottom: 1px solid var(--accent);
  background: color-mix(in srgb, var(--accent) 11%, transparent); }
@media (max-width: 560px) {
  .reldrawer .drawerhead { margin: -.6rem -.7rem .4rem; padding: .45rem .7rem .5rem; }
}
.reldrawer .drawerhead .title { font-weight: 700; color: var(--text); flex: 1;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.reldrawer .drawerhead button { margin: 0; padding: .25rem .6rem; font-size: .85rem; }
.reldrawer .drawerhead a { padding: .25rem .4rem; color: var(--muted); }
.reldrawer .drawerhead a:hover { color: var(--text); text-decoration: none; }
/* overflow-x too, because a statement can be wider than the drawer — Airbnb
   reports ten quarters across one. It has to be THIS element and not a box
   around the table: sticky column heads stick to their nearest scrolling
   ancestor, so a wrapper of its own would leave the heads behind at the top
   of the statement instead of at the top of the drawer. */
.reldrawer .drawerbody { flex: 1; overflow: auto; overscroll-behavior: contain;
  scrollbar-gutter: stable; }
.reldrawer.loading .drawerbody { opacity: .45; transition: opacity .12s ease .08s; }

/* --- loading wheel ---
   Shared by every "the old content is still on screen" state: a drawer
   fetching a fragment, and a full-page reload after a window / filter / tab
   change (body.navloading, set by app.js on the click). The dim alone is easy
   to miss mid-read; the wheel says outright that what's underneath is not yet
   the requested data. It fades in only after .25s — a fast load never flashes
   it — and the page variant clears itself on pageshow (bfcache restores skip
   the load event, so a CSS-only reset would strand the overlay). */
.entdrawer.loading::after, .reldrawer.loading::after, body.navloading::after {
  content: ""; position: absolute; top: 4.4rem; left: 50%;
  width: 26px; height: 26px; margin-left: -13px; box-sizing: border-box;
  border-radius: 50%; border: 3px solid var(--line);
  border-top-color: var(--accent2); opacity: 0; z-index: 5; pointer-events: none;
  animation: bl-wheel .8s linear infinite, bl-wheelin .15s ease .25s forwards; }
body.navloading::after { position: fixed; top: 38vh; z-index: 200; }
body.navloading .content { opacity: .45; transition: opacity .12s ease .12s; }
@keyframes bl-wheel { to { transform: rotate(360deg); } }
@keyframes bl-wheelin { to { opacity: 1; } }

.relpanel .relmeta { margin: .1rem 0 .3rem; }
.relpanel .relhead { margin: 0 0 .7rem; font-size: 1.1rem; color: var(--accent); }
.reltext p { margin: 0 0 .7rem; font-size: .9rem; }
.relsection { margin: 1.1rem 0 .4rem; font-size: .95rem; color: var(--text); }
.relbullets { margin: 0 0 .8rem; padding-left: 1.1rem; }
.relbullets li { font-size: .88rem; margin: .2rem 0; }
/* Management commentary in the release's own voice — gold, so the quotes read
   as the human line through the numbers. */
.relquote { margin: 0 0 .8rem; padding: .1rem 0 .1rem .7rem;
  border-left: 2px solid var(--accent); color: var(--accent);
  font-size: .9rem; font-style: italic; }
/* ---- earnings call transcript, in the same drawer as the release ----
   A transcript is read as a conversation, so the speaker is the structure:
   each turn carries its own attribution line and the roles are separated by
   weight and colour rather than by boxes, which would fragment a 45k-character
   read into hundreds of cards. Management keeps the release's gold accent —
   it is the same voice — while analysts stay neutral and the operator recedes,
   because "your next question comes from…" is furniture a reader skims past. */
.trpanel .relmeta { margin: .1rem 0 .3rem; }
.trswitch { margin: 0 0 .7rem; display: flex; flex-wrap: wrap; gap: .45rem; }
.trswitch strong { color: var(--accent); }
.trnote { margin: 0 0 .7rem; }
.trturn { margin: 0 0 .95rem; }
/* Every paragraph of a turn, not just the last — a long turn is broken into
   several (transcript_paragraphs), and the spacing between them has to be
   tighter than the spacing between speakers or the attribution stops reading
   as a heading for what follows it. */
.trturn > p { margin: 0 0 .55rem; font-size: .9rem; }
.trturn > p:last-child { margin: 0; }
.trwho { margin: 0 0 .15rem; font-size: .82rem; font-weight: 700;
  color: var(--text); }
.trrole { font-weight: 400; color: var(--muted); margin-left: .4rem;
  font-size: .78rem; }
.tr-mgmt .trwho { color: var(--accent); }
/* The operator is procedural, never substantive: dimmed whole, so the eye
   lands on the next real speaker instead of re-reading the hand-off. */
.tr-operator { color: var(--muted); }
.tr-operator > p:last-child { font-size: .85rem; }
.tr-analyst .trwho { color: var(--text); }
.tr-analyst { border-left: 2px solid var(--line); padding-left: .7rem; }
.trfoot { margin: 1.2rem 0 .3rem; border-top: 1px solid var(--line);
  padding-top: .6rem; }
/* Who was on the call, one line. Reserved above the turns rather than shown on
   hover: it is the first thing a reader wants and it never changes length once
   loaded, so it costs no reflow. */
.trroster { margin: 0 0 .8rem; line-height: 1.35; }
/* The issuer page's list of calls: chips, so a dozen quarters take one line
   rather than a dozen cards, and the row height does not move as the archive
   fills in. */
.trlist { display: flex; flex-wrap: wrap; gap: .5rem; margin: 0 0 1rem; }
.trlist a { border: 1px solid var(--line); border-radius: 4px;
  padding: .2rem .5rem; font-size: .85rem; white-space: nowrap; }
.trlist a:hover { border-color: var(--accent); text-decoration: none; }

/* Filer footnotes ("1 Reconciliations of non-GAAP measures…"). In a paginated
   release these sit at the foot of each page and precede, in the markup, the
   body they annotate — so they are set small and muted to read as apparatus
   rather than as the opening of the letter. */
/* A table's units caption ("Unaudited", "In thousands") — apparatus for the
   grid beneath it, never a section of the letter. */
.relunits { margin: .6rem 0 -.4rem; font-size: .78rem; color: var(--muted); }

.relfoot { margin: 0 0 .8rem; padding-left: .7rem;
  border-left: 2px solid var(--line); }
.relfoot p { margin: 0 0 .3rem; font-size: .78rem; color: var(--muted); }
.relfoot p:last-child { margin: 0; }

/* Statement tables, rebuilt from the flattened label/value/value runs the
   extractor produces. Figures right-aligned and tabular so the columns line up
   as they did in the filing; the linked original stays authoritative. */
.relnums { width: 100%; border-collapse: collapse; margin: 0 0 .9rem;
  font-size: .85rem; }
.relnums th, .relnums td { padding: .22rem .45rem; text-align: left;
  border-bottom: 1px solid var(--line); vertical-align: baseline; }
.relnums th { font-weight: 400; color: var(--muted); }
/* Column labels stay put while a long statement scrolls past — the drawer body
   is the scroll container, so the head sticks to its top rather than the page. */
.relnums thead th { position: sticky; top: 0; z-index: 1;
  font-weight: 700; color: var(--text); background: var(--panel2);
  border-bottom: 1px solid var(--accent); }
.relnums thead th.num { text-align: right; white-space: nowrap; }
/* Two header rows when the periods are grouped ("Three Months Ended June 30"
   over its 2026/2025 pair) — without the span row an income statement reads
   "2026 | 2025 | 2026 | 2025" with no way to tell the pairs apart. Each row
   sticks at its own offset so both stay legible while the statement scrolls. */
.relnums thead tr.relspan th { text-align: center; font-weight: 600;
  border-bottom: 1px solid var(--line); white-space: normal; }
.relnums thead tr:first-child th { top: 0; }
.relnums thead tr:nth-child(2) th { top: 1.7rem; }
/* section labels inside a statement ("ASSETS", "Current liabilities") */
.relnums tr.relsec th { font-weight: 700; color: var(--text);
  padding-top: .6rem; background: transparent; }
.relnums tr.relsec:hover { background: transparent; }
.relnums td { text-align: right; font-variant-numeric: tabular-nums;
  white-space: nowrap; width: 1%; }
.relnums tr:hover { background: var(--panel); }
/* the release's own title and subheads, ahead of the first paragraph */
.reldeck { display: flex; flex-direction: column; gap: .15rem; margin: 0 0 .8rem;
  padding-bottom: .6rem; border-bottom: 1px solid var(--line); }
.reldeck strong { font-size: 1rem; color: var(--text); }
.reldeck span { font-size: .85rem; color: var(--muted); }
/* the drawer link that opens all this */
.entpanel-release { margin: .25rem 0 .5rem; }
.entpanel-release a { font-size: .9rem; }
/* the call's theme tags, tucked under the Latest Earnings Call link they
   belong to — each opens the same drawer with the mentions highlighted */
.entpanel-themes { margin: -.35rem 0 .6rem; padding-left: 1.5rem; }
.entpanel-themes a { font-size: .8rem; }
/* Thematics live search: results are an overlay under the box, never inline
   expansion — the theme cards hold still while you type. */
.thsearch { position: relative; max-width: 30rem; margin: 0 0 1rem; }
.thsearch input { width: 100%; }
.thsearch-pop { position: absolute; top: calc(100% + .3rem); left: 0; right: 0;
  z-index: 60; background: var(--panel2); border: 1px solid var(--line);
  border-radius: var(--radius); box-shadow: 0 10px 26px rgba(0, 0, 0, .45);
  max-height: 24rem; overflow: auto; padding: .3rem .65rem .45rem; }
.thsearch-pop .tsr { padding: .4rem 0; border-bottom: 1px solid var(--line); }
.thsearch-pop .tsr:last-child { border-bottom: 0; }
.thsearch-pop .tsr-none { margin: .3rem 0; }
.thsearch-pop a.tsr-theme { font-weight: 600; }
.thsearch-pop a.tsr-quote { display: block; margin-top: .2rem;
  color: var(--text); }
.thsearch-pop a.tsr-quote:hover .tsr-tick { text-decoration: underline; }
.thsearch-pop .tsr-tick { color: var(--accent2); font-weight: 600; }
.thsearch-pop mark { background: color-mix(in srgb, var(--accent) 28%, transparent);
  color: inherit; border-radius: 3px; padding: 0 .08em; }
/* Thematics season rollup: <details> per category styled as table rows,
   the vocabulary indented beneath. Native details/summary — no JS, and the
   rest of the page holds still when one expands. */
.thprim-card { padding: .3rem .65rem .45rem; }
.thprim-row { display: grid;
  grid-template-columns: minmax(8rem, 1fr) repeat(6, 4.2rem);
  gap: .5rem; align-items: baseline; padding: .32rem .2rem; }
.thprim-head { color: var(--muted); font-size: .78rem; font-weight: 600;
  border-bottom: 1px solid var(--line); }
.thprim-sort { appearance: none; border: 0; background: none; color: inherit;
  font: inherit; font-weight: inherit; padding: 0; cursor: pointer; }
.thprim-sort:hover, .thprim-sort:focus-visible { color: var(--accent); text-decoration: underline; }
.thprim-sort[data-sort-dir="asc"]::after { content: " ▲"; font-size: .7em; }
.thprim-sort[data-sort-dir="desc"]::after { content: " ▼"; font-size: .7em; }
.thprim-num { text-align: right; font-variant-numeric: tabular-nums; }
.thprim-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.thprim { border-bottom: 1px solid var(--line); }
.thprim:last-child { border-bottom: 0; }
.thprim > summary { cursor: pointer; list-style: none; }
.thprim > summary::-webkit-details-marker { display: none; }
.thprim > summary .thprim-label::before { content: "▸"; display: inline-block;
  width: 1em; color: var(--muted); transition: transform .15s ease; }
.thprim[open] > summary .thprim-label::before { transform: rotate(90deg); }
.thprim > summary:hover { background:
  color-mix(in srgb, var(--accent2) 8%, transparent); }
.thprim > summary .thprim-label { font-weight: 600; }
.thprim-kids { padding: 0 0 .35rem; }
.thprim-kid { padding: .18rem .2rem .18rem 1.35rem; font-size: .88rem; }
.thprim-flat { padding-left: .2rem; border-bottom: 1px solid var(--line); }
.thprim-flat:last-child { border-bottom: 0; }
.thprim-kid:hover { background: color-mix(in srgb, var(--accent2) 6%, transparent); }
/* a developing theme (< current-min issuers) rides in the same expansion,
   dimmed — the separate Developing card it replaces is gone. Dim the ROW,
   never its cells: `.thprim-dev .thprim-num { color: var(--muted) }` outranked
   .pos/.neg on specificity and repainted the return columns (and the theme
   link) grey on every developing row, which is most of the season card. */
.thprim-dev { opacity: .8; }
/* Thematics detail: the quote cell opens the transcript drawer at that
   passage. Text-colored like .tsr-quote — a paragraph of link-blue would
   out-shout the table — with the underline on hover carrying the affordance. */
table.data a.thquote { color: var(--text); text-decoration: none; }
table.data a.thquote:hover, table.data a.thquote:focus-visible {
  text-decoration: underline; }

/* inline show-all: a button that reads as a link (the bar form is .morebar) */
button.morelink { background: none; border: 0; padding: 0; cursor: pointer;
  color: var(--accent2); font: inherit; font-size: .8rem; }
button.morelink:hover { text-decoration: underline; }
/* 📄 beside a ticker in a table: same [data-rel-open] drawer as the panel link,
   but reduced to an icon so it rides in the Issuer cell without pushing the
   column. Dimmed at rest so a column of them does not out-shout the tickers. */
.prlink { font-size: .85em; opacity: .72; margin-left: .2rem;
  text-decoration: none; }
.prlink:hover, .prlink:focus-visible { opacity: 1; text-decoration: none; }

.entpanel h3 { margin: 0; font-size: 1.05rem; }
.entpanel h4 { margin: 1.1rem 0 .35rem; font-size: .92rem; color: var(--muted);
  font-weight: 600; }
.entpanel .card h4:first-child { margin-top: 0; }
.entpanel-head { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; }
.entpanel-head .tickerbadge { font-size: .95rem; padding: .1rem .5rem; }
/* the drawer's own research badges live here, beside the name (the drawer
   carries no strip) — free the fixed table-alignment width */
.entpanel-head .rmarks { width: auto; margin-right: 0; }
.entpanel-meta { margin: .3rem 0 .1rem; }
.entpanel-open { margin: .4rem 0 .8rem; font-size: .9rem; }
.entpanel .card { padding: .7rem .8rem; margin: .5rem 0; }
.entpanel .entstats dl { margin: 0; }
.entpanel .entstats dl div { padding: .18rem 0; border-bottom: 1px solid var(--line); }
.entpanel .entstats dl div:last-child { border-bottom: none; }
.entpanel .entstats dt { color: var(--muted); font-size: .85rem; }
.entpanel .entstats dd { font-size: .9rem; }
.entpanel .chartbox { padding: .5rem .4rem .2rem; margin: .2rem 0 .6rem; }
.entpanel .chart-sm { height: 170px; }
.entpanel table.data { font-size: .82rem; }
.entpanel table.data th, .entpanel table.data td { padding: .28rem .4rem; }
.entpanel .tablewrap { max-height: 20rem; overflow-y: auto; }
.entpanel .rangebar { margin: .2rem 0 0; }
/* the full group-research strip (watch / Interesting / B / S / comment) — the
   same controls as the entity page, tightened for the drawer's width */
.entpanel .research { margin: .2rem 0 .9rem; padding: .55rem .5rem; }
/* Watch / Interesting / Buyer / Seller / Comment are ONE row: the badge strip
   reads as a single line of controls, and the drawer is 32vw on a desktop —
   far wider than the 400px floor these sizes were cut against (the four
   labelled buttons come to ~334px of 352px in DejaVu Sans Bold, the widest
   fallback in the stack, and Segoe UI leaves far more). Only at that floor can
   Comment still drop to a second line. */
.entpanel .research-actions { gap: .25rem; margin: .5rem 0 .2rem; }
.entpanel .research-actions button, .entpanel .research .btnlike {
  font-size: .75rem; padding: .25rem .35rem; white-space: nowrap; }
.entpanel .research-actions .ricon { width: 1.25em; height: 1.25em; }
.entpanel .research-actions .inline-form { flex: none; }
.entpanel .research-head { gap: .5rem; }
/* the strip must not change size when a control is used:
   - the watch button holds room for its longer "Watching" label up front, so
     toggling it can never re-wrap the row;
   - the comment popup (also auto-opened as the optional note after flagging
     Interesting) hangs over the panel instead of expanding inside it. It
     anchors to the actions row rather than to its own summary — left/right 0
     of the row can't hang off the drawer the way the page's 24rem popover
     would. Only the comment/edit forms further down the panel stay in flow. */
.entpanel .research-form { position: static; width: auto; box-shadow: none;
  padding: .55rem .6rem; }
.entpanel .research-actions { position: relative; }
.entpanel .research-actions .research-watch { min-width: 5.4rem; }
.entpanel .research-actions .research-pop { flex: none; position: static; }
.entpanel .research-actions .research-form { position: absolute;
  top: calc(100% + .3rem); left: 0; right: 0; width: auto;
  box-shadow: 0 10px 26px rgba(0, 0, 0, .55); }
/* the "set by …" line appears the moment a flag is saved — hold its row from
   the start so activating a badge doesn't shove the panel down */
.entpanel .research-agg { min-height: 1.25rem; }

.chatgrid { display: grid; grid-template-columns: 220px 1fr; gap: 1rem;
  height: calc(100vh - 8.5rem); height: calc(100dvh - 8.5rem); }
.chatside { border-right: 1px solid var(--line); padding-right: .8rem; overflow-y: auto; }
.chatlist { list-style: none; margin: .8rem 0 0; padding: 0; }
.chatlist li { padding: .4rem .5rem; border-radius: 6px; cursor: pointer; color: var(--muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.chatlist li:hover, .chatlist li.active { background: var(--panel); color: var(--text); }
.chatmain { display: flex; flex-direction: column; min-height: 0; }
.messages { flex: 1; overflow-y: auto; padding: .5rem .2rem; }
.msg { display: flex; margin: .5rem 0; }
.msg.user { justify-content: flex-end; }
.msg .bubble { max-width: 72%; padding: .6rem .9rem; border-radius: 12px;
  background: var(--panel); border: 1px solid var(--line); overflow-x: auto; }
.msg.user .bubble { background: #2a3a4e; }
.msg .bubble p { margin: .25rem 0; }
.msg .bubble pre { background: var(--bg); padding: .5rem .7rem; border-radius: 6px; overflow-x: auto; }
.artifact-card { display: block; margin: .5rem 0 .5rem 2rem; padding: .7rem 1.1rem;
  border: 1px solid var(--accent); border-radius: 8px; background: var(--panel2);
  color: var(--accent); font-size: 1.05rem; font-weight: 600; max-width: 72%; }
.artifact-card:hover { background: #2a3547; text-decoration: none; }

/* --- web-research source strip ---
   Approved sources carry the accent underline the answer's facts may rest on;
   leads are muted and dashed, so "we read this" never looks like "we verified
   this" at a glance. */
.sourcestrip { display: flex; flex-wrap: wrap; gap: .45rem; align-items: baseline;
  margin: .35rem 0 .6rem 2rem; max-width: 72%; }
.sourcestrip .src { font-size: .82rem; padding: .1rem .45rem; border-radius: 5px;
  background: var(--panel2); border: 1px solid transparent; }
.sourcestrip .src.ok { color: var(--accent); border-color: var(--accent); }
.sourcestrip .src.lead { color: var(--muted); border-style: dashed;
  border-color: var(--line); }
.sourcestrip .src:hover { text-decoration: none; color: var(--text); }

/* --- dataview drill-down --- */
.dv-tgl { width: 1.8rem; }
.dv-toggle { background: none; border: none; color: var(--accent2); cursor: pointer;
  font: inherit; padding: 0 .25rem; margin: 0; line-height: 1; }
.dv-toggle:hover { color: var(--text); }
tr.dv-childwrap > td { background: var(--bg); padding: .25rem .5rem .6rem 1.6rem; }
table.dv-subtable { font-size: .88em; }
table.dv-subtable thead th { background: var(--bg); }
/* Rows with hover detail keep the native title tooltip; no help cursor —
 * a question-mark cursor on every row read as clutter. */

/* --- dealer positions: industry filter card ---
 * One ranking, not two half-width lists: the industries sort by customer net
 * so the buyers stack from the top and the sellers from the bottom, and the
 * full card width goes to a single table (net, volume, one-sidedness, tranche
 * count) instead of being halved into two three-column stubs. The security
 * card is height-pinned to it (data-match-height), so filtering never
 * reflows. */
.dealertr { overflow-y: auto; }
#dealerind .tablewrap { max-height: 26rem; overflow-y: auto; }
.indbtn { background: none; border: none; color: var(--accent2); cursor: pointer;
  font: inherit; padding: 0; text-align: left; }
.indbtn:hover { color: var(--text); }
.indbtn.active { color: var(--text); font-weight: 600; }
/* the sign of the flow, carried on the industry row so direction reads without
   relying on colour alone */
#dealerind td.side { color: var(--muted); font-size: .8rem; }

/* --- dataview share popover --- */
.dvtitle { display: flex; align-items: center; gap: .9rem; flex-wrap: wrap;
  margin: .2rem 0 .4rem; }
.dvtitle h1 { margin: 0; }
.dvtitle .sharebtn { margin: 0; padding: .3rem .8rem; font-size: .85rem; }
.sharebtn.shared { background: var(--pos); border-color: var(--pos); color: #0c1710; }
.sharebtn.shared:hover { background: #63c188; }
.sharewrap { position: relative; display: inline-block; }
.dvtitle .sharepop { right: auto; left: 0; }
.sharepop { position: absolute; right: 0; top: calc(100% + .4rem); z-index: 50;
  width: min(26rem, 90vw); background: var(--panel2); border: 1px solid var(--line);
  border-radius: var(--radius); padding: .9rem 1.1rem;
  box-shadow: 0 8px 24px rgba(0,0,0,.5); }
.sharepop input[type=text] { max-width: 100%; font-size: .85rem; }
.sharepop button { margin-top: .4rem; }
.dvlink { font-size: 1.05rem; font-weight: 600; }
.chatform { display: flex; gap: .6rem; margin-top: .6rem; }
.chatform textarea { flex: 1; max-width: none; resize: vertical; }
.chatform button { margin-top: 0; align-self: flex-end; }
/* chat page on small screens: the session sidebar becomes a horizontal strip
 * (new-chat button + scrollable chip list) so switching chats still works */
@media (max-width: 800px) {
  .chatgrid { grid-template-columns: 1fr; grid-template-rows: auto minmax(0, 1fr);
    height: calc(100vh - 7.5rem); height: calc(100dvh - 7.5rem); }
  .chatside { display: flex; align-items: center; gap: .6rem; min-width: 0;
    border-right: none; border-bottom: 1px solid var(--line); padding: 0 0 .6rem; }
  .chatside > button { margin: 0; white-space: nowrap; font-size: .85rem;
    padding: .35rem .8rem; }
  .chatlist { display: flex; gap: .4rem; margin: 0; flex: 1; min-width: 0;
    overflow-x: auto; padding-bottom: .15rem; }
  .chatlist li { flex: 0 0 auto; max-width: 11rem; border: 1px solid var(--line);
    border-radius: 999px; padding: .25rem .8rem; }
}

/* --- group research workspace --- */
.navbadge { display: inline-block; margin-left: .35rem; padding: 0 .45rem;
  border-radius: 999px; background: var(--accent); color: #14100a;
  font-size: .75rem; font-weight: 700; }
.navworkspace select { background: var(--bg); border: 1px solid var(--line);
  color: var(--text); border-radius: 6px; padding: .25rem .5rem; font-size: .85rem;
  max-width: 11rem; }
.research { background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--radius); padding: .7rem .9rem; margin: .6rem 0 1rem; }
.research-head { display: flex; gap: .8rem; align-items: center; flex-wrap: wrap; }
.research-actions { display: flex; gap: .5rem; flex-wrap: wrap; align-items: flex-start;
  margin: .55rem 0 .2rem; }
.research-actions button, .btnlike { font-size: .85rem; padding: .3rem .8rem; margin: 0; }
.research-actions button.active { outline: 2px solid var(--accent2); }
.research-watch .star { color: var(--muted); }
.research-actions .research-watch.active { outline-color: var(--accent); }
.research-watch.active .star { color: var(--accent); }
/* popover/active states: comment white+red, interesting white+blue —
   both darkened from the palette hues for AA contrast on white */
.research-pop[open] > .btnlike { background: #fff; border: 1px solid var(--neg);
  color: #a83a44; outline: none; }
.research-actions .research-tag.active,
.research-pop-tag[open] > .btnlike { background: #fff; border: 1px solid var(--accent2);
  color: #2b6ca3; outline: none; }
.research-actions .stance-buy.active { background: var(--pos);
  border-color: var(--pos); color: #101820; outline: none; }
.research-actions .stance-sell.active { background: var(--neg);
  border-color: var(--neg); color: #101820; outline: none; }
/* research mark chips: B green = better buyer, S red = better seller,
   I blue with white trim = Interesting, amber ⇄ = marked both ways (swap —
   display-only, never a button). .off keeps the chip visible but grayed.
   Rounded squares, not circles: the inline slot shows two of them side by
   side (stance + Interesting), and a square reads as one of a set where a
   circle read as a lone dot. */
.ricon { display: inline-flex; align-items: center; justify-content: center;
  width: 1.5em; height: 1.5em; border-radius: 5px; border: 1.5px solid transparent;
  font-style: normal; font-weight: 700; font-size: .72em; line-height: 1;
  flex: none; cursor: default; user-select: none; color: #101820;
  padding: 0; margin: 0; }
.ricon-buy { background: var(--pos); }
.ricon-sell { background: var(--neg); }
.ricon-int { background: var(--accent2); border-color: #fff; }
/* the swap pill says "Swap" and spans exactly the B+S columns */
.ricon-swap { background: var(--accent); width: 3.28em; }
/* an unset chip is a hole in the strip, not a control: dimmer than the panel
   it sits on so the set ones carry all the attention */
.ricon.off { background: #242c37; border-color: transparent; color: #596575; }
/* watchlist star inside the action menu */
.ricon-watch { background: var(--accent); }
/* single-unit swap chip used in the inline mark slot (the wide pill above is
   the research strip's, where it spans the B and S columns) */
.ricon-swap.mini { width: 1.5em; }
/* read-only roll-ups (issuer rows aggregate their securities' stances): outlined,
   vs the filled look of badges you can click */
.ricon.ro.ricon-buy { background: transparent; border-color: var(--pos); color: var(--pos); }
.ricon.ro.ricon-sell { background: transparent; border-color: var(--neg); color: var(--neg); }
.ricon.ro.off { background: transparent; border-color: #3a4757; color: #5d6a7a; }
/* clickable badges (buttons) get pointer + hover ring; a POST in flight dims */
button.ricon { cursor: pointer; }
button.ricon:hover { box-shadow: 0 0 0 2px #dbe4ee40; }
button.ricon:disabled { opacity: .5; cursor: default; }
/* the table-badge "mark Interesting" note form: viewport-fixed at the clicked
   badge (left/top set by app.js). The compound selector must outrank
   .research-form's position: absolute, which is declared later in this file. */
.research-form.rmark-pop { position: fixed; }
/* read-only security-stance cluster on the issuer research panel */
.stance-ro { display: inline-flex; gap: .25em; align-items: center;
  align-self: center; }
/* badges keep a visible ring on the solid active stance buttons */
.stance-buy.active .ricon, .stance-sell.active .ricon {
  border-color: rgba(16, 24, 32, .5); }
/* entity research marks: a fixed-width slot before every security/issuer link
   in tables and cards, holding TWO chips — stance, then Interesting — both
   always drawn (grayed when unset) so row text always starts at the same
   offset and a flagged name differs from an unflagged one only in colour. */
.rmarks { display: inline-flex; gap: .25em; margin-right: .3rem; width: 2.4em;
  flex: none; vertical-align: baseline; align-items: center; }
/* research action strip (opened by clicking a mark circle): one row of the
   B/S/I/W letter toggles over a single-line comment box. The compound
   selectors outrank .research-form's width/padding, declared later. */
.rmark-pop.rmenu { display: flex; flex-direction: column; gap: .4rem;
  width: min(14rem, 90vw); padding: .45rem .5rem; }
.rstrip { display: flex; gap: .3rem; }
.rstrip .rcell { display: flex; align-items: center; justify-content: center;
  flex: 1 1 0; background: var(--bg); border: 1px solid var(--line);
  margin: 0; padding: .3rem 0; border-radius: 6px; cursor: pointer; }
.rstrip .rcell:hover { border-color: var(--accent2); }
.rstrip .rcell[aria-pressed="true"] { background: var(--panel2);
  border-color: var(--accent2); }
.rstrip .rcell:disabled { opacity: .55; cursor: default; }
.rstrip .rcell:disabled:hover { border-color: var(--line); }
.rstrip .rcell .ricon { font-size: .78rem; }
.rcomment { display: flex; gap: .3rem; align-items: center; }
.rmark-pop.rmenu .rcomment input[type=text] { flex: 1 1 auto; min-width: 0;
  width: auto; margin: 0; padding: .28rem .4rem; font-size: .8rem; }
.rmenu button.rpost { flex: none; margin: 0; padding: .3rem .5rem;
  font-size: .78rem; }
/* fund overview "In Focus" bucket headings */
.infocus-h { display: flex; align-items: center; gap: .45rem;
  margin: 1rem 0 .3rem; font-size: 1rem; }
.btnlike { display: inline-block; background: var(--panel2); color: var(--text);
  border: 1px solid var(--line); border-radius: 6px; cursor: pointer;
  list-style: none; user-select: none; }
.btnlike::-webkit-details-marker { display: none; }
details[open] > .btnlike { outline: 2px solid var(--accent2); }
.research-pop { position: relative; display: inline-block; }
.research-form { position: absolute; left: 0; top: calc(100% + .4rem); z-index: 40;
  width: min(24rem, 88vw); background: var(--panel2); border: 1px solid var(--line);
  border-radius: var(--radius); padding: .8rem 1rem;
  box-shadow: 0 8px 24px rgba(0,0,0,.5); }
.research-form textarea, .research-form input[type=text] { max-width: 100%; width: 100%; }
.research-form button { margin-top: .5rem; }
.rulesform { position: static; width: min(56rem, 96vw); box-shadow: none; }
.research-agg { display: flex; gap: 1.1rem; align-items: baseline; flex-wrap: wrap;
  margin: .3rem 0; }
.inlinedetails { display: inline-block; }
.inlinedetails > summary { cursor: pointer; color: var(--accent2); list-style: none; }
.inlinedetails > summary::-webkit-details-marker { display: none; }
.inlinedetails[open] > summary { color: var(--text); }
.plainlist { list-style: none; margin: .3rem 0; padding: 0; }
.plainlist li { padding: .12rem 0; }
.research-comments { margin-top: .5rem; border-top: 1px solid var(--line); }
.research-comment { padding: .5rem 0 .3rem; border-bottom: 1px solid var(--line); }
.research-comment:last-child { border-bottom: none; }
.research-body { margin: .25rem 0; white-space: pre-line; }
.activity li { padding: .2rem 0; border-bottom: 1px solid var(--line); }
.activity li:last-child { border-bottom: none; }
.scenarioform { background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 1rem 1.2rem; }
.badge.unread { color: var(--accent); border-color: var(--accent); }
.badge.acknowledged { color: var(--accent2); }
.badge.resolved { color: var(--muted); }
h3.small { margin: .6rem 0 .4rem; }
.chattext { white-space: pre-wrap; word-break: break-word; margin: 0; }

/* ============================== mobile (≤820px) ==============================
 * Phones and small tablets. The topnav collapses behind a ☰ toggle driven by
 * a hidden checkbox (works without JS, no inline handlers — CSP-clean); the
 * checkbox itself stays focusable so keyboard users can toggle with Space. */
@media (max-width: 820px) {
  /* keeps the drawer inset in the shorthand (zero at this width today, but the
     shorthand would drop it if the 900px cutoff ever moves) */
  .content { padding: .9rem calc(.8rem + var(--drawer-inset)) 3.5rem .8rem; }
  h1 { font-size: 1.3rem; }

  .topnav { position: relative; gap: .6rem .9rem; padding: .5rem .8rem; }
  .navtoggle { display: block; margin-left: auto; font-size: 1.3rem; line-height: 1;
    padding: .2rem .6rem; border: 1px solid var(--line); border-radius: 6px;
    color: var(--text); cursor: pointer; user-select: none; }
  .navcheck { display: block; position: absolute; width: 1px; height: 1px;
    margin: 0; opacity: 0; pointer-events: none; }
  .navcheck:focus-visible + .navtoggle { outline: 2px solid var(--accent2); }
  .navcheck:checked + .navtoggle { background: var(--panel2); }
  .navlinks, .navuser { display: none; }
  .navcheck:checked ~ .navlinks { display: flex; flex-direction: column;
    align-items: stretch; gap: 0; width: 100%; order: 10; }
  .navcheck:checked ~ .navuser { display: flex; flex-direction: column;
    align-items: stretch; gap: .7rem; width: 100%; order: 11; margin-left: 0;
    border-top: 1px solid var(--line); padding-top: .8rem; }
  .navlinks a { padding: .5rem .1rem; }
  .navlinks .fundchips { padding: .45rem 0; }
  .navlinks .fundchips a { padding: .05rem .5rem; }
  .navsep { display: none; }
  /* inside the ☰ sheet the admin menus expand in flow — a floating panel
     anchored to a full-width row would hang off the screen */
  .navmenu { width: 100%; }
  .navmenu > summary { padding: .5rem .1rem; }
  .navmenu-items { position: static; min-width: 0; background: none; border: none;
    box-shadow: none; padding: 0 0 .3rem .9rem; }
  .navmenu-items a { padding: .35rem .1rem; }
  .adminbar { gap: .1rem .7rem; }
  .navuser .navsearch input { width: 100%; max-width: none; }
  .navworkspace select { width: 100%; max-width: none; }

  /* 16px form fields stop iOS Safari from zooming in on focus; the extra
   * selectors out-rank the smaller component-level font sizes above */
  input[type=text], input[type=password], input[type=search], input[type=number],
  input[type=email], input[type=date], select, textarea,
  .entityhead .asof input[type=date], .chatdrawer .drawerhead select,
  .chatdrawer .chatform textarea, .navworkspace select,
  .sharepop input[type=text] { font-size: 16px; }

  .chart-lg { height: 260px; } .chart-md { height: 230px; } .chart-sm { height: 200px; }

  .entityhead { gap: .5rem .9rem; }
  .entityhead .asof { margin-left: 0; flex-basis: 100%; }
  .subtabs { gap: 0; }
  .subtabs a { padding: .5rem .7rem; }

  table.data { font-size: .85rem; }
  table.data th, table.data td { padding: .35rem .5rem; }
  .kpis { gap: .7rem; }
  .rangebar, .modebtns { flex-wrap: wrap; }

  .msg .bubble { max-width: 92%; }
  .artifact-card { margin-left: 0; max-width: 100%; }
  .sourcestrip { margin-left: 0; max-width: 100%; }

  /* anchored popovers clamp to the viewport instead of their trigger */
  .research-form, .sharepop, .dvtitle .sharepop { position: fixed;
    left: .6rem; right: .6rem; top: auto; width: auto;
    max-height: 70vh; overflow-y: auto; }
  .rulesform { position: static; left: auto; right: auto; width: auto;
    max-height: none; overflow-y: visible; }
}

/* small phones: the chat drawer takes the full screen */
@media (max-width: 600px) {
  .chatdrawer, body.chat-w20 .chatdrawer { width: 100vw;
    padding: .7rem .8rem calc(.7rem + env(safe-area-inset-bottom, 0px)); }
  .chatdrawer [data-chat-width] { display: none; } /* width toggle is moot */
  .chatdrawer .drawerhead { flex-wrap: wrap; }
  body.chat-open { overflow: hidden; } /* don't scroll the page behind it */
}

/* Daily fund briefs (dashboard card blurb + fund page panel) */
.fundcard .brief { margin-top: .55rem; padding-top: .55rem;
  border-top: 1px solid var(--line); color: var(--muted); font-size: .85rem;
  line-height: 1.4; display: -webkit-box; -webkit-line-clamp: 2;
  -webkit-box-orient: vertical; overflow: hidden; }
.dailybrief { max-width: 80ch; }
.dailybrief .brief-headline { font-weight: 600; margin: 0 0 .55rem; }
.dailybrief .brief-body { margin: 0 0 .6rem; line-height: 1.55; }
.dailybrief p:last-child { margin-bottom: 0; }

/* --- issuer securities table: securities no fund currently holds --- */
tr.notheld td { opacity: .55; }
tr.notheld.focused td { opacity: 1; }
.gridbar label { display: inline-flex; align-items: center; gap: .4rem; }

/* --- grid columns: start level (no stray top margin on the first heading),
       keep long side-tables from stretching their row --- */
.cols2 > div > h2:first-child, .cols3 > div > h2:first-child { margin-top: 0; }
.tablewrap.capped { max-height: 19rem; overflow-y: auto; }
/* The Earnings tab's report history is the page's main table — a 19rem window
   turns ~200 reports into a peephole. Give it most of the viewport and let the
   sticky header carry the column labels down the scroll. */
.tablewrap.capped.tall { max-height: min(78vh, 60rem); }
@media (max-width: 900px) { .tablewrap.capped.tall { max-height: 34rem; } }
/* the industry currently driving the cross-filter */
[data-xset-key] .indbtn.active { color: var(--accent); font-weight: 700; }
[data-xset-key] tr:has(.indbtn.active) { background: var(--panel2); }
.dailybrief { min-height: 32rem; max-height: 40rem; overflow-y: auto; }
/* floor for height-matched row cards (brief, dealer, earnings, drill, movers):
   min-height beats the inline height set by matchHeights, so a short reference
   chart can no longer collapse a whole row */
[data-match-height], .movercard .tablewrap, .dealercard { min-height: 16rem; }
/* the brief row keys off the sector-contribution chart box: floor it to the
   brief's height and the height-matchers align the whole row (the responsive
   chart stretches its bars to fill) */
.chartbox:has(> #contribchart) { min-height: 32rem; }
[data-match-height] { overflow-y: auto; }
/* section rhythm on the fund tabs: the methodology footnotes that used to sit
   between a block and the next heading now live in that block's ? tooltip, so
   the heading has to carry the separation itself. A grid row following another
   block needs it too — its own heading is a column's first child and so has no
   top margin of its own (see the .cols2 > div > h2 rule above). */
body:has([data-fundpage])
  :is(.tablewrap, .chartbox, .cols2, .cols3, .cards4, .kpis, [data-mode-scope])
  + :is(h2, .sectionbar, .cols2, .cols3, .cards4) { margin-top: 2.4rem; }

/* --- historical tab: custom-period form sits inline in the section bar --- */
.sectionbar .filterbar { margin: 0 0 0 auto; align-items: center; }
.sectionbar .filterbar label { font-size: .85rem; color: var(--muted); }

/* --- dealer positions card (fund overview) --- */
.dealercard h3 + table { margin-bottom: .7rem; }
.dealercard table.data th:nth-child(n+2), .dealercard table.data td:nth-child(n+2) {
  width: 3.9rem; }
.dealercard table.data th:nth-child(2), .dealercard table.data td:nth-child(2) {
  width: 5rem; }
.dealercard table.data td:first-child { overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; }
/* dealer security panel: the card itself must not scroll — each table scrolls
   inside its own .tablewrap so the sticky column headers stay pinned. Panels
   flex-column so the tablewrap takes exactly the space under its heading
   (matchHeights gives .dealertr a definite inline height). display only when
   not [hidden], else the flex display would defeat the hidden attribute. */
.dealertr { overflow: hidden; }
.dealertr [data-mode-panel]:not([hidden]) { display: flex; flex-direction: column;
  height: 100%; min-height: 0; }
.dealertr [data-mode-panel] h3 { flex: 0 0 auto; }
.dealertr [data-mode-panel] .tablewrap { flex: 1 1 auto; min-height: 0;
  max-height: none; overflow-y: auto; border: 0; border-radius: 0; }

/* --- customer-flow cards: their own column grid ---
   These are half-page cards, not the 4-up movers, so the mover widths above
   (tuned for a quarter-page card) both clipped the "Customer net" title at
   4.2rem and left the two unsized trailing columns splitting the slack with
   the name. Every numeric column is sized here, which frees the rest for the
   name, and the cells get the roomier page padding back. Titles wrap rather
   than truncate: if a card is squeezed narrow (both drawers open on a laptop)
   a two-line header still reads. */
#dealerind table.data th, #dealerind table.data td,
.dealertr table.data th, .dealertr table.data td { padding: .34rem .6rem; }
#dealerind table.data thead th, .dealertr table.data thead th {
  white-space: normal; vertical-align: bottom; }
/* Industry | Customers | Customer net | Volume | One-sided | Tranches — every
   trailing column fixed so the industry name absorbs the slack. The fixed
   layout itself comes from .movercard, so the phone rules can still release
   it (an #id table-layout here would outrank them and re-truncate names). */
#dealerind table.data th:nth-child(2), #dealerind table.data td:nth-child(2) { width: 3.6rem; }
#dealerind table.data th:nth-child(3), #dealerind table.data td:nth-child(3) { width: 6.6rem; }
#dealerind table.data th:nth-child(4), #dealerind table.data td:nth-child(4) { width: 5.6rem; }
#dealerind table.data th:nth-child(5), #dealerind table.data td:nth-child(5) { width: 5rem; }
#dealerind table.data th:nth-child(6), #dealerind table.data td:nth-child(6) { width: 4.2rem; }
/* the industry name is a button, so it needs its own clipping — the cell's
   ellipsis does not reach inside it */
#dealerind .indbtn { max-width: 100%; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; display: inline-block; vertical-align: bottom; }
/* Security | Customer net | Px | ΔPx | YTW | Dur */
.dealertr table.data th:nth-child(2), .dealertr table.data td:nth-child(2) { width: 6.8rem; }
.dealertr table.data th:nth-child(3), .dealertr table.data td:nth-child(3),
.dealertr table.data th:nth-child(4), .dealertr table.data td:nth-child(4),
.dealertr table.data th:nth-child(5), .dealertr table.data td:nth-child(5) { width: 4.5rem; }
.dealertr table.data th:nth-child(6), .dealertr table.data td:nth-child(6) { width: 4rem; }

/* --- fund-page "How to" box (gold glow off the site accent #d9a441) --- */
.howto { margin: 0 0 1.1rem; padding: .65rem 1rem .75rem;
  background: #222b3a; /* a shade above --panel so the box reads as its own layer */
  border-color: var(--accent);
  box-shadow: 0 0 9px rgba(217, 164, 65, .35), 0 0 22px rgba(217, 164, 65, .16),
    inset 0 0 14px rgba(217, 164, 65, .07); }
.howto-head strong { color: var(--accent); }
@media (prefers-reduced-motion: no-preference) {
  .howto { animation: howto-glow 2.8s ease-in-out infinite alternate; }
  @keyframes howto-glow {
    from { box-shadow: 0 0 6px rgba(217, 164, 65, .25), 0 0 16px rgba(217, 164, 65, .10),
      inset 0 0 14px rgba(217, 164, 65, .05); }
    to { box-shadow: 0 0 12px rgba(217, 164, 65, .55), 0 0 30px rgba(217, 164, 65, .28),
      inset 0 0 14px rgba(217, 164, 65, .10); }
  }
}
.howto-head { display: flex; align-items: center; gap: .8rem; flex-wrap: wrap; }
.howto-btns { margin-left: auto; display: inline-flex; gap: .9rem; }
.howto-body ul { margin: .55rem 0 0; padding-left: 1.15rem; }
.howto-body li { margin: .3rem 0; line-height: 1.5; }
.howto-body li ul { margin: .15rem 0 .1rem; }
.howto-body[hidden] { display: none; }

/* --- inline help: ? icons on cards (instant tooltip on hover/focus) and
   column descriptions on th[data-help] (tooltip only after the pointer rests
   on the title for 2s). th tooltips draw with ::before — ::after is taken by
   the sort arrows. Pure CSS so views stay CSP-clean. --- */
.qh { display: inline-flex; align-items: center; justify-content: center;
  position: relative; width: 1.5em; height: 1.5em; border-radius: 50%;
  border: 1px solid var(--muted); color: var(--muted); font-size: .68em;
  font-weight: 700; font-style: normal; line-height: 1; cursor: help;
  user-select: none; vertical-align: .2em; margin-left: .35em; flex: none; }
.qh:hover, .qh:focus-visible { color: var(--accent2); border-color: var(--accent2); }
.qh::after, th[data-help]::before {
  content: attr(data-help); position: absolute; left: 0; top: calc(100% + .5em);
  z-index: 60; width: max-content; max-width: min(24rem, 70vw);
  background: var(--panel2); color: var(--text); border: 1px solid var(--line);
  border-radius: 8px; padding: .55rem .75rem; font-size: .8rem; font-weight: 400;
  line-height: 1.45; text-align: left; white-space: pre-line;
  box-shadow: 0 8px 24px rgba(0, 0, 0, .5);
  opacity: 0; visibility: hidden; pointer-events: none;
  transition: opacity .15s ease, visibility 0s linear .15s; }
.qh:hover::after, .qh:focus-visible::after {
  opacity: 1; visibility: visible; transition-delay: 0s; }
th[data-help]:hover::before {
  opacity: 1; visibility: visible; transition: opacity .2s ease 2s, visibility 0s linear 2s; }
/* right-aligned numeric columns anchor their tooltip to the right edge so it
   stays inside the table wrap */
th.num[data-help]::before { left: auto; right: 0; }
/* head cells are already positioned (sticky); body row headers carrying a
   description are not, and an unpositioned th throws its ::before at the page
   instead of at itself */
table.data tbody th[data-help] { position: relative; }

/* --- phones: security/issuer names must read in full — everything else
   yields. Names and headers wrap instead of truncating; numbers stay on
   one line. --- */
@media (max-width: 700px) {
  table.data { font-size: .82rem; }
  table.data th, table.data td { white-space: normal; padding: .32rem .45rem; }
  table.data td.num { white-space: nowrap; }
  /* column titles may wrap/stack into narrow headers */
  table.data thead th { white-space: normal; overflow-wrap: anywhere;
    font-size: .72rem; line-height: 1.15; vertical-align: bottom; }
  /* mover cards: release the fixed column grid + ellipsis so names wrap
     visibly instead of being cut */
  .movercard table.data { table-layout: auto; }
  .movercard table.data th, .movercard table.data td { overflow: visible;
    text-overflow: clip; }
  /* industry names wrap in full too — they are buttons, so they clip on their
     own account rather than through the cell */
  #dealerind .indbtn { max-width: none; overflow: visible; white-space: normal;
    display: inline; }
}

/* --- coverage heatmap (/admin/quality) ---
   The cell fill is an ORDINAL severity ramp, not a categorical palette: one
   hue (OKLCH H 18, the --neg family, deliberately outside the fixed fund
   colors so a cell never impersonates a fund), lightness stepping 0.46 ->
   0.78 with dL >= 0.06 per step, validated against the #1a212b panel with the
   dataviz ordinal checks (light end 2.09:1 vs surface). Louder = worse, which
   on a dark surface means brighter. Full coverage takes no fill at all, so
   the eye lands only on gaps. Ink flips to dark at hm-3 where the fill gets
   light enough that #dbe4ee would drop under 4.5:1 — every step clears it.
   Colour is never the only channel: each cell prints its own percentage. --- */
/* separate borders give the 2px surface gap between fills without a stroke
   around any cell; row rules would only add ink the fills already separate */
table.heat { border-collapse: separate; border-spacing: 2px; margin: .3rem 0; }
table.heat th, table.heat td { border-bottom: none; }
/* relative, not static: only four rows so sticky buys nothing, but the column
   help tooltips still need their th as the containing block */
table.heat thead th { position: relative; background: transparent;
  vertical-align: bottom; }
table.heat thead th.grp { color: var(--muted); font-weight: 600;
  text-align: center; border-bottom: 1px solid var(--line);
  font-size: .72rem; letter-spacing: .05em; text-transform: uppercase;
  padding-bottom: .25rem; }
/* the fund column survives horizontal scrolling on a 14-column table */
table.heat .rowhead { position: sticky; left: 0; z-index: 2;
  background: var(--panel); }
table.heat tbody tr:hover .rowhead { background: var(--panel2); }
table.heat td.hm { border-radius: 4px; }
td.hm-full, td.hm-na { color: var(--muted); }
td.hm-1 { background: #9e2034; color: #dbe4ee; }
td.hm-2 { background: #b43745; color: #dbe4ee; }
td.hm-3 { background: #d55660; color: #101820; }
td.hm-4 { background: #f16f78; color: #101820; }
td.hm-5 { background: #ff9498; color: #101820; }
.heatlegend { display: flex; align-items: center; flex-wrap: wrap; gap: .4rem;
  margin: .5rem 0 1.4rem; }
.hmkey { display: inline-block; padding: .1rem .5rem; border-radius: 6px;
  font-size: .78rem; border: 1px solid var(--line); color: var(--muted); }
.hmkey.hm-1 { background: #9e2034; color: #dbe4ee; border-color: #9e2034; }
.hmkey.hm-2 { background: #b43745; color: #dbe4ee; border-color: #b43745; }
.hmkey.hm-3 { background: #d55660; color: #101820; border-color: #d55660; }
.hmkey.hm-4 { background: #f16f78; color: #101820; border-color: #f16f78; }
.hmkey.hm-5 { background: #ff9498; color: #101820; border-color: #ff9498; }
@media (max-width: 700px) {
  /* a sticky first column plus a scrolling body is worse than plain scroll
     on a phone-width viewport — release it */
  table.heat .rowhead { position: static; }
}

/* --- operations table (/admin/system): six columns of mixed prose and
   timestamps, so release the nowrap the data tables use and let the row
   header carry its unit name on a second line. --- */
table.ops th, table.ops td { white-space: normal; vertical-align: top; }
table.ops th[data-help] { min-width: 12rem; }
table.ops td { min-width: 7rem; }
.opsunit { display: block; color: var(--muted); font-size: .78rem;
  font-weight: 400; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }

/* ================= ISSUER/ISSUE PREVIEW (/admin/marks) =================
   TEMPORARY. Layout for the design preview of the three-column mark slot and
   the scope-labelled inputs. The chips themselves are the real .ricon classes,
   so the page shows exactly what production would. Delete this block with the
   route, admin_marks.php and views/admin/marks.php. */
.mkp-slot { display: inline-flex; align-items: center; gap: .25em;
  margin-right: .35rem; width: 3.6em; flex: none; vertical-align: baseline; }
.mkp-slot2 { width: 2.4em; }
/* the credit chip is its own group — a wider gap separates "the company" from
   "the paper" without a divider rule */
.mkp-gap { width: .22em; flex: none; }
.mkp-key { display: grid; gap: .45rem; background: var(--panel);
  border: 1px solid var(--line); border-radius: var(--radius);
  padding: .8rem 1rem; margin: .6rem 0 .8rem; font-size: .88rem; }
.mkp-key span { display: flex; align-items: center; gap: .5rem; }
.mkp-key b { font-weight: 600; }
/* menus side by side, drawn in flow (the real one is position: fixed) */
.mkp-menus { display: grid; grid-template-columns: 1fr 1fr; gap: 1.6rem;
  align-items: start; }
@media (max-width: 900px) { .mkp-menus { grid-template-columns: 1fr; } }
.mkp-menus h3 { margin: 0 0 .4rem; font-weight: 600; }
.research-form.rmark-pop.mkp-menu { position: static; width: 17rem; }
.mkp-menuhead { display: flex; flex-direction: column; line-height: 1.3;
  padding-bottom: .45rem; margin-bottom: .1rem; border-bottom: 1px solid var(--line); }
.mkp-mrow { display: flex; align-items: center; gap: .5rem; }
.mkp-mlab { display: flex; flex-direction: column; font-size: .78rem; font-weight: 600;
  color: var(--text); width: 5.6rem; flex: none; line-height: 1.2; }
.mkp-mlab em { font-style: normal; font-weight: 400; font-size: .72rem; color: var(--muted); }
.mkp-mrow .rstrip { flex: 1 1 auto; }
.mkp-trsum { display: flex; align-items: center; gap: .35rem; font-size: .8rem; }
/* scope labels inside the research strip */
.mkp-strip .research-actions { align-items: center; }
.mkp-scope { font-size: .72rem; font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase; color: var(--muted); }
.mkp-div { width: 1px; align-self: stretch; background: var(--line); margin: 0 .35rem; }
.mkp-cost { margin: .4rem 0 0; padding-left: 1.15rem; font-size: .9rem; }
.mkp-cost li { margin: .35rem 0; }

/* Read-only prompt text on /admin/prompts (shipped default, history entries) */
.codeblock { background: var(--bg); border: 1px solid var(--line); border-radius: 6px;
  padding: .6rem .8rem; font-family: ui-monospace, monospace; font-size: .85rem;
  white-space: pre-wrap; word-break: break-word; max-height: 26rem; overflow-y: auto; }

/* ── Documents (/admin/documents) ──────────────────────────────────────────
   A rail of folders beside one pane that shows whatever is selected — a
   folder's contents, a rendered document, an image, an editor. Both panes
   scroll inside a fixed frame so selecting a file never moves the page: the
   rail stays where you left it and the reading pane starts at the top. */
.docmgr { display: grid; grid-template-columns: minmax(14rem, 20rem) minmax(0, 1fr);
  gap: 1.2rem; align-items: start; }
@media (max-width: 900px) { .docmgr { grid-template-columns: 1fr; } }

.docrail { background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--radius); padding: .6rem .5rem; max-height: 78vh;
  overflow: auto; font-size: .9rem; }
.docrootrow { display: flex; align-items: center; gap: .35rem; padding: .3rem .35rem;
  border-radius: 6px; margin-top: .5rem; }
.docrootrow:first-child { margin-top: 0; }
.docrail .docname.root { font-weight: 700; color: var(--accent); }

.doctree { list-style: none; margin: .1rem 0 0; padding-left: .85rem; }
.doctree li { margin: 0; }
.doctree summary { list-style: none; cursor: pointer; border-radius: 6px;
  padding: .12rem .25rem; }
.doctree summary::-webkit-details-marker { display: none; }
/* the disclosure triangle is drawn here so the whole row, including the
   triangle, is one drop target */
.doctree summary::before { content: "▸"; color: var(--muted); display: inline-block;
  width: 1em; transition: transform .12s ease; }
.doctree details[open] > summary::before { transform: rotate(90deg); }
@media (prefers-reduced-motion: reduce) {
  .doctree summary::before { transition: none; }
}
.docname { display: inline-block; padding: .05rem .2rem; border-radius: 4px;
  color: var(--text); word-break: break-word; }
.docname.folder { font-weight: 600; }
.docname.file { color: var(--accent2); }
.docname.on { background: var(--panel2); box-shadow: inset 2px 0 0 var(--brand);
  color: var(--text); }
.docname:hover { text-decoration: none; background: var(--panel2); }

/* drag state: the target under the pointer is the only thing that changes, so
   the tree does not reflow while you are aiming at it */
body.doc-dragging .docname { cursor: grabbing; }
.dropover, .doctree summary.dropover, .doccrumbs a.dropover {
  outline: 2px dashed var(--brand); outline-offset: 1px; border-radius: 6px;
  background: rgba(111, 180, 234, .12); }
tr.dropover td { background: rgba(111, 180, 234, .12); }

.docmain { min-width: 0; }
.doccrumbs { display: flex; flex-wrap: wrap; align-items: center; gap: .3rem;
  font-size: .95rem; margin-bottom: .5rem; }
.doccrumbs .sep { color: var(--muted); }
.doccrumbs a { padding: .1rem .3rem; border-radius: 4px; }

/* one action row, always present whatever is selected, so the pane below it
   starts at the same place on every file */
.docbar { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem;
  margin: .4rem 0 .8rem; }
.docbar .docspacer { flex: 1 1 auto; }
.docbar form { margin: 0; }
.docbar input[type=text] { max-width: 14rem; padding: .3rem .5rem; }
.docbar button, .docbar .btn { margin-top: 0; padding: .3rem .8rem; font-size: .85rem; }
.docbar .btn.secondary, a.btn.secondary { background: var(--panel2); color: var(--text);
  border: 1px solid var(--line); display: inline-block; }
.docbar .linkbtn { margin: 0; }
.docbar .inline-form { display: inline-flex; align-items: center; gap: .4rem; }

/* upload: the picker sits in its own bar so choosing a file never pushes the
   New folder / New file / Delete row around */
.docupload input[type=file] { max-width: 22rem; font-size: .8rem; color: var(--muted);
  background: var(--panel); border: 1px solid var(--line); border-radius: 6px;
  padding: .22rem .35rem; }
.docupload input[type=file]::file-selector-button { margin-right: .5rem;
  background: var(--panel2); color: var(--text); border: 1px solid var(--line);
  border-radius: 4px; padding: .18rem .6rem; font: inherit; cursor: pointer; }
.docreplace { display: inline-flex; align-items: center; gap: .3rem;
  font-size: .82rem; color: var(--muted); white-space: nowrap; }
.docreplace input { margin: 0; }

.doctext { background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--radius); padding: .8rem 1rem; margin: 0;
  font-family: ui-monospace, monospace; font-size: .84rem; line-height: 1.5;
  max-height: 72vh; overflow: auto; white-space: pre; }
.doceditor textarea { width: 100%; height: 62vh; background: var(--bg);
  border: 1px solid var(--line); border-radius: var(--radius); padding: .7rem .9rem;
  font-family: ui-monospace, monospace; font-size: .84rem; line-height: 1.5;
  white-space: pre; overflow: auto; }
.docimg { background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--radius); padding: .8rem; text-align: center; }
.docimg img { max-width: 100%; height: auto; border-radius: 6px; }
.docpdf { width: 100%; height: 76vh; border: 1px solid var(--line);
  border-radius: var(--radius); background: var(--panel); }

/* rendered markdown: reading width, not full bleed — a 2400px line of prose is
   unreadable, but the tables inside it still get the page's full width */
.docmd { background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 1rem 1.4rem 1.4rem; max-height: 78vh;
  overflow: auto; }
.docmd > * { max-width: 58rem; }
.docmd > .tablewrap { max-width: none; }
.docmd h2 { font-size: 1.25rem; margin: 1.4rem 0 .5rem; color: var(--accent); }
.docmd h3 { font-size: 1.05rem; margin: 1.2rem 0 .4rem; }
.docmd h4, .docmd h5, .docmd h6 { font-size: .95rem; margin: 1rem 0 .3rem;
  color: var(--muted); }
.docmd > h2:first-child, .docmd > h3:first-child { margin-top: 0; }
.docmd p { margin: .6rem 0; }
.docmd ul, .docmd ol { margin: .5rem 0; padding-left: 1.4rem; }
.docmd li { margin: .2rem 0; }
.docmd code { background: var(--bg); border: 1px solid var(--line); border-radius: 4px;
  padding: .05rem .3rem; font-family: ui-monospace, monospace; font-size: .85em; }
.docmd pre { background: var(--bg); border: 1px solid var(--line); border-radius: 6px;
  padding: .7rem .9rem; overflow-x: auto; }
.docmd pre code { background: none; border: none; padding: 0; font-size: .84rem; }
.docmd blockquote { margin: .7rem 0; padding: .1rem 0 .1rem .9rem;
  border-left: 3px solid var(--line); color: var(--muted); }
.docmd hr { border: none; border-top: 1px solid var(--line); margin: 1.2rem 0; }
.docmd table.data td, .docmd table.data th { white-space: normal; }

/* --- dataview engine (assets/dvgrid.js) ---
   The control bar holds still: every control is an overlay popover anchored to
   its button, so opening one never reflows the table underneath it. The
   summary strip reserves its height for the same reason — a filter that
   changes the numbers must not also move the rows under the pointer. --- */
.dvbar { display: flex; flex-wrap: wrap; gap: .45rem; align-items: center;
  margin: .8rem 0 .5rem; }
.dvsearch { flex: 1 1 16rem; max-width: 26rem; border-radius: 999px;
  padding-left: .9rem; }
/* Pill buttons that wear their own state: a lit sky border (and a count in the
   label, set by dvgrid.js syncBar) whenever that control is shaping the view.
   Flat fills only — the palette bans button gradients. */
.dvbtn { margin-top: 0; padding: .32rem .85rem; white-space: nowrap;
  border-radius: 999px; background: var(--panel); border: 1px solid var(--line);
  transition: border-color .15s ease, background-color .15s ease, color .15s ease; }
.dvbtn:hover { border-color: var(--accent2); background: var(--panel2); }
.dvbtn.on { border-color: var(--accent2); color: var(--accent2);
  background: rgba(41, 142, 219, .12); font-weight: 600; }
.dvreset.on { border-color: var(--accent2); color: var(--accent2); }
.dvpopwrap { position: relative; display: inline-block; }
.dvpop { position: absolute; left: 0; top: calc(100% + .4rem); z-index: 60;
  min-width: 20rem; max-width: min(32rem, 92vw); max-height: 60vh; overflow-y: auto;
  background: var(--panel2); border: 1px solid var(--line); border-radius: var(--radius);
  padding: .8rem .9rem; box-shadow: 0 10px 28px rgba(0, 0, 0, .45); }
.dvpop > strong { display: block; margin-bottom: .5rem; }
.dvpop p { margin: .2rem 0 .6rem; }
.dvpop input[type=text], .dvpop input[type=search], .dvpop input[type=number],
.dvpop input[type=date], .dvpop select { width: 100%; }
.dvpop button { margin-top: .5rem; }
/* right-anchor the popovers that would otherwise run off the viewport */
.dvbar > .dvpopwrap:nth-last-child(-n+3) .dvpop { left: auto; right: 0; }

.dvcollist, .dvfilterlist, .dvagglist, .dvderivedlist { display: flex;
  flex-direction: column; gap: .25rem; }
.dvcolrow, .dvaggrow, .dvfacet { display: flex; align-items: center; gap: .5rem;
  padding: .12rem 0; cursor: pointer; }
.dvcolrow > span:first-of-type, .dvaggrow > span:first-of-type,
.dvfacet > span:first-of-type { flex: 1 1 auto; }
.dvaggrow { flex-wrap: wrap; }
.dvaggrow select { width: auto; flex: 0 1 8rem; min-width: 6rem; }
.dvaggrow select[hidden] { display: none; }
.dvnotice { margin: .2rem 0 .7rem; }
.dvfilter { padding: .4rem 0; border-top: 1px solid var(--line); }
.dvfilter:first-child { border-top: none; }
.dvfilterhead { font-weight: 600; margin-bottom: .25rem; }
.dvrange { display: flex; align-items: center; gap: .4rem; flex-wrap: wrap; }
.dvrange input { flex: 1 1 6rem; min-width: 5rem; }
.dvfacets { max-height: 12rem; overflow-y: auto; }
.dvgrouprow { display: flex; align-items: center; gap: .5rem; margin: .3rem 0; }
.dvgrouprow select { flex: 1 1 auto; }
.dvgrouprow > span { flex: 0 0 4.5rem; }
.dvderivedrow { display: flex; align-items: center; gap: .5rem;
  justify-content: space-between; }
.dverr:empty { display: none; }

.dvchips { display: flex; flex-wrap: wrap; gap: .3rem; min-height: 1.5rem;
  margin-bottom: .4rem; }
.dvchip { display: inline-flex; align-items: center; gap: .35rem; }
.dvchipx { padding: 0; font-size: 1rem; line-height: 1; color: inherit; }

/* Reserved height: the strip appears and disappears with the column set, and
   the table below must not jump when it does. */
.dvsummary { display: flex; flex-wrap: wrap; gap: .5rem; min-height: 4.2rem;
  margin-bottom: .5rem; }
.dvstat { flex: 0 1 11rem; background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--radius); padding: .45rem .65rem; display: flex;
  flex-direction: column; gap: .1rem; }
.dvstatlabel { font-size: .76rem; color: var(--muted); text-transform: uppercase;
  letter-spacing: .04em; }
.dvstatvalue { font-size: 1.15rem; font-weight: 600; font-variant-numeric: tabular-nums; }
.dvstatmeta { font-size: .72rem; color: var(--muted); }

.dvtablewrap { max-height: min(72vh, 54rem); overflow: auto; }
.dvtable thead th { position: sticky; top: 0; z-index: 2; background: var(--panel); }
.dvtable th.sortable { cursor: pointer; }
.dvtable tr.dvgroup > td { background: var(--panel2); font-weight: 600; }
.dvtable tr.dvgroup.dvlvl1 > td { font-weight: 500; }
.dvtable td.dvgroupkey { white-space: nowrap; }
.dvtable tr.dvchild > td { color: var(--muted); }
.dvtable tr.dvchild td.num, .dvtable tr.dvchild td:not(.dvgroupkey) { color: inherit; }
.dvtable tr.dvchild.dvlvl1 td.dvgroupkey, .dvtable tr.dvchild.dvlvl2 td.dvgroupkey {
  border-left: 2px solid var(--line); }
.dvtable tfoot tr.dvtotal > td { border-top: 2px solid var(--line); font-weight: 600;
  background: var(--panel); position: sticky; bottom: 0; }
.dvtable th.dvgroupn, .dvtable td.dvgroupn { text-align: right; white-space: nowrap; }
.dvchartbox:empty { display: none; }
.dvshape { margin: .2rem 0 .8rem; }

@media (max-width: 720px) {
  .dvpop { position: fixed; left: .6rem; right: .6rem; top: auto;
    max-width: none; max-height: 70vh; }
  .dvbar > .dvpopwrap:nth-last-child(-n+3) .dvpop { left: .6rem; right: .6rem; }
  .dvsummary { min-height: 0; }
  .dvtablewrap { max-height: none; }
}

/* --- dataview pivot heat grid ---
   TWO ramps, because a pivot cell answers two different questions and the
   existing hm-1..5 ramp answers neither: that one is a SEVERITY ramp (louder =
   worse) and a weight of 40% is not "worse" than 4%.

   Sequential (magnitude, all one sign) is the documented single blue hue,
   OKLCH H 253-256, L 0.480 -> 0.905, every gap >= 0.06. Diverging (polarity)
   is blue for positive against the H18 red family already validated for this
   surface, with L matched arm-for-arm (0.461/0.620/0.699 red vs
   0.480/0.622/0.717 blue) so neither side shouts louder than the other, and a
   near-neutral --line midpoint that reads as "nothing". Louder = further from
   zero, which on a dark surface means brighter.

   Ink flips to dark where the fill gets light enough that #dbe4ee would drop
   under 4.5:1; every step clears it on one side or the other, and the two
   blue steps that clear neither (L 0.527/0.575) are deliberately not in the
   ramp. Validated with the dataviz skill's validator, ordinal mode, against
   this #1a212b surface:
     node scripts/validate_palette.js "#1c5cab,#3987e5,#6da7ec,#9ec5f4,#cde2fb" \
       --mode dark --surface "#1a212b" --ordinal
   Colour is never the only channel — every cell prints its own value. --- */
table.dvheat { border-collapse: separate; border-spacing: 2px; }
table.dvheat th, table.dvheat td { border-bottom: none; }
table.dvheat thead th { background: var(--panel); vertical-align: bottom;
  position: sticky; top: 0; z-index: 2; }
table.dvheat .rowhead { position: sticky; left: 0; z-index: 1;
  background: var(--panel); text-align: left; white-space: nowrap; }
table.dvheat tbody tr:hover .rowhead { background: var(--panel2); }
table.dvheat td.hc { border-radius: 4px; text-align: right;
  font-variant-numeric: tabular-nums; white-space: nowrap; }
table.dvheat td.hc-none { color: var(--muted); }
table.dvheat tfoot td, table.dvheat tfoot th { border-top: 2px solid var(--line);
  font-weight: 600; }

/* sequential: magnitude, one hue, low -> high */
.hs-1 { background: #1c5cab; color: #dbe4ee; }
.hs-2 { background: #3987e5; color: #101820; }
.hs-3 { background: #6da7ec; color: #101820; }
.hs-4 { background: #9ec5f4; color: #101820; }
.hs-5 { background: #cde2fb; color: #101820; }
/* diverging: negative arm, near-zero -> far */
.hd-n1 { background: #9e2034; color: #dbe4ee; }
.hd-n2 { background: #d55660; color: #101820; }
.hd-n3 { background: #f16f78; color: #101820; }
/* diverging: the midpoint reads as nothing, not as a value */
.hd-0  { background: #2e3947; color: #dbe4ee; }
/* diverging: positive arm, near-zero -> far */
.hd-p1 { background: #1c5cab; color: #dbe4ee; }
.hd-p2 { background: #3987e5; color: #101820; }
.hd-p3 { background: #6da7ec; color: #101820; }

.dvlegend { display: flex; align-items: center; flex-wrap: wrap; gap: .3rem;
  margin: .5rem 0 .2rem; font-size: .78rem; color: var(--muted); }
.dvlegend .key { display: inline-block; padding: .1rem .5rem; border-radius: 6px;
  border: 1px solid var(--line); }
/* the pivot brings its own scroller so the legend stays put above it */
.dvtablewrap.dvpivoted { border: none; overflow: visible; max-height: none; }
.dvheatscroll { overflow: auto; max-height: min(72vh, 54rem);
  border: 1px solid var(--line); border-radius: var(--radius); padding: .2rem; }
.dvcolrow .dvmove, .dvcolrow .dvpinbtn { padding: 0 .25rem; font-size: .8rem; }
.dvcolrow .dvmove[disabled] { opacity: .3; cursor: default; }
.dvpinbtn.on { color: var(--accent2); }
.dvcolsep { margin: .4rem 0 .1rem; font-size: .76rem; color: var(--muted);
  text-transform: uppercase; letter-spacing: .04em; border-top: 1px solid var(--line);
  padding-top: .35rem; }
/* the pinned column survives horizontal scrolling; z above cells, below thead */
.dvtable td.dvpin { position: sticky; left: 0; z-index: 1; background: var(--panel); }
.dvtable th.dvpin { position: sticky; left: 0; z-index: 3; background: var(--panel); }
.dvtable tr:hover td.dvpin { background: var(--panel2); }
