/* ============================================================================
   Component layer - charts, KPI tiles, kanban, timeline, dossier, command
   palette, provenance drawer. Builds on theme.css tokens.
   ========================================================================== */

/* ── Charts ───────────────────────────────────────────────────────────────── */
.chart-svg { display: block; overflow: visible; }

/* --- The mark is a group, not a path ----------------------------------------
   A bar is drawn as <g class="chart-mark"> holding the filled path and a sheen
   path over it. Grouping is what lets the two animate as one object - a
   highlight that grows a frame out of step with the shape it lights visibly
   detaches - and it means depth, hover and the mount stagger are declared once
   per mark instead of once per path. Donut and funnel segments are single
   paths, so .chart-seg carries the same states directly. */
.chart-svg .chart-mark,
.chart-svg .chart-seg {
  --mark-c: var(--teal);
  filter: drop-shadow(0 3px 7px color-mix(in srgb, var(--mark-c) 20%, transparent));
  transition: filter .14s ease, opacity .14s ease, transform .14s cubic-bezier(.22,1,.36,1);
}
/* Depth is tinted with the mark's OWN color. This used to be one blanket
   drop-shadow(... var(--teal) ...) applied to every bar, which gave an
   overdue-red bar a cyan glow; worse, it matched .chart-bar:hover on
   specificity and sat later in the file, so it silently overwrote the hover
   brightness and no bar in the product responded to being pointed at. Resting
   and hover are one property on one selector now, so they compose. */
.chart-svg .chart-mark:hover,
.chart-svg .chart-mark.is-hot,
.chart-svg .chart-seg:hover,
.chart-svg .chart-seg.is-hot {
  filter: drop-shadow(0 6px 14px color-mix(in srgb, var(--mark-c) 42%, transparent))
          brightness(1.07) saturate(1.06);
}

/* --- Hovering one mark quiets the rest ---------------------------------------
   is-focus lands on the SVG while any mark is hovered or focused. Dimming the
   siblings rather than only brightening the target is what makes a dense bar row
   readable - the comparison you are making is the one under the cursor. Held at
   .42 rather than lower so the quiet marks keep their shape; this is emphasis,
   not a spotlight that hides the rest of the data. */
.chart-svg.is-focus .chart-mark:not(.is-hot),
.chart-svg.is-focus .chart-seg:not(.is-hot) { opacity: .42; filter: none; }

/* Donut segments step outward from the center on hover. The offset is set inline
   per segment (--pop-x/--pop-y hold its own radial direction) so each slice moves
   away from the ring's middle rather than all of them sliding one way. */
.chart-svg .chart-seg { transform-origin: center; cursor: default; }
.chart-svg .chart-seg.is-pop.is-hot { transform: translate(var(--pop-x, 0px), var(--pop-y, 0px)); }

/* Keyboard reach. A drillable mark is a real button: focusable, Enter/Space
   activated and ringed when focused - drill-through used to be mouse-only on
   every chart in the product, so a keyboard user could reach a chart and then
   do nothing with it. */
.chart-svg .is-drillable { cursor: pointer; }
.chart-svg .is-drillable:focus { outline: none; }
.chart-svg .is-drillable:focus-visible { outline: 2px solid var(--teal-bright); outline-offset: 2px; border-radius: 3px; }

/* Texture: alternating bands between value ticks. Barely-there structure so the
   plot reads as a measured field rather than an empty void, and the eye can
   carry a bar's height across to the axis without tracking a hairline. */
.chart-svg .plot-band { fill: var(--tx); opacity: .028; pointer-events: none; }

/* Texture: a specular sheen painted over each mark - the same path again, filled
   with a white-to-transparent vertical wash. A separate path rather than extra
   gradient stops because it has to stay hue-independent: every series color,
   including one a caller passes in, catches the light identically. */
.chart-svg .bar-sheen { pointer-events: none; }

/* `.chart-bar` intentionally has NO rule in this file any more - the group
   around it carries the fill's depth, hover, emphasis and mount animation. The
   class is still emitted by mountBars and is not dead: `path.chart-bar` is the
   selector the headless scenarios click through (scripts/scenarios/**) and the
   one dashboard-kit's recolorBars() repaints. Do not drop it from the renderer
   on the grounds that nothing styles it. */

/* Bars grow up from the baseline on mount, staggered by --bi (set per mark). */
@keyframes bar-grow { from { transform: scaleY(0); } to { transform: scaleY(1); } }
.chart-svg .chart-mark { animation: bar-grow .55s cubic-bezier(.22,1,.36,1) both; animation-delay: calc(var(--bi, 0) * 38ms); }
/* Horizontal bars grow out of the value axis instead, so the same stagger reads
   as the bar extending rightwards rather than a sideways bar inflating. */
@keyframes bar-grow-h { from { transform: scaleX(0); } to { transform: scaleX(1); } }
.chart-svg .chart-mark.is-h { animation-name: bar-grow-h; }
/* Donut segments sweep out from the ring's center. */
@keyframes seg-in { from { transform: scale(.82); opacity: 0; } to { transform: scale(1); opacity: 1; } }
.chart-svg .chart-seg.seg-in { animation: seg-in .5s cubic-bezier(.22,1,.36,1) both; animation-delay: calc(var(--bi, 0) * 45ms); }
/* Line/area traces in on mount. */
@keyframes chart-draw { to { stroke-dashoffset: 0; } }
.chart-line-draw { animation: chart-draw 1s cubic-bezier(.22,1,.36,1) forwards; }
/* The area under the primary line fades up while the line draws over it. */
@keyframes area-in { from { opacity: 0; } to { opacity: 1; } }
.chart-svg .chart-area { animation: area-in .9s ease both; }
/* An isolated series (a legend key clicked) leaves the others faintly in place
   rather than removing them - the shape you are reading keeps its context. */
.chart-svg .series-off { opacity: .12; transition: opacity .16s ease; }
.chart-svg .chart-area.series-off { opacity: 0; }
@media (prefers-reduced-motion: reduce) {
  .chart-svg .chart-mark,
  .chart-svg .chart-seg.seg-in,
  .chart-svg .chart-area { animation: none; }
  .chart-line-draw { animation: none; stroke-dashoffset: 0 !important; }
  .chart-svg .chart-mark,
  .chart-svg .chart-seg { transition: none; }
  .chart-svg .chart-seg.is-pop.is-hot { transform: none; }
}
.chart-grid { stroke: var(--line); stroke-width: 1; stroke-dasharray: 2 5; }
.chart-cross { stroke: var(--teal-deep); stroke-width: 1; stroke-dasharray: 3 3; }
.chart-axis { display: flex; justify-content: space-between; margin-top: 6px; }
.chart-axis span { font-family: var(--f-mono); font-size:12px; color: var(--tx-mute); white-space: nowrap; overflow: hidden; }
/* Printed values and the target caption are annotation, not targets. They are
   drawn over the plot - and in a horizontal chart the value sits inside its own
   row's hit band - so taking pointer events they would steal the hover from the
   mark they describe and the read-out would blink out exactly when the cursor
   reached the number. `.cat-name` is deliberately NOT in this list: a truncated
   category name carries its full text in a <title> that needs the hover. */
.chart-svg .bar-val,
.chart-svg .bar-target { pointer-events: none; }
.chart-svg .bar-val { font-family: var(--f-mono); font-size: 11px; fill: var(--tx-soft); font-weight: 700; }
.chart-svg .bar-target { font-family: var(--f-mono); font-size: 11px; fill: var(--teal-deep); font-weight: 700; }
/* ── Printed numbers get a halo ──────────────────────────────────────────────
   A bar's value label and the target caption are drawn in the plot area, so
   anything the chart draws at the same height runs straight through them - most
   visibly the dashed average/target line, which by definition sits at the height
   of the typical bar and therefore crosses exactly the labels clustered around
   the average. Gridlines do it too, just more faintly.

   `paint-order: stroke` paints the stroke BEFORE the fill, so a stroke in the
   surface color becomes a knock-out behind each glyph rather than an outline on
   top of it. This fixes every crossing at once - no z-order juggling, no
   per-label collision maths, and it keeps working when the data changes.

   var(--card) because charts live on cards; a chart moved onto the page canvas
   should carry --paper here instead. `.on-mark` opts out: a label sitting on top
   of its own bar would get a pale ring drawn around it. */
.chart-svg .bar-val:not(.on-mark),
.chart-svg .bar-target {
  paint-order: stroke;
  stroke: var(--card);
  stroke-width: 3px;
  stroke-linejoin: round;
}
/* Axis furniture - tick values on both axes + optional axis titles. Kept a
   notch quieter than the printed data values so the numbers on the marks stay
   the thing the eye lands on first. */
.chart-svg .ax-tick { font-family: var(--f-mono); font-size: 11px; fill: var(--tx-mute); font-weight: 600; font-variant-numeric: tabular-nums; }
.chart-svg .ax-title { font-family: var(--f-body, inherit); font-size: 10.5px; font-weight: 700; fill: var(--tx-mute); letter-spacing: .07em; text-transform: uppercase; }
/* Category names in a horizontal bar chart's left gutter. These are words, not
   readings, so they get the body face (not the tabular mono the tick values
   use) and a touch more contrast - they identify the row, not annotate it. */
.chart-svg .cat-name { font-family: var(--f-body, inherit); font-size: 12px; font-weight: 600; fill: var(--tx-soft); }
/* Share printed inside a donut slice - white on the segment fill. */
.chart-svg .donut-pct { font-family: var(--f-mono); font-size: 11px; font-weight: 700; fill: #fff; paint-order: stroke; stroke: rgba(0,0,0,.18); stroke-width: 2px; pointer-events: none; }
/* Line-chart legend: series name + its latest value, printed above the plot. */
.chart-keys { display: flex; flex-wrap: wrap; gap: 6px 16px; margin: 0 0 8px 2px; }
.chart-keys .ck { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; color: var(--tx-soft); font-weight: 600; }
.chart-keys .ck i { width: 10px; height: 3px; border-radius: 2px; flex-shrink: 0; }
.chart-keys .ck i.dash { background: repeating-linear-gradient(90deg, var(--tx-mute) 0 3px, transparent 3px 5px); }
.chart-keys .ck b { font-family: var(--f-mono); font-weight: 700; color: var(--head, var(--ink)); font-variant-numeric: tabular-nums; }
/* A legend key is a control: it isolates its series. Reset to look exactly like
   the caption it replaced, so a chart with one series reads no differently. */
button.ck { background: none; border: 0; padding: 2px 4px; margin: -2px -4px; border-radius: 6px; font: inherit; cursor: pointer; }
button.ck:hover { background: var(--line-soft); }
button.ck:focus-visible { outline: 2px solid var(--teal-bright); outline-offset: 1px; }
.chart-keys .ck.is-off { opacity: .4; }
/* The donut's legend rows and its arcs light each other up - see mountDonut. */
.chart-legend .lg-row { border-radius: 6px; padding: 1px 5px; margin: -1px -5px; transition: background .14s ease; }
.chart-legend .lg-row.is-hot { background: var(--line-soft); }
.chart-legend .lg-row.is-drillable { cursor: pointer; }
.chart-legend .lg-row.is-drillable:focus-visible { outline: 2px solid var(--teal-bright); outline-offset: 1px; }
/* Donut + printed legend side by side (mountDonut legend:true). */
.donut-row { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
.donut-row .chart-legend { flex: 1; min-width: 150px; }
/* Tapering-funnel in-band text (crisp; rendered at true pixel size) */
/* In-band text is white on whatever color the CALLER passed, and a caller can
   pass a pale one. The same dark paint-order stroke the donut percentages use
   keeps it legible on a light band instead of leaving it to the palette to be
   kind.
   The stroke was .22/.20, which was not enough to carry it. The default ramp's
   first stage is --teal-bright (#00E5FF): white on that is about 1.6:1, so the
   top band of every funnel in the product - the one holding the largest number -
   was the hardest one to read. Darkened to .42/.38, which is the difference
   between a hint of a halo and one that actually seats the glyphs on the band.
   Left as a halo rather than switching the ink to dark, because a caller may
   equally pass a deep color, and the halo is the one treatment that works for
   both without the renderer having to reason about luminance. */
.chart-svg .funnel-lbl { font-family: var(--f-head); font-size: 13px; font-weight: 700; fill: #fff; letter-spacing: .01em;
  paint-order: stroke; stroke: rgba(0,0,0,.42); stroke-width: 3px; }
.chart-svg .funnel-val { font-family: var(--f-mono); font-size: 11.5px; font-weight: 700; fill: rgba(255,255,255,.96);
  paint-order: stroke; stroke: rgba(0,0,0,.38); stroke-width: 2.5px; }
.chart-svg .funnel-drop { font-family: var(--f-mono); font-size: 10.5px; font-weight: 700; fill: var(--tx-mute); letter-spacing: .03em; }
/* Out-of-band variants, used when a stage is too narrow to hold its own label.
   These sit on the CARD, not on the band, so they take the page's text colors -
   the white in-band fills would be invisible here. */
.chart-svg .funnel-lbl-out { font-family: var(--f-head); font-size: 12.5px; font-weight: 700; fill: var(--tx); letter-spacing: .01em; }
.chart-svg .funnel-val-out { font-family: var(--f-mono); font-size: 11.5px; font-weight: 700; fill: var(--tx-mute); }
.chart-tip {
  position: fixed; z-index: 200; pointer-events: none; opacity: 0; transform: translateY(-2px);
  background: var(--ink); color: var(--on-ink); padding: 8px 11px; border-radius: 9px; font-size: 12px;
  box-shadow: var(--sh-3); transition: opacity .12s; max-width: 220px; line-height: 1.5;
}
.chart-tip b { display: block; font-family: var(--f-head); font-size: 12.5px; margin-bottom: 2px; }
.chart-tip span { display: flex; align-items: center; gap: 6px; color: rgba(232,238,242,.82); }
.chart-tip span i { width: 8px; height: 8px; border-radius: 2px; display: inline-block; }
.chart-tip small { color: rgba(232,238,242,.55); }
/* Inline chart legend - printed numbers beside donuts (always-visible values). */
.chart-legend { display: flex; flex-direction: column; gap: 7px; }
.chart-legend .lg-row { display: flex; align-items: center; gap: 8px; font-size: 12.5px; line-height: 1.3; }
.chart-legend .lg-row i { width: 9px; height: 9px; border-radius: 3px; flex-shrink: 0; }
.chart-legend .lg-row .lg-l { color: var(--tx-soft); font-weight: 600; }
.chart-legend .lg-row .lg-v { margin-left: auto; font-family: var(--f-mono); font-weight: 700; color: var(--head, var(--ink)); white-space: nowrap; }
.chart-legend .lg-row .lg-n { color: var(--tx-mute); font-family: var(--f-mono); font-size:12px; }
/* card-head pace badge - the always-visible "X of Y · ±Z% vs pace" number. */
.head-badge { font-family: var(--f-mono); font-size: 12px; font-weight: 700; padding: 3px 10px; border-radius: var(--r-pill); white-space: nowrap; }
.head-badge.ok { background: var(--ok-soft); color:var(--ok-tx, var(--ok)); }
.head-badge.bad { background: var(--bad-soft); color:var(--bad-tx, var(--bad)); }
.head-badge.warn { background: var(--warn-soft); color:var(--warn-tx, var(--warn)); }
.head-delta { font-family: var(--f-mono); font-size: 12px; font-weight: 700; }
.head-delta.up { color:var(--ok-tx, var(--ok)); } .head-delta.down { color:var(--bad-tx, var(--bad)); }
.tag-modeled { font-family: var(--f-mono); font-size:12px; font-weight: 600; letter-spacing: .04em; text-transform: uppercase; color: var(--tx-mute); background: var(--line-soft); padding: 2px 7px; border-radius: var(--r-pill); }
.donut-center { position: absolute; inset: 0; display: grid; place-content: center; text-align: center; }
.donut-center b { font-family: var(--f-num); font-weight: 800; font-size: 24px; color: var(--head, var(--ink)); }
.donut-center small { display: block; font-size:12px; color: var(--tx-mute); text-transform: uppercase; letter-spacing: .05em; }

/* Funnel */
.funnel { display: flex; flex-direction: column; gap: 10px; }
.funnel-row { display: grid; grid-template-columns: 1fr 46px; align-items: center; gap: 10px; }
.funnel-meta { grid-column: 1; display: flex; justify-content: space-between; font-size: 12.5px; margin-bottom: 4px; }
.funnel-meta .fl { color: var(--tx-soft); font-weight: 600; } .funnel-meta .fv { font-family: var(--f-mono); font-weight: 700; }
.funnel-track { grid-column: 1; height: 12px; background: var(--line-soft); border-radius: 6px; overflow: hidden; }
.funnel-fill { height: 100%; border-radius: 6px; transition: width .7s cubic-bezier(.22,1,.36,1); }
.funnel-conv { grid-column: 2; grid-row: 1 / span 2; font-family: var(--f-mono); font-size: 12px; font-weight: 700; color: var(--teal-deep); text-align: right; }

/* ── KPI tiles (clickable → provenance) ───────────────────────────────────── */
.kpi-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(212px, 1fr)); gap: 14px; align-items: stretch; }
.kpi {
  position: relative; background: var(--card); border: 1px solid var(--line); border-radius: var(--r-lg);
  padding: 16px 18px 12px; cursor: pointer; overflow: hidden; text-align: left; width: 100%;
  display: flex; flex-direction: column;
  transition: border-color .15s, transform .12s, box-shadow .15s;
}
.kpi:hover { border-color: color-mix(in srgb, var(--teal) 40%, var(--line)); transform: translateY(-2px); box-shadow: var(--sh-2); }
.kpi .k { font-size:12px; font-weight: 600; text-transform: uppercase; letter-spacing: .05em; color: var(--tx-mute); display: flex; align-items: center; gap: 6px; }
/* A tile WITH a mark carries a 22px badge in its label row; one without is a
   14px line of text. Mixed in the same strip - which is the normal case, since
   only the headline metric takes a mark - every marked tile pushed its own
   basis line 8px below its neighbors', so the second row of the strip stepped
   down and back up. The values still lined up (`.v` is bottom-anchored by the
   `margin-top:auto` below), which is what made it read as a defect rather than
   a layout: same baseline underneath, ragged one above.
   Reserve the badge's height for the whole strip. Scoped with :has so a strip
   that has no marks at all is byte-identical to before. */
.kpi-grid:has(.kmark) .kpi .k { min-height: 22px; }
.kpi .kbasis { font-family: var(--f-mono); font-size:12px; letter-spacing: .02em; color: var(--tx-mute); margin-top: 3px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.kpi .v { font-family: var(--f-num); font-weight: 800; font-size: 28px; color: var(--head, var(--ink)); margin-top: auto; padding-top: 8px; white-space: nowrap; font-variant-numeric: tabular-nums; line-height: 1.05; }
.kpi .d { font-size: 12px; margin-top: 3px; min-height: 17px; color: var(--tx-soft); }
.kpi .d .up { color:var(--ok-tx, var(--ok)); font-weight: 700; } .kpi .d .down { color:var(--bad-tx, var(--bad)); font-weight: 700; }
.kpi .spark { position: absolute; right: 14px; bottom: 12px; width: 96px; height: 30px; opacity: .9; }
.kpi .drill { position: absolute; top: 14px; right: 14px; color: var(--tx-mute); opacity: 0; transition: opacity .15s; }
.kpi:hover .drill { opacity: 1; }
/* `.kpi.feature` is RETIRED - see kpiTile() in core/components.js. It painted
   one tile a solid block of `--ink`, which on a consulting workspace came out
   dark green while nothing else in the product did, and made the other tiles in
   the row read as its footnotes. Every call site uses `.is-accent` now. The
   declaration is deleted rather than left unused so a stale bundle cannot
   resurrect the block, and this note is here so nobody re-adds it. */

/* ── Segmented control ────────────────────────────────────────────────────── */
/* Panning, not wrapping: the track is a single pill with a sliding selection, so
   a wrapped second row would break the control's own metaphor. Four filters run
   ~415px - wider than a phone's content column - and with no max-width the
   overflow pushed the DOCUMENT wide, which is what made whole pages render
   zoomed out. Same treatment .dossier-tabs already uses. */
.segctl { display: inline-flex; max-width: 100%; overflow-x: auto; scrollbar-width: none; background: var(--line-soft); border-radius: var(--r-sm); padding: 3px; gap: 2px; }
.segctl::-webkit-scrollbar { display: none; }
.segctl button { flex: 0 0 auto; }
.segctl button { font-family: var(--f-body); font-size: 13px; font-weight: 600; color: var(--tx-soft); background: transparent; border: 0; padding: 7px 15px; border-radius: 8px; cursor: pointer; transition: all .14s; white-space: nowrap; }
.segctl button.on { background: var(--card); color: var(--head, var(--ink)); box-shadow: var(--sh-1); }
.segctl.seg-ic button { display: inline-flex; align-items: center; gap: 7px; }
.segctl.seg-ic button svg { width: 15px; height: 15px; flex-shrink: 0; }

/* ── Kanban ───────────────────────────────────────────────────────────────── */
.kanban { display: flex; gap: 14px; overflow-x: auto; padding-bottom: 8px; align-items: flex-start; }
/* The chat dock's tab unit hangs on the right edge of the window, over the
   page. Scrolled all the way right, the last lane (Lost) sat under it. A
   trailing spacer lets the board scroll that lane clear of the dock; a flex
   spacer, because a scroll container's end padding is dropped by some engines. */
.kanban::after { content: ''; flex: 0 0 34px; align-self: stretch; }
.kan-col { flex: 0 0 280px; background: var(--card-2); border: 1px solid var(--line); border-radius: var(--r-lg); display: flex; flex-direction: column; max-height: 72vh; }
.kan-col.drop { border-color: var(--teal); box-shadow: 0 0 0 3px color-mix(in srgb, var(--teal) 16%, transparent); }
.kan-head { display: flex; align-items: center; gap: 8px; padding: 12px 14px; border-bottom: 1px solid var(--line-soft); }
.kan-head .nm { font-family: var(--f-head); font-weight: 700; font-size: 13.5px; }
.kan-head .ct { margin-left: auto; font-family: var(--f-mono); font-size:12px; color: var(--tx-mute); background: var(--line-soft); padding: 2px 8px; border-radius: 999px; }
.kan-head .sum { font-family: var(--f-mono); font-size: 12px; color: var(--teal-deep); font-weight: 700; }
.kan-body { flex: 1; overflow-y: auto; padding: 10px; display: flex; flex-direction: column; gap: 9px; }
.kan-card { background: var(--card); border: 1px solid var(--line); border-radius: var(--r); padding: 12px; cursor: grab; transition: box-shadow .14s, transform .1s, border-color .14s; }
.kan-card:hover { box-shadow: var(--sh-2); border-color: color-mix(in srgb, var(--teal) 30%, var(--line)); }
.kan-card.drag { opacity: .5; }
.kan-card .amt { font-family: var(--f-num); font-weight: 800; font-size: 17px; color: var(--head, var(--ink)); }
.kan-card .who { display: flex; align-items: center; gap: 8px; margin-top: 8px; font-size: 13px; color: var(--tx-soft); }
.kan-card .foot { display: flex; align-items: center; justify-content: space-between; margin-top: 10px; }

/* ── Activity timeline ────────────────────────────────────────────────────── */
.timeline { position: relative; padding-left: 26px; }
.timeline::before { content: ''; position: absolute; left: 9px; top: 4px; bottom: 4px; width: 2px; background: var(--line); }
.tl-item { position: relative; padding: 0 0 18px; }
.tl-item::before { content: ''; position: absolute; left: -22px; top: 3px; width: 12px; height: 12px; border-radius: 50%; background: var(--card); border: 2.5px solid var(--teal); }
.tl-item.note::before { border-color: var(--tx-mute); } .tl-item.call::before { border-color: var(--info); }
.tl-item.email::before { border-color: var(--gold); } .tl-item.status::before { border-color: var(--teal-bright); }
.tl-item .tl-top { display: flex; justify-content: space-between; gap: 10px; }
.tl-item .tl-kind { font-size:12px; font-weight: 700; text-transform: uppercase; letter-spacing: .05em; color: var(--tx-mute); }
.tl-item .tl-when { font-size:12px; color: var(--tx-mute); white-space: nowrap; }
.tl-item .tl-body { font-size: 13.5px; color: var(--tx); margin-top: 3px; }

/* ── Dossier (record drawer) ──────────────────────────────────────────────── */
.dossier-tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(96px, 1fr)); gap: 8px; margin: 4px 0 16px; }
/* THE FIGURES LINE UP ACROSS THE ROW.
   Four tiles in a 520px drawer means captions wrap - "Lifetime Value" and
   "Active Projects" take two lines, "Open Deals" takes one - and with the tile
   in normal flow each figure sat directly under its own caption, so the row of
   numbers came out stepped. A KPI strip is read ACROSS, so the values are
   pinned to the bottom of a common box and the captions stack above them. */
.dossier-tile { background: var(--card-2); border: 1px solid var(--line-soft); border-radius: 11px; padding: 10px 11px;
  display: flex; flex-direction: column; justify-content: space-between; gap: 6px; min-height: 62px; }
.dossier-tile .k { font-size:12px; text-transform: uppercase; letter-spacing: .05em; color: var(--tx-mute); line-height: 1.25; }
.dossier-tile .v { font-family: var(--f-num); font-weight: 700; font-size: 16px; color: var(--head, var(--ink)); white-space: nowrap; }
.dossier-tabs { display: flex; gap: 3px; border-bottom: 1px solid var(--line); margin: 4px 0 14px; overflow-x: auto; }
.dossier-tabs button { font-family: var(--f-body); font-size: 13px; font-weight: 600; color: var(--tx-soft); background: none; border: 0; border-bottom: 2px solid transparent; padding: 9px 12px; cursor: pointer; white-space: nowrap; }
.dossier-tabs button.on { color: var(--teal-deep); border-bottom-color: var(--teal); }
.related-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 10px 0; border-bottom: 1px solid var(--line-soft); font-size: 13.5px; cursor: pointer; }
.related-row:last-child { border-bottom: 0; } .related-row:hover { color: var(--teal-deep); }

/* ── Client dossier (drawer body) ─────────────────────────────────────────────
   These live here, NOT in the Clients page's injected <style>, because the
   drawer is appended to <body> - outside the page node that carried those
   rules. Any moment the drawer outlives or precedes its page (a route change
   under an open drawer, a dossier opened straight from a deep link) stripped
   the whole block: the label/value rows collapsed into "Emailjenna@…" run
   together, the score lost its bar, and the head stacked vertically. A drawer
   that can be opened from anywhere cannot depend on a page for its CSS. */
.doss-head { display: flex; align-items: center; gap: 12px; margin-bottom: 16px; }
.doss-head .avatar.lg { width: 46px; height: 46px; font-size: 16px; border-radius: 12px; }
.doss-id { flex: 1; min-width: 0; }
.doss-id h2 { font-family: var(--f-head); font-weight: 700; font-size: 19px; color: var(--head, var(--ink)); line-height: 1.2; }
.doss-id .muted { font-size: 13px; margin-top: 2px; }

.doss-score { display: flex; align-items: center; gap: 16px; padding: 14px 16px; background: var(--card-2); border: 1px solid var(--line-soft); border-radius: 14px; margin-bottom: 6px; }
.doss-score-big { font-family: var(--f-num); font-weight: 800; font-size: 42px; line-height: 1; white-space: nowrap; }
.doss-score-big small { font-size: 16px; font-weight: 600; color: var(--tx-mute); margin-left: 2px; }
.doss-score-meta { flex: 1; min-width: 0; }
.doss-score-read { font-size: 13px; font-weight: 600; color: var(--tx); margin-top: 9px; }
/* The score bar's own base rules. The Clients table scopes its copy under
   .clients-page, which never matched inside the drawer, so the dossier's bar
   was a bare div with a height and no track. */
/* The track is tinted from --sc, the score's own color, which the dossier
   declares on .doss-score (see openDossier). A flat --line track is the same
   value as every border in the panel, so the gauge read as a hairline with a
   stub of color sitting on it. The inset ring closes the shape at both ends
   so the empty half is still a bar when the score is 8. */
.doss-score .relbar-track { height: 9px; border-radius: 6px; overflow: hidden;
  background: color-mix(in srgb, var(--sc, var(--teal)) 22%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--sc, var(--teal)) 30%, transparent); }
.doss-score .relbar-fill { height: 100%; border-radius: 6px; transition: width .5s cubic-bezier(.22,1,.36,1);
  box-shadow: 0 0 12px -4px var(--sc, var(--teal)); }

.doss-list { display: flex; flex-direction: column; gap: 8px; }
.doss-line { display: flex; align-items: center; gap: 12px; padding: 11px 12px; border: 1px solid var(--line-soft); border-radius: 11px; background: var(--card); }
.doss-line.is-link { cursor: pointer; transition: border-color .12s, background .12s, transform .1s; }
.doss-line.is-link:hover { border-color: color-mix(in srgb, var(--teal) 38%, var(--line)); background: color-mix(in srgb, var(--teal) 4%, var(--card)); transform: translateY(-1px); }
.doss-line-go { display: inline-grid; place-items: center; color: var(--tx-mute); opacity: 0; transition: opacity .12s; }
.doss-line-go svg { width: 14px; height: 14px; }
.doss-line.is-link:hover .doss-line-go { opacity: 1; color: var(--teal-deep); }
.doss-line-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.doss-line-main b { font-size: 13.5px; color: var(--head, var(--ink)); }
.doss-line-main .muted { font-size: 12px; }
.doss-line-right { display: flex; align-items: center; gap: 10px; flex-shrink: 0; }
.doss-line-right .num { font-size: 13px; font-weight: 700; }
/* An action that belongs to ONE row, inside a row that is itself a link. It
   stays quiet until the row is hovered so the list still reads as a list, and
   it keeps the row's own hover treatment off itself - a button that lifts with
   its container looks like part of the link it is deliberately not. */
.doss-line-act { padding: 4px 10px; font-size: 12px; opacity: .72; }
.doss-line.is-link:hover .doss-line-act { opacity: 1; }
.doss-line-act svg { width: 13px; height: 13px; }

/* Work tab: deals and engagements are one list in three named groups, so the
   reader does not have to know which side of Won a piece of work is on. */
.doss-group + .doss-group { margin-top: 26px; }
/* The heading and its count are two elements with a real gap between them, not
   a heading with a bare <span> welded to its last letter - "In Delivery" and
   "1 Engagement" ran together as "In Delivery1 engagement". The count is a
   chip: a number against an uppercase mono heading needs its own shape or it
   reads as part of the words. */
.doss-group-h { display: flex; align-items: center; gap: 10px; font-family: var(--f-mono); font-size: 11px;
  letter-spacing: .08em; text-transform: uppercase; font-weight: 700; color: var(--tx-mute);
  padding-bottom: 8px; margin-bottom: 11px; border-bottom: 1px solid var(--line); }
.doss-group-t { white-space: nowrap; }
.doss-group-n { font-family: var(--f-body); font-size: 11.5px; letter-spacing: 0; text-transform: none;
  font-weight: 600; color: var(--tx-soft); padding: 2px 8px; border-radius: 999px;
  background: var(--card-2); border: 1px solid var(--line-soft); white-space: nowrap; }
.doss-group-note { font-size: 12px; line-height: 1.5; color: var(--tx-soft); margin: -3px 0 10px; }
.doss-group-note b { color: var(--head, var(--ink)); font-weight: 600; }
/* A won deal that has become an engagement keeps its place in the history and
   gives up its figure - the money is on the engagement, and printing it in
   both places is how one $8,000 sale reads as $16,000 of work. */
.doss-line-becomes { font-size: 11px; font-weight: 600; color: var(--tx-mute); padding: 2px 9px;
  border-radius: 999px; border: 1px dashed var(--line); white-space: nowrap; }
.doss-line.is-converted .doss-line-main b { font-weight: 600; }
.doss-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 16px;
  padding-top: 14px; border-top: 1px solid var(--line); }
.doss-actions .grow { flex: 1; }
.doss-note { font-size: 12.5px; line-height: 1.5; color: var(--tx-soft); margin-bottom: 12px;
  padding: 10px 12px; border-radius: 10px; background: color-mix(in srgb, var(--teal) 6%, var(--card));
  border: 1px solid var(--line-soft); }
/* NOT A WARNING STRIPE. This was a gold wash over the card with a gold border,
   which on paper is a highlighter smear and on the graphite panel came out
   brown - Jeremy's word, and the right one. What the note actually says is
   "two things on this record disagree and a person has to pick one", which is
   information, not danger: nothing is broken and nothing is unsafe.

   So it is a plain card with a rule down its leading edge, the color carried
   by that rule alone. The surface stays the card the rest of the panel is
   drawn on, the text stays at reading weight, and the words that matter -
   the status, the action - are bold in the heading ink. */
.doss-warn { font-size: 12.5px; line-height: 1.55; color: var(--tx-soft); margin-bottom: 12px;
  padding: 11px 13px; border-radius: 10px;
  background: var(--card);
  border: 1px solid var(--line-soft);
  border-left: 3px solid var(--info, #2f72c4); }
.doss-warn b { color: var(--head, var(--ink)); font-weight: 700; }

/* Contacts tab: the people at a client, as a card each.
   Rows with the actions pinned right meant a name, a job title, two addresses
   and three role chips competing for one line - so the chips wrapped under a
   240px cap and the tab read as a table that had run out of room. A contact is
   a small record with four facts on it, which is a card. */
.cp-bar { display: flex; align-items: center; gap: 9px; margin-bottom: 9px; }
.cp-search { position: relative; flex: 1; min-width: 0; display: flex; align-items: center; }
.cp-search svg { position: absolute; left: 10px; width: 15px; height: 15px; color: var(--tx-mute); pointer-events: none; }
.cp-search .input { padding-left: 32px; }
/* Safari draws its own clear affordance on type=search and it collides with
   the right edge of the field. */
.cp-search .input::-webkit-search-decoration,
.cp-search .input::-webkit-search-results-button { -webkit-appearance: none; }
.cp-sort { width: auto; flex-shrink: 0; font-size: 12.5px; padding-top: 7px; padding-bottom: 7px; }
.cp-seg { margin-bottom: 12px; flex-wrap: wrap; }
/* The count rides inside the button so a filter that would empty the grid is
   visibly empty before it is clicked - and is disabled when it is zero. */
.cp-seg-n { margin-left: 6px; font-family: var(--f-mono); font-size: 10.5px; opacity: .62; }
.cp-seg button:disabled { opacity: .4; cursor: default; }

/* auto-FIT, not auto-fill: a client with one contact should get one full-width
   tile, not a half-width one beside an empty track. */
.cp-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(248px, 1fr)); gap: 10px; }
.cp-tile { display: flex; flex-direction: column; gap: 9px; padding: 13px 13px 12px;
  border: 1px solid var(--line); border-radius: 13px; background: var(--card);
  transition: border-color .14s, box-shadow .14s; }
.cp-tile:hover { border-color: color-mix(in srgb, var(--teal) 34%, var(--line)); box-shadow: var(--sh-1, 0 1px 3px rgba(0,0,0,.08)); }
/* The company-record entry is not a row in the contacts table, and the tile
   says so on its own face rather than in a paragraph above the list. */
.cp-tile.is-derived { border-style: dashed; background: var(--card-2); }
.cp-tile-h { display: flex; align-items: flex-start; gap: 10px; }
/* There is no .avatar.sm in the base sheet - only .lg, added for the dossier
   head. Sized here rather than adding a global modifier one screen uses. */
.cp-tile .avatar.sm { width: 32px; height: 32px; font-size: 12px; border-radius: 10px; flex-shrink: 0; }
.cp-tile-id { flex: 1; min-width: 0; }
.cp-tile-id b { display: block; font-size: 13.5px; color: var(--head, var(--ink)); line-height: 1.3; word-break: break-word; }
.cp-title { display: block; font-size: 12px; font-weight: 400; color: var(--tx-mute); margin-top: 1px; }
.cp-title.is-none { font-style: italic; opacity: .72; }
/* The actions fade in on hover on a pointer device and are always present for
   keyboard and touch - a control you cannot reach is not a control. */
.cp-tile-acts { display: flex; align-items: center; gap: 2px; flex-shrink: 0; }
@media (hover: hover) and (pointer: fine) {
  .cp-tile-acts { opacity: 0; transition: opacity .13s; }
  .cp-tile:hover .cp-tile-acts,
  .cp-tile:focus-within .cp-tile-acts { opacity: 1; }
}
.cp-tile-acts .btn-sm { padding: 4px 6px; }
.cp-tile-acts svg { width: 14px; height: 14px; }

.cp-tile-meta { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.cp-chan { display: flex; align-items: center; gap: 7px; min-width: 0; font-size: 12.5px;
  color: var(--teal-deep); text-decoration: none; }
.cp-chan svg { width: 13px; height: 13px; flex-shrink: 0; opacity: .8; }
.cp-chan span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cp-chan:hover span { text-decoration: underline; }
.cp-chan.is-none { color: var(--tx-mute); font-style: italic; }
.cp-chan.is-none:hover span { text-decoration: none; }

.cp-tile-roles { display: flex; gap: 5px; flex-wrap: wrap; }
.cp-notes { font-size: 12.5px; line-height: 1.5; color: var(--tx-soft); margin: 0; }
.cp-tile-src { font-size: 11.5px; line-height: 1.45; color: var(--tx-mute);
  padding-top: 9px; border-top: 1px dashed var(--line); }

.cp-role { font-size: 11px; padding: 2px 8px; border-radius: 999px; color: var(--tx-mute);
  background: var(--card-2); border: 1px solid var(--line); white-space: nowrap; }
.cp-role.is-primary { color: var(--teal-deep); background: color-mix(in srgb, var(--teal) 11%, var(--card));
  border-color: color-mix(in srgb, var(--teal) 26%, var(--line)); }
.cp-role.is-none { border-style: dashed; background: transparent; font-style: italic; }
.cp-foot { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-top: 14px;
  padding-top: 12px; border-top: 1px solid var(--line); }
.cp-routing { flex: 1; min-width: 200px; font-size: 12px; line-height: 1.5; }
.cp-routing b { color: var(--tx-soft); font-weight: 600; }
.cp-check { display: flex; align-items: flex-start; gap: 9px; padding: 6px 0; font-size: 12.5px;
  line-height: 1.5; color: var(--tx-soft); cursor: pointer; }
.cp-check input { width: 15px; height: 15px; margin-top: 2px; accent-color: var(--teal); flex-shrink: 0; }
.cp-check b { color: var(--head, var(--ink)); font-weight: 600; }

/* Detail rows: label hard left, value hard right, and never touching - the
   minimum gap holds even when a long email pushes the value across the row. */
.doss-details { display: flex; flex-direction: column; }
.doss-detail { display: flex; align-items: baseline; justify-content: space-between; gap: 20px; padding: 11px 2px; border-bottom: 1px solid var(--line-soft); }
.doss-detail:last-child { border-bottom: 0; }
.doss-detail .dl { flex-shrink: 0; font-size: 12.5px; color: var(--tx-mute); font-weight: 600; text-transform: uppercase; letter-spacing: .04em; }
.doss-detail .dv { min-width: 0; font-size: 13.5px; color: var(--tx); text-align: right; word-break: break-word; }
.doss-detail a.dv { color: var(--teal-deep); text-decoration: none; }
.doss-detail a.dv:hover { text-decoration: underline; }
/* What a one-word value means, on the row that carries it - "Past Client" is
   the only value on this panel nobody can interpret from the word alone. */
.doss-detail-why { display: block; font-size: 12px; line-height: 1.45; color: var(--tx-mute); font-weight: 400; margin-top: 2px; }
.doss-tags { display: flex; flex-wrap: wrap; gap: 7px; margin-top: 16px; }
.tag-pill { font-size: 12px; font-weight: 600; color: var(--teal-deep); background: color-mix(in srgb, var(--teal) 11%, var(--card, #fff)); border: 1px solid color-mix(in srgb, var(--teal) 22%, var(--line)); border-radius: var(--r-pill); padding: 3px 10px; }


/* ── Provenance / source drawer rows ──────────────────────────────────────── */
.prov-row { display: grid; grid-template-columns: 1fr auto; gap: 4px 12px; padding: 12px 0; border-bottom: 1px solid var(--line-soft); cursor: pointer; transition: background .1s; }
.prov-row:hover { background: var(--card-2); }
.prov-row .nm { font-weight: 700; font-size: 13.5px; } .prov-row .mt { grid-column: 1; font-size:12px; color: var(--tx-mute); }
.prov-row .amt { grid-row: span 2; align-self: center; font-family: var(--f-mono); font-weight: 700; font-size: 13.5px; text-align: right; }

/* ── Command palette ──────────────────────────────────────────────────────── */
/* Neutral black, matching .scrim in theme.css. The old value was a green-tinted
   ink from the emerald system - over charcoal it read as a faint green cast. */
.cmdk-scrim { position: fixed; inset: 0; z-index: 300; background: rgba(0,0,0,.66); backdrop-filter: blur(3px); display: flex; align-items: flex-start; justify-content: center; padding-top: 12vh; opacity: 0; transition: opacity .15s; }
.cmdk-scrim.on { opacity: 1; }
.cmdk { width: min(620px, 94vw); background: var(--card); border-radius: var(--r-lg); box-shadow: var(--sh-3); overflow: hidden; transform: translateY(-8px); transition: transform .18s; }
.cmdk-scrim.on .cmdk { transform: none; }
.cmdk-input { display: flex; align-items: center; gap: 11px; padding: 16px 18px; border-bottom: 1px solid var(--line); }
.cmdk-input svg { width: 19px; height: 19px; color: var(--tx-mute); }
.cmdk-input input { flex: 1; border: 0; outline: 0; font-family: var(--f-body); font-size: 16px; color: var(--tx); background: none; }
.cmdk-input kbd { font-family: var(--f-mono); font-size:12px; color: var(--tx-mute); background: var(--line-soft); padding: 3px 7px; border-radius: 6px; }
.cmdk-list { max-height: 52vh; overflow-y: auto; padding: 8px; }
.cmdk-group { font-family: var(--f-mono); font-size:12px; text-transform: uppercase; letter-spacing: .08em; color: var(--tx-mute); padding: 10px 10px 5px; }
.cmdk-item { display: flex; align-items: center; gap: 11px; padding: 10px 11px; border-radius: 9px; cursor: pointer; font-size: 14px; }
.cmdk-item.on, .cmdk-item:hover { background: color-mix(in srgb, var(--teal) 10%, transparent); }
.cmdk-item .ic { width: 30px; height: 30px; border-radius: 8px; display: grid; place-items: center; background: var(--line-soft); color: var(--teal-deep); flex-shrink: 0; }
.cmdk-item .ic svg { width: 16px; height: 16px; } .cmdk-item .meta { min-width: 0; } .cmdk-item .meta b { display: block; font-weight: 600; }
.cmdk-item .meta small { color: var(--tx-mute); font-size: 12px; }
.cmdk-item .tag { margin-left: auto; font-size:12px; color: var(--tx-mute); font-family: var(--f-mono); }

/* Stagger helper for grids of cards */
.stagger > * { opacity: 0; animation: reveal .5s cubic-bezier(.22,1,.36,1) forwards; }
.stagger > *:nth-child(1){animation-delay:.02s}.stagger > *:nth-child(2){animation-delay:.06s}.stagger > *:nth-child(3){animation-delay:.1s}
.stagger > *:nth-child(4){animation-delay:.14s}.stagger > *:nth-child(5){animation-delay:.18s}.stagger > *:nth-child(6){animation-delay:.22s}
.stagger > *:nth-child(7){animation-delay:.26s}.stagger > *:nth-child(8){animation-delay:.3s}
@media (prefers-reduced-motion: reduce) { .stagger > * { animation: none; opacity: 1; } }

/* ============================================================================
   POP LAYER - depth, color, interactivity. Loaded after the base; wins.
   Goal: nothing flat, nothing white-on-white, everything alive on hover.
   ========================================================================== */

/* A calm but non-flat field so every card visibly floats. Echoes the dark
   "pre-login" atmosphere - a faint blueprint grid plus twin accent glows: the
   mode color from the top-right, the harmonizing secondary thread from the
   bottom-left. The base is a light, airy fade from pale mode-green at the top
   into the cool light-blue paper - a daylight take on the landing wash. */
/* The wash lives on a fixed pseudo-element, not on the body with
   `background-attachment: fixed` - fixed attachment forces the browser to
   repaint the whole multi-layer background on every scroll (it can't be
   lifted onto a compositor layer), which showed up as scroll jank on long
   pages. A `position: fixed` layer paints once and composites. Same layers,
   same look; `z-index: -1` keeps it under all content, above the body's
   paper base. */
body::before {
  content: '';
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  /* The ambient wash. Retuned for the charcoal canvas - the ratios here are not
     interchangeable with a light theme's:

     • The grid used color-mix(--ink 3.5%), i.e. the CHROME near-black. On a pale
       room that drew a faint dark rule; --ink is now the page itself, so it drew
       near-black on near-black - nothing. A grid on a dark canvas is a faint
       LIGHT line, which is exactly what --grid-ln has always held (it was
       declared in theme.css and never consumed here).
     • The accent glows drop from 22%/11% to 7%/4.5%. Over white, a 22% cyan wash
       is a pale tint; over #0C0C0E it is a saturated cyan fog across the top of
       every page. These are the values the shipped MinMax skin proved.
     • The 170deg sheet is gone. It mixed the accent into --card and ran it to
       --paper to fake a lit corner on paper; the two radials already do that
       here, and stacked on charcoal it only greyed the top third. */
  background:
    /* GRAIN on top of everything, so it sits over the wash rather than under
       it - a noise layer beneath a gradient is a gradient. See --grain in
       theme.css for the recipe and for why it is a token. */
    var(--grain) 0 0 / 160px 160px,
    linear-gradient(var(--grid-ln) 1px, transparent 1px) 0 0 / 60px 60px,
    linear-gradient(90deg, var(--grid-ln) 1px, transparent 1px) 0 0 / 60px 60px,
    radial-gradient(150% 100% at 100% -10%, color-mix(in srgb, var(--teal-bright) var(--wash-1, 22%), transparent), transparent 52%),
    radial-gradient(130% 95% at -10% 110%, color-mix(in srgb, var(--accent-2) var(--wash-2, 11%), transparent), transparent 54%),
    /* The lit sheet. It used to run from an accent mixed into --CARD, which on
       this polarity is #ffffff - so the top 60% of every page was a sheet of
       tinted WHITE and a white card on it had nothing to sit against. That is
       the single biggest contributor to the "too bright" read. Mixing the same
       accent into --paper keeps the lit corner and the hue exactly, and simply
       stops the canvas from impersonating a card. */
    linear-gradient(170deg, color-mix(in srgb, var(--teal-bright) var(--wash-3, 14%), var(--paper)) 0%, var(--paper) 62%),
    /* A vignette, and the second half of the "too bright" fix. The wash lights
       the top-right and the bottom-left corner; between them the middle of a
       1560px window was the flattest, brightest area on the screen and the one
       the eye spends the whole day on. This darkens the outer edge by ~4% and
       leaves the center alone, so the page has a shape. */
    radial-gradient(120% 90% at 50% 42%, transparent 52%, color-mix(in srgb, var(--ink) 7%, transparent) 100%),
    var(--paper);
}
/* THE WASH IS POLARITY-DEPENDENT and the ratios are not interchangeable: over
   white, 22% cyan is the pale tint that makes the canvas read as lit; over
   #0C0C0E the same 22% is a saturated fog. The defaults above are the product's
   own (light); a dark pack turns them down via --wash-1/2/3 in theme.css.
   Dropping the light numbers to the dark ones is what turned a cyan-lit
   workspace gray, so do not "simplify" these to one value.

   It mixes --teal-bright, not --teal. --teal is the accent stepped down until it
   is legible AS TEXT (#007c89); the wash is a glow, not type, and wants the
   brand cyan itself (#00E5FF). Using --teal here is what makes the canvas look
   muddy rather than lit. */
/* The Field Portal paints its own dark paper. The old body-background version
   of this wash lost to the field skin automatically (higher-specificity body
   rule); a pseudo-element doesn't compete with `body { background }`, so the
   opt-out has to be said. (The client portal needs nothing here - portal-skin
   declares its own `body::before` under html[data-portal="on"], which wins.) */
:root[data-field="on"] body::before { content: none; }

/* Cards: crisper edge + layered shadow + faint top highlight.
   Retuned with the paper step (theme.css). Three things carry a tile's edge and
   all three were tuned against a 94%-luminance page: the border, a tight contact
   shadow, and an ambient one. The border gains ~50% more ink, the contact shadow
   goes from .04 to .07 alpha at 3px, and the ambient layer keeps its reach but
   lands darker. The inset hairline is the "faint top highlight" this comment has
   promised since it was written and never had - a 1px lit edge along the top of
   a white card is what separates it from the card behind it when they stack. */
.card {
  border-color: color-mix(in srgb, var(--ink) 14%, var(--line));
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.9),
    0 1px 3px rgba(7,20,15,.07),
    0 16px 34px -20px rgba(7,20,15,.34);
}
.card-head {
  background: linear-gradient(180deg, color-mix(in srgb, var(--teal) 4%, var(--card, #fff)), var(--card, #fff));
  border-bottom-color: color-mix(in srgb, var(--teal) 10%, var(--line-soft));
}

/* KPI tiles - the headline fix: tinted gradient, live accent edge, glow on hover. */
.kpi {
  background: linear-gradient(158deg, var(--card, #fff) 0%, color-mix(in srgb, var(--teal) 6%, var(--card, #fff)) 100%);
  border-color: color-mix(in srgb, var(--teal) 24%, var(--line));
  box-shadow: inset 0 1px 0 rgba(255,255,255,.85), 0 1px 3px rgba(7,20,15,.07), 0 12px 26px -16px rgba(7,20,15,.30);
}
.kpi::after {
  content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: 3px;
  background: linear-gradient(var(--teal-bright), var(--teal)); opacity: 0; transition: opacity .16s;
}
.kpi:hover {
  transform: translateY(-3px);
  border-color: color-mix(in srgb, var(--teal) 38%, var(--line));
  box-shadow: 0 18px 40px -18px color-mix(in srgb, var(--teal) 45%, rgba(7,20,15,.4));
}
.kpi:hover::after { opacity: 1; }
.kpi .v { font-size: 30px; }
.kpi .k { color: var(--tx-soft); }
.kpi .spark { opacity: 1; }
.kpi .drill { opacity: .35; }
/* The second half of the retired `.kpi.feature` treatment - deleted with the
   first (see the note above). Nothing emits the class any more. */

/* ── Accented KPI tiles ───────────────────────────────────────────────────────
   A per-tile accent (--kpi-a) instead of one solid dark "feature" tile. The rail
   is always visible here (on a plain tile it only appears on hover), so a row of
   tiles reads as a set of distinct measures rather than one headline and four
   also-rans. The corner wash is what gives the row its depth. */
.kpi.is-accent {
  background:
    radial-gradient(120% 130% at 100% 0%, color-mix(in srgb, var(--kpi-a) 15%, transparent), transparent 62%),
    linear-gradient(158deg, var(--card, #fff) 0%, color-mix(in srgb, var(--kpi-a) 7%, var(--card, #fff)) 100%);
  border-color: color-mix(in srgb, var(--kpi-a) 26%, var(--line));
}
.kpi.is-accent::after { opacity: 1; background: linear-gradient(color-mix(in srgb, var(--kpi-a) 55%, var(--card, #fff)), var(--kpi-a)); }
.kpi.is-accent:hover {
  border-color: color-mix(in srgb, var(--kpi-a) 50%, var(--line));
  box-shadow: 0 18px 40px -18px color-mix(in srgb, var(--kpi-a) 50%, rgba(7,20,15,.4));
}
.kpi.is-accent .kmark { display: inline-grid; place-items: center; width: 22px; height: 22px; border-radius: 7px;
  background: color-mix(in srgb, var(--kpi-a) 16%, transparent); color: var(--kpi-a); flex: none; }
.kpi.is-accent .kmark svg { width: 13px; height: 13px; }
.kpi.is-accent .v { color: var(--head, var(--ink)); }

/* Stat tiles - tint + bolder accent rail. */
.stat {
  background: linear-gradient(158deg, var(--card, #fff), color-mix(in srgb, var(--teal) 5%, var(--card, #fff)));
  border-color: color-mix(in srgb, var(--ink) 14%, var(--line));
  box-shadow: inset 0 1px 0 rgba(255,255,255,.85), 0 1px 3px rgba(7,20,15,.07), 0 12px 24px -16px rgba(7,20,15,.28);
  transition: transform .12s, box-shadow .15s, border-color .15s;
}
.stat::before { width: 4px; }
.stat:hover { transform: translateY(-2px); box-shadow: 0 16px 32px -18px rgba(7,20,15,.32); }
.stat.is-coral { background: linear-gradient(158deg, var(--card, #fff), color-mix(in srgb, var(--coral) 6%, var(--card, #fff))); }
.stat.is-gold  { background: linear-gradient(158deg, var(--card, #fff), color-mix(in srgb, var(--accent-2) 7%, var(--card, #fff))); }
/* is-gold is the NEUTRAL second accent (mint) since gold left the palette; a
   tile that means "look at this soon" - the middle of an ok / caution / coral
   ladder - is is-caution, the soft-coral step of the warning band. */
.stat.is-caution { background: linear-gradient(158deg, var(--card, #fff), color-mix(in srgb, var(--gold) 8%, var(--card, #fff))); }
.stat.is-ok    { background: linear-gradient(158deg, var(--card, #fff), color-mix(in srgb, var(--ok) 6%, var(--card, #fff))); }

/* Drillable stat tiles (core/stat-drill.js). A tile that opens its source
   records is a real <button>, so it needs the control reset - and it earns a
   pointer, a focus ring, and a corner glyph that plain tiles never get. */
button.stat {
  font: inherit; color: inherit; text-align: left; width: 100%; display: block;
  -webkit-appearance: none; appearance: none;
}
.stat.is-drill { cursor: pointer; }
.stat.is-drill:hover { border-color: var(--teal); }
.stat.is-drill:focus-visible { outline: 2px solid var(--teal); outline-offset: 2px; }
.stat.is-drill:active { transform: translateY(0); }

/* ── The corner mark ─────────────────────────────────────────────────────────
   Every tile carries an icon for WHAT IT MEASURES - money, time, volume, risk -
   resolved from its own label by ui.js's metricIcon(). It replaced `.stat-go`,
   an identical three-bar list glyph that appeared on every tile in the product
   regardless of the metric and read as a rendering fault rather than a control.

   PERMANENT REMOVAL. `.stat-go` is hidden below, not merely unstyled, because
   the markup that emitted it came from no-store ES modules while this sheet is
   cached: a stale bundle must not be able to put it back. Nothing in src/ emits
   the class any more and nothing should again - a tile that needs a different
   mark passes `ic`. */
.stat { padding-right: 40px; }
.stat-ic {
  position: absolute; top: 12px; right: 12px; width: 16px; height: 16px;
  color: var(--teal-deep); opacity: .34; pointer-events: none;
  transition: opacity .14s, color .14s, transform .14s;
}
.stat-ic svg { width: 100%; height: 100%; display: block; }
.stat.is-gold .stat-ic  { color:var(--accent-2-deep, var(--accent-2)); opacity: .46; }
.stat.is-caution .stat-ic { color:var(--gold-tx, var(--gold)); opacity: .46; }
.stat.is-coral .stat-ic { color:var(--coral-tx, var(--coral)); opacity: .46; }
.stat.is-ok .stat-ic    { color:var(--ok-tx, var(--ok)); opacity: .44; }
.stat.is-drill:hover .stat-ic,
.stat.is-drill:focus-visible .stat-ic { opacity: .92; transform: translateY(-1px); }
.stat-go { display: none !important; }

/* Compact, denser conversion funnel (was wasting vertical space). */
.funnel { gap: 6px; }
.funnel-row { grid-template-columns: 1fr 38px; gap: 8px; }
.funnel-meta { margin-bottom: 2px; font-size: 12px; }
/* The track is a channel cut into the card; the fill is an object in it. That
   reading is what makes the empty remainder mean "not yet" rather than "blank".
   The sheen is a gradient over whatever color the caller passed, so every
   stage color - and every per-tenant brand color - catches light identically. */
.funnel-track { height: 10px; border-radius: 5px; box-shadow: inset 0 1px 2px rgba(0,0,0,.14); }
.funnel-fill {
  box-shadow: inset 0 1px 0 rgba(255,255,255,.34);
  background-image: linear-gradient(180deg, rgba(255,255,255,.28) 0, rgba(255,255,255,.04) 50%, rgba(0,0,0,.07) 100%);
}
/* Reading a funnel is reading one row against the one above it, so a row lifts
   on hover and its neighbors step back - the same emphasis the SVG charts use. */
.funnel:hover .funnel-row { opacity: .55; transition: opacity .14s ease; }
.funnel .funnel-row:hover { opacity: 1; }
@media (prefers-reduced-motion: reduce) {
  .funnel-fill { transition: none; }
  .funnel:hover .funnel-row { transition: none; }
}
.funnel-conv { font-size:12px; }

/* Pills - a touch more saturated + crisp. */
.pill { box-shadow: inset 0 0 0 1px color-mix(in srgb, currentColor 16%, transparent); }

/* The blanket drop-shadow(... var(--teal) ...) that used to live here is gone.
   It tinted every bar's shadow cyan whatever the bar's own color was, and it
   matched .chart-bar:hover on specificity while sitting later in the file, so it
   overwrote the hover brightness entirely. Depth is per-mark now, keyed off
   --mark-c, in the chart block at the top of this file. */

/* THE GLOW IS GONE (2026-09-20, Jeremy: "cut the glow behind cyan buttons").
   These two rules put an 18px, then a 24px, cast shadow mixed from the fill
   behind every primary button, which is how a cyan button came to sit in a
   cyan haze - the control had no edge, only a fade. The button is an object
   now, with a lit top edge and a short shadow, and it is defined once in
   theme.css beside the rest of the family. Nothing replaces these here: a
   second box-shadow in a later file is exactly what made the first one
   impossible to see. */

/* Tables - resting zebra + warmer hover + clearer header. The stripe keys off
   --ink so it retunes per mode and stays subtle on the warm-cream paper. */
.tbl thead th { background: color-mix(in srgb, var(--teal) 3%, var(--card, #fff)); }
.tbl tbody tr:nth-child(even) { background: color-mix(in srgb, var(--ink) 2.4%, transparent); }
.tbl tbody tr:hover { background: color-mix(in srgb, var(--teal) 6%, var(--card, #fff)); }

/* Kanban cards - left status accent + stronger hover. */
.kan-card { border-left: 3px solid color-mix(in srgb, var(--teal) 35%, var(--line)); }
.kan-card:hover { transform: translateY(-2px); }

/* Dossier tiles - tint, not flat. */
.dossier-tile { background: linear-gradient(158deg, var(--card, #fff), color-mix(in srgb, var(--teal) 5%, var(--card, #fff))); }

/* Generic section heading used across pages. */
.sec-h { display: flex; align-items: baseline; justify-content: space-between; margin: 22px 0 12px; }
.sec-h .t { font-family: var(--f-head); font-weight: 800; font-size: 15px; color: var(--head, var(--ink)); }
.sec-h .t small { font-weight: 500; color: var(--tx-mute); font-family: var(--f-body); margin-left: 8px; }

/* ============================================================================
   SCHEDULER - calendar / board / list views + GPS-style operations map.
   ========================================================================== */

/* Calendar layout: grid + optional map side panel. */
.cal-split { display: grid; grid-template-columns: minmax(0, 1fr) 360px; gap: 14px; align-items: start; }
@media (max-width: 1100px) { .cal-split { grid-template-columns: 1fr; } }

/* Per-staff summary strip. */
.sched-strip { display: grid; grid-template-columns: repeat(auto-fill, minmax(186px, 1fr)); gap: 12px; }
.sched-staff { padding: 14px 16px; }
.sched-figs { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
.sched-figs .fk { font-size:12px; text-transform: uppercase; letter-spacing: .05em; color: var(--tx-mute); }
.sched-figs .fv { font-family: var(--f-num); font-weight: 800; font-size: 16px; color: var(--head, var(--ink)); margin-top: 2px; font-variant-numeric: tabular-nums; }

/* ── DAY grid: time axis + staff lanes ─────────────────────────────────────── */
.day-grid { display: flex; overflow-x: auto; padding: 4px; }
.day-axis { flex: 0 0 52px; position: sticky; left: 0; background: var(--card); z-index: 2; }
.day-axis-body { position: relative; }
.day-hour { position: absolute; left: 0; right: 4px; font-family: var(--f-mono); font-size:12px; color: var(--tx-mute); transform: translateY(-6px); text-align: right; padding-right: 6px; }
.day-hour::after { content: ''; position: absolute; right: 0; top: 6px; width: 6px; border-top: 1px solid var(--line); }
/* The lanes are NOT their own scroll container - .day-grid is the single scroller
   so the sticky hour axis and the staff lanes always move as one. flex-shrink 0
   lets the lanes keep their natural width and push .day-grid into scrolling. */
.day-lanes { display: flex; flex: 1 0 auto; min-width: 0; }
.day-lane { flex: 1 0 150px; border-left: 1px solid var(--line-soft); }
.day-lane-head { height: 40px; display: flex; align-items: center; gap: 7px; padding: 0 10px; border-bottom: 1px solid var(--line); position: sticky; top: 0; background: linear-gradient(180deg, color-mix(in srgb, var(--teal) 4%, var(--card, #fff)), var(--card, #fff)); z-index: 1; font-size: 12.5px; font-weight: 600; color: var(--tx-soft); white-space: nowrap; }
.day-lane-body { position: relative; background-image: repeating-linear-gradient(to bottom, transparent 0, transparent 53px, var(--line-soft) 53px, var(--line-soft) 54px); }
.day-block { position: absolute; z-index: 1; left: 4px; right: 4px; border: 0; border-left: 3px solid var(--c); border-radius: 8px; background: color-mix(in srgb, var(--c) 12%, var(--card, #fff)); padding: 4px 7px; text-align: left; cursor: pointer; overflow: hidden; display: flex; flex-direction: column; gap: 1px; box-shadow: var(--sh-1); transition: transform .1s, box-shadow .14s; }
.day-block:hover { transform: translateY(-1px); box-shadow: var(--sh-2); z-index: 3; }
.day-block .db-t { font-family: var(--f-mono); font-size:12px; color: var(--tx-mute); }
.day-block .db-title { font-size: 12.5px; font-weight: 700; color: var(--head, var(--ink)); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.day-block .db-sub { font-size:12px; color: var(--tx-soft); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.day-block[draggable="true"] { cursor: grab; }
.day-block.drag { opacity: .5; cursor: grabbing; }
.day-lane-body.lane-drop { background: color-mix(in srgb, var(--teal) 7%, transparent); outline: 2px dashed color-mix(in srgb, var(--teal) 40%, transparent); outline-offset: -3px; }
/* Busy time from a connected calendar. The band itself is styled in
   schedule.js (.day-busy); these are the pieces around it: the all-day strip
   pinned to the top of a lane, the screen-reader explanation the band carries
   instead of a tooltip nobody can hover (pointer-events: none), and the legend
   chip in the calendar toolbar that names the hatching. */
.day-busy.is-all-day { top: 2px; height: 18px; border-radius: 5px; }
.day-busy .day-busy-sr { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
.day-busy-legend { display: inline-flex; align-items: center; gap: 7px; font-size: 12px; font-weight: 600; color: var(--tx-mute); white-space: nowrap; }
.day-busy-legend .day-busy-swatch { display: inline-block; width: 22px; height: 12px; border-radius: 4px; border: 1px dashed color-mix(in srgb, var(--tx-mute) 45%, transparent); background: repeating-linear-gradient(135deg, color-mix(in srgb, var(--tx-mute) 13%, transparent) 0 5px, transparent 5px 10px); }

/* ── WEEK grid: 7 columns ──────────────────────────────────────────────────── */
.week-grid { display: grid; grid-template-columns: repeat(7, 1fr); }
.week-col { border-left: 1px solid var(--line-soft); min-height: 220px; }
.week-col:first-child { border-left: 0; }
.week-col.is-today { background: color-mix(in srgb, var(--teal) 4%, var(--card, #fff)); }
.week-col-head { display: flex; align-items: baseline; justify-content: space-between; padding: 8px 10px; border-bottom: 1px solid var(--line); font-size: 12px; }
.week-col-head b { font-size:12px; text-transform: uppercase; letter-spacing: .04em; color: var(--tx-soft); }
.week-today { font-family: var(--f-head); font-weight: 800; color: var(--teal-deep); }
.week-col-body { padding: 7px; display: flex; flex-direction: column; gap: 6px; }
.week-item { display: flex; flex-direction: column; gap: 1px; border: 0; border-left: 3px solid var(--c); background: color-mix(in srgb, var(--c) 10%, var(--card, #fff)); border-radius: 6px; padding: 5px 7px; text-align: left; cursor: pointer; transition: transform .1s; }
.week-item:hover { transform: translateX(1px); }
.week-item .wi-t { font-size:12px; color: var(--tx-mute); }
.week-item .wi-title { font-size: 12px; font-weight: 600; color: var(--head, var(--ink)); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.week-empty { padding: 8px; text-align: center; font-size: 13px; }

/* ── MONTH grid: 6×7 ───────────────────────────────────────────────────────── */
.month-dow { display: grid; grid-template-columns: repeat(7, 1fr); padding: 8px 4px 0; }
.month-dow div { font-size:12px; text-transform: uppercase; letter-spacing: .05em; color: var(--tx-mute); text-align: center; font-weight: 600; }
.month-grid { display: grid; grid-template-columns: repeat(7, 1fr); grid-auto-rows: minmax(92px, 1fr); padding: 4px; }
.month-cell { border: 1px solid var(--line-soft); margin: -0.5px 0 0 -0.5px; padding: 5px 6px; cursor: pointer; overflow: hidden; transition: background .12s; }
.month-cell:hover { background: color-mix(in srgb, var(--teal) 5%, var(--card, #fff)); }
.month-cell.out { background: var(--card-2); }
.month-cell.out .mc-date { color: var(--tx-mute); }
.month-cell.is-today .mc-date { background: var(--teal); color: var(--on-teal); border-radius: 999px; }
.mc-date { display: inline-grid; place-items: center; min-width: 21px; height: 21px; font-size: 12px; font-weight: 600; color: var(--tx-soft); padding: 0 5px; }
.mc-chips { display: flex; flex-direction: column; gap: 3px; margin-top: 3px; }
.mc-chip { font-size:12px; color: var(--tx); border-left: 2px solid var(--c); background: color-mix(in srgb, var(--c) 9%, var(--card, #fff)); border-radius: 4px; padding: 1px 5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.mc-chip .mono { font-size: 11px; color: var(--tx-mute); }
.mc-more { font-size:12px; color: var(--teal-deep); font-weight: 600; padding-left: 3px; }

/* ── GPS-style operations map ──────────────────────────────────────────────── */
.map-wrap { position: relative; }
.ops-map { width: 100%; height: auto; display: block; border-radius: 14px; box-shadow: inset 0 0 60px rgba(0,0,0,.35); }
.map-street { stroke: rgba(120, 220, 200, .10); stroke-width: 1; }
.map-arterial { stroke: rgba(140, 240, 215, .22); stroke-width: 2.5; }
.map-route { fill: none; stroke: var(--teal-bright); stroke-width: 2.5; stroke-linecap: round; stroke-linejoin: round; stroke-dasharray: 2 7; opacity: .7; filter: drop-shadow(0 0 4px color-mix(in srgb, var(--teal-bright) 60%, transparent)); }
.map-pin { cursor: pointer; }
.map-pin .map-glow { fill: var(--c); opacity: .28; filter: url(#pinblur); transition: opacity .15s; }
.map-pin .map-dot { fill: var(--c); stroke: #fff; stroke-width: 1.6; }
.map-pin .map-num { fill: #06231f; font-family: var(--f-mono); font-size: 8px; font-weight: 700; text-anchor: middle; }
.map-pin:hover .map-glow { opacity: .55; }
.map-pin:hover .map-dot { stroke-width: 2.2; }
.map-empty { fill: rgba(180, 230, 215, .5); font-size: 13px; font-family: var(--f-body); }
.map-legend { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 12px; }
.map-legend span { display: inline-flex; align-items: center; gap: 6px; font-size:12px; color: var(--tx-soft); }
.map-legend i { width: 9px; height: 9px; border-radius: 50%; display: inline-block; }
.map-legend-route i { width: 14px; height: 0; border: 0; border-top: 2px dashed var(--teal-bright); border-radius: 0; }

/* Even KPI rows - fixed responsive columns so no orphan tile dangles. */
.kpi-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); }
@media (max-width: 1180px) { .kpi-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (max-width: 820px)  { .kpi-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 520px)  { .kpi-grid { grid-template-columns: 1fr; } }

/* ── Responsive safety - keep every page within the viewport on small screens ──
   Multi-column inline grids collapse to one column; wide tables scroll instead
   of forcing horizontal page overflow.

   The selector list is the whole point. This block was `.page`-scoped, and
   drawers and modals are appended to <body> (core/ui.js) - they are siblings of
   .shell, not descendants of .page - so every two-column form and every table
   inside a panel kept its desktop layout on a phone. That is ~40 call sites:
   the invoice editor, the customer forms, the task panel, the consulting
   pipeline. Any new overlay root belongs in this list. */
@media (max-width: 760px) {
  :is(.page, .drawer-body, .modal .box-body, .na-body) .grid { grid-template-columns: minmax(0, 1fr) !important; }
  :is(.page, .drawer-body, .modal .box-body, .na-body) .tbl { display: block; overflow-x: auto; -webkit-overflow-scrolling: touch; }
  /* Headers stay on one line (they are short labels and set the column rhythm);
     data cells wrap. Blanket `white-space: nowrap` on the table turned every
     table that WOULD have fitted into a horizontal scroller. `.tbl .num` keeps
     its own nowrap from theme.css, so figures never break mid-number. */
  :is(.page, .drawer-body, .modal .box-body, .na-body) .tbl th { white-space: nowrap; }
}
/* Charts never push past their container. (The day grid is handled by .day-grid's
   own overflow - do NOT add a scroller on .day-lanes: that desyncs the hour axis
   from the appointment lanes and produces two competing scrollbars.) */
.chart-svg { max-width: 100%; }

/* ── The one help affordance (core/hint.js, docs/UI-HINTS.md) ─────────────────
   A "?" marker that shows its text on hover or keyboard focus. The bubble is
   absolutely positioned above the marker and clamped to the viewport width so
   it can never widen the layout - the .fs-help it replaces once hung 100px
   past the right edge of every phone. */
.ui-hint { display: inline-flex; align-items: center; justify-content: center; width: 16px; height: 16px; margin-left: 6px;
  border-radius: 50%; font-size: 10.5px; font-weight: 700; line-height: 1; vertical-align: middle; cursor: help;
  color: var(--tx-soft); background: color-mix(in srgb, var(--tx-soft) 14%, transparent); position: relative; user-select: none; }
.ui-hint:hover, .ui-hint:focus-visible { color: var(--paper, #fff); background: var(--accent, #0c8f86); outline: none; }
/* On a page title. Every page used to carry a lede under its <h1>, a band of
   vertical space spent on one sentence the reader needed once; the sentence
   now hangs off the title as a hint. Scaled up against a 27px heading and
   lifted a hair so it sits on the cap height rather than the baseline. */
h1 .ui-hint { width: 19px; height: 19px; font-size: 12px; margin-left: 9px; transform: translateY(-1px); }
/* The CSS bubble is the NO-JS FALLBACK only. It is absolutely positioned, so
   inside a `.drawer-body` or `.modal .box-body` - both `overflow-y: auto`, which
   makes overflow-x auto too - it is clipped at the panel edge. core/hint.js
   replaces it with a body-level fixed bubble and stamps [data-hint-js] on <html>,
   which switches every rule below off. Do not "fix" the clipping here; it cannot
   be fixed here. */
.ui-hint::after { content: attr(data-hint); display: none; position: absolute; bottom: calc(100% + 8px); left: 50%; transform: translateX(-50%);
  width: max-content; max-width: min(300px, 78vw); background: var(--card); border: 1px solid var(--line); border-radius: 10px;
  box-shadow: var(--sh-2, 0 6px 24px rgba(0,0,0,.18)); padding: 9px 11px; font-size: 12px; font-weight: 400;
  color: var(--tx-soft); line-height: 1.5; text-align: left; white-space: normal; pointer-events: none; z-index: 70; }
.ui-hint:hover::after, .ui-hint:focus-visible::after { display: block; }
.ui-hint-end::after { left: auto; right: 0; transform: none; }
.ui-hint-start::after { left: 0; transform: none; }
@media (max-width: 640px) { .ui-hint::after { left: auto; right: 0; transform: none; } }
:root[data-hint-js="on"] .ui-hint::after { content: none; display: none; }

/* The real bubble: one element on <body>, positioned from the marker's rect by
   core/hint.js. `position: fixed` is what puts it outside every scroll container
   in the product, and the z-index clears the drawer (100) and the Brand Studio
   overlay (120). */
.ui-hint-pop { position: fixed; z-index: 200; max-width: 300px; background: var(--card, #fff);
  border: 1px solid var(--line, #e2e8e3); border-radius: 10px; box-shadow: var(--sh-2, 0 6px 24px rgba(0,0,0,.18));
  padding: 9px 11px; font-family: var(--f-body, system-ui), sans-serif; font-size: 12px; font-weight: 400;
  color: var(--tx-soft, #42504a); line-height: 1.5; text-align: left; white-space: normal;
  pointer-events: none; }
.ui-hint-pop[hidden] { display: none; }
