/* court-tracker.css — all selectors prefixed `ctt-` so nothing bleeds in/out of the
   host page. The widget lives entirely inside #court-tracker-root. Fixed outer height
   so the host article never reflows when the info pane slides. */

.ctt-root {
  --ctt-bg: #f7f8fa;
  --ctt-panel: #ffffff;
  --ctt-ink: #1b1f24;
  --ctt-muted: #5b6470;
  --ctt-line: #d8dde4;
  --ctt-accent: #2f6feb;
  --ctt-rep: #d1343f;
  --ctt-dem: #2f6feb;
  --ctt-other: #9aa3ad;
  --ctt-senior: #9aa3ad;
  --ctt-map: #e9edf2;
  --ctt-circuit: #cdd6e0;

  box-sizing: border-box;
  position: relative;
  width: 100%;
  max-width: 1100px;
  margin: 0 auto;
  height: 640px;
  display: flex;
  flex-direction: column;
  font-family: system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif;
  color: var(--ctt-ink);
  background: var(--ctt-bg);
  border: 1px solid var(--ctt-line);
  border-radius: 10px;
  overflow: hidden;
}
.ctt-root *, .ctt-root *::before, .ctt-root *::after { box-sizing: border-box; }

.ctt-header {
  flex: 0 0 auto; display: flex; flex-direction: column; gap: 2px;
  padding: 10px 14px; border-bottom: 1px solid var(--ctt-line); background: var(--ctt-panel);
}
.ctt-tracked {
  font-size: 11px; font-weight: 600; color: var(--ctt-accent);
}
.ctt-tracked:empty { display: none; }
.ctt-title-row { display: flex; align-items: center; gap: 10px; }
.ctt-title-group { display: flex; align-items: baseline; gap: 10px; flex: 1 1 auto; min-width: 0; }
.ctt-title { font-size: 15px; font-weight: 650; }
.ctt-subtitle { font-size: 12px; color: var(--ctt-muted); }

/* Header search bar — right-aligned in the title row (operator spec, 2026-09-05). */
.ctt-search { position: relative; flex: 0 0 auto; width: 220px; max-width: 42%; }
.ctt-search-input {
  width: 100%; padding: 6px 26px 6px 10px; border: 1px solid var(--ctt-line); border-radius: 6px;
  background: var(--ctt-bg); color: inherit; font: inherit; font-size: 12px;
}
.ctt-search-input:focus { outline: 2px solid var(--ctt-accent); outline-offset: -1px; }
.ctt-search-clear {
  display: none; position: absolute; right: 5px; top: 50%; transform: translateY(-50%);
  width: 18px; height: 18px; border: 0; border-radius: 50%; background: transparent;
  color: var(--ctt-muted); font-size: 14px; line-height: 1; cursor: pointer; padding: 0;
}
.ctt-search-clear.ctt-is-visible { display: block; }
.ctt-search-clear:hover { background: var(--ctt-line); color: var(--ctt-ink); }
.ctt-search-results {
  display: none; position: absolute; top: calc(100% + 5px); right: 0; width: 320px; max-width: 80vw;
  max-height: 360px; overflow-y: auto; background: var(--ctt-panel); border: 1px solid var(--ctt-line);
  border-radius: 8px; box-shadow: 0 8px 24px rgba(0,0,0,0.16); z-index: 30; padding: 4px;
}
.ctt-search-results.ctt-is-open { display: block; }
.ctt-search-empty { padding: 10px 8px; font-size: 12px; color: var(--ctt-muted); }
.ctt-search-result {
  display: block; width: 100%; text-align: left; padding: 6px 8px; border-radius: 6px;
  background: transparent; color: inherit; font: inherit; cursor: pointer; outline: none;
}
.ctt-search-result:hover, .ctt-search-result:focus { background: var(--ctt-map); }
.ctt-search-result:focus { outline: 2px solid var(--ctt-accent); outline-offset: -2px; }
/* A roving judge's row has no click-to-navigate of its own (CLAUDE.md-equivalent operator spec:
   "the usual behavior of the whole result row jumping... is turned off for this one row, except
   through its buttons") — the plain-row cursor/hover above would misleadingly suggest otherwise. */
.ctt-search-result-roving { cursor: default; }
.ctt-search-result-roving:hover { background: transparent; }
.ctt-search-result + .ctt-search-result { margin-top: 1px; }
.ctt-search-name { font-size: 13px; font-weight: 500; }
.ctt-search-match { font-weight: 800; }
.ctt-search-senior {
  font-size: 10px; font-weight: 600; color: var(--ctt-senior); border: 1px solid var(--ctt-senior);
  border-radius: 3px; padding: 0 3px; margin-left: 4px; vertical-align: 1px;
}
/* Two separate flex children (`.ctt-search-president` / `.ctt-search-court`), not one run of
   text: a flex item wraps as a WHOLE unit, never mid-phrase, so when the line doesn't fit — a
   roving judge's district buttons are the main case — the court half drops to its own line
   BELOW the president chip, instead of an awkward mid-list wrap. Ordinary rows still read as
   one line whenever they fit; nothing about this rule fires unless the content actually overflows. */
.ctt-search-meta {
  font-size: 11px; color: var(--ctt-muted); display: flex; align-items: center;
  column-gap: 5px; row-gap: 1px; flex-wrap: wrap; margin-top: 1px;
}
.ctt-search-president { display: inline-flex; align-items: center; white-space: nowrap; }
.ctt-search-court { min-width: 0; }
/* Per-court jump buttons for a roving judgeship (one seat shared across same-state districts).
   Plain/muted like ordinary meta text until it's the ArrowLeft/Right-selected one — the
   operator's own "visible marker of text formatting switching" — which turns bold + underlined
   + accent-colored, doubling as both the visual marker AND the Enter/Space navigation target. */
.ctt-search-court-btn {
  border: 0; background: transparent; color: inherit; font: inherit; font-size: 11px;
  padding: 0; cursor: pointer; text-decoration: underline; text-decoration-color: transparent;
}
.ctt-search-court-btn:hover { color: var(--ctt-ink); }
.ctt-search-court-btn.ctt-is-selected {
  font-weight: 700; color: var(--ctt-accent); text-decoration-color: var(--ctt-accent);
}

.ctt-body { flex: 1 1 auto; display: flex; min-height: 0; }

/* selector bar */
.ctt-selector {
  flex: 0 0 220px; border-right: 1px solid var(--ctt-line); background: var(--ctt-panel);
  overflow-y: auto; padding: 8px;
}
.ctt-selector-group-label {
  font-size: 11px; text-transform: uppercase; letter-spacing: 0.6px;
  color: var(--ctt-muted); padding: 8px 8px 4px;
}
.ctt-selector-item, .ctt-selector-back {
  display: block; width: 100%; text-align: left; padding: 7px 10px; border: 0;
  border-radius: 6px; background: transparent; color: inherit; font: inherit; cursor: pointer;
}
.ctt-selector-item:hover { background: var(--ctt-map); }
.ctt-selector-item.ctt-is-selected { background: var(--ctt-accent); color: #fff; }
.ctt-selector-back { margin-bottom: 6px; color: var(--ctt-accent); font-weight: 600; }
.ctt-selector-back:hover { background: var(--ctt-map); }
/* "Summary" replaces the old lone SCOTUS entry — a distinct top-level destination (SCOTUS |
   Appellate | District), so it reads larger than an ordinary circuit row, not like a 14th
   circuit in the list. */
.ctt-summary-btn {
  font-size: 14px; font-weight: 700; padding: 11px 10px; margin-bottom: 8px;
  border: 1px solid var(--ctt-line); background: var(--ctt-panel);
}
.ctt-summary-btn:hover { background: var(--ctt-map); }
.ctt-summary-btn.ctt-is-selected { background: var(--ctt-accent); border-color: var(--ctt-accent); }

/* map viewport + stacked SVG layers */
.ctt-map-viewport { position: relative; flex: 1 1 auto; min-width: 0; overflow: hidden; background: var(--ctt-map); }
.ctt-svg-stack { position: absolute; inset: 0; }
.ctt-svg-layer {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  padding: 12px; transition: opacity 420ms ease, transform 420ms ease;
}
.ctt-svg-layer svg { max-width: 100%; max-height: 100%; }
/* An idle circuit-local layer must be display:none, NOT merely opacity:0. An opacity:0 element
   is still laid out, painted and RASTERISED — so every circuit ever visited kept a full-map
   layer's worth of raster tiles alive in the renderer's native memory (invisible to the JS heap,
   which is why this hid for so long). Only the layer in play should be paintable. */
.ctt-local-layer { display: none; opacity: 0; }
.ctt-local-layer.ctt-hidden { display: none; pointer-events: none; }
/* Paintable only while it is the active layer (or mid-crossfade into being one). */
.ctt-local-layer.ctt-fade-in, .ctt-local-layer.ctt-fading { display: flex; }
.ctt-local-layer.ctt-fade-in { opacity: 1; }
.ctt-fade-out { opacity: 0; transform: scale(1.25); pointer-events: none; }

/* Vertex-morph layer: shown only while interpolating between the national and circuit-local
   projections, so it must not transition anything itself — JS sets `d`/viewBox per frame. */
.ctt-morph-layer { transition: none; pointer-events: none; }
/* Seat-block LABELS must not render inside the morph layer. The labels are <text> inside a
   scaled <g>; while the morph interpolates the viewBox, their net device scale sweeps through
   values where Chrome's compositor text rasterization locks up — the renderer's Compositor
   thread spins at 100% inside one LayerTreeHostImpl::CalculateRenderPasses, indefinitely.
   Reproduced DETERMINISTICALLY with a single no-input drill at viewport widths 680-700px
   (band shifts per circuit viewBox), and it is the core of the "mobile" freeze at 380px under
   pointer input. Bisected: hiding ONLY these labels cures every known freeze config (squares,
   hit-rects, district paths, non-scaling strokes all exonerated). The static layers keep their
   labels; they vanish only for the ~620ms morph. See tests/freeze-hunt.mjs / PROGRESS (y). */
.ctt-morph-layer .ctt-block-label { display: none; }
/* The fade groups' opacity is written straight from JS each frame (see runMorph) and NOT via a
   CSS custom property + calc(). A custom property invalidates style for the whole subtree, and a
   var-driven opacity cannot be handed to the compositor — so the previous version restyled and
   REPAINTED all ~106 paths every frame, which is what churned raster memory. will-change gives
   NO will-change here: promoting each group to its own compositor texture changed stroke
   antialiasing enough to break the pixel-identical handoff (measured ~9k px of hairline colour
   shift). Writing opacity on 4 group nodes already restyles 4 elements instead of ~106.
   Shapes with no circuit-local twin fade out; a circuit's own insets/blocks fade in. */
/* Hard-hidden (not just transparent) so the outgoing layer can't ghost under the morph. */
.ctt-hidden-hard { display: none; }
/* The morph ends exactly on the local geometry, so the swap must be instant — a fade here
   would blink the map to blank and back at the end of every drill-in. */
.ctt-no-transition { transition: none !important; }

/* Circuit territory = uniform fill; district territory = thin borders on top (paint order
   set in JS). Circuit boundaries are then re-drawn as stroke-only clones ON TOP of the
   district lines (.ctt-circuit-outline) so they stay clearly visible (#12). */
.ctt-svg-layer [data-layer="circuit"] { fill: var(--ctt-circuit); stroke: none; }
/* Districts: transparent (NOT none) fill so the whole area is hittable in circuit-local view
   while the circuit shading shows through; thin white cut-lines (#17). */
.ctt-svg-layer [data-layer="district"] { fill: transparent; stroke: #ffffff; stroke-width: 0.9; vector-effect: non-scaling-stroke; }
.ctt-circuit-outline { fill: none; stroke: #5c6775; stroke-width: 2; vector-effect: non-scaling-stroke; pointer-events: none; }
/* Insets are a circuit's sole map presence, so they take the circuit fill + outline colour/weight (#11/#18). */
.ctt-svg-layer [data-inset="true"][data-layer="district"] { fill: var(--ctt-circuit); stroke: #5c6775; stroke-width: 2; }
.ctt-shape { cursor: pointer; }
.ctt-shape-selected { fill: #b9c8e6 !important; }
/* Same "standard blue" as .ctt-shape-selected, but a SEPARATE class: this fires from hovering
   the deployed district-cartogram overlay (a different mouse position than the shape itself),
   and must be independent of whatever court happens to be actually selected right now. */
.ctt-shape-district-hover { fill: #b9c8e6 !important; }
/* National view: mainland districts are visual-only (hover/click fall through to the
   circuit beneath); insets stay interactive so they can be selected (#11). */
.ctt-national-layer [data-layer="district"] { pointer-events: none; }
.ctt-national-layer [data-inset="true"][data-layer="district"] { pointer-events: auto; }
/* District (circuit-local) view: the circuit outline is NOT map-selectable (#13) — use the
   selector-bar circuit repeat; only districts are clickable on the map. */
.ctt-local-layer [data-layer="circuit"] { pointer-events: none; }
.ctt-hover-overlay { fill: none; stroke: var(--ctt-accent); stroke-width: 2.5; vector-effect: non-scaling-stroke; pointer-events: none; visibility: hidden; }

/* Seat blocks: one square per authorized judgeship. POSITION is in map units (glued to the
   court); SIZE is a constant screen px, converted in JS — see BLOCK_PX.
   Annotation only — never eat a click meant for the shape underneath. */
.ctt-blocks { pointer-events: none; }
.ctt-sq { stroke: rgba(255,255,255,0.85); stroke-width: 0.6; vector-effect: non-scaling-stroke; }
.ctt-sq-rep { fill: var(--ctt-rep); }
.ctt-sq-dem { fill: var(--ctt-dem); }
.ctt-sq-other { fill: var(--ctt-other); }
/* Vacancy: an empty seat — pale fill, solid dark outline. Its rect is inset (VACANCY_INSET_PX) so
   this stroke's OUTER edge lands on the party squares' colour edge rather than bulging past it. */
.ctt-sq-vacant { fill: #f0f2f5; stroke: #39414d; stroke-width: 0.8; }

/* Invisible but hittable: `transparent` catches the pointer, `none` would not. Buffered so the
   mouse never slips between squares onto the map and flickers the hover. */
.ctt-block-hit { fill: transparent; pointer-events: all; cursor: pointer; }
/* Ties a block to its shape: rather than a plate behind it (which fought the label), the squares
   and the label take an accent outline — the label's existing white halo becomes accent, so the
   text pops instead of being crossed by a rectangle. Selection outranks hover. */
/* Black, and specific enough to beat .ctt-sq-vacant's own (already dark) stroke — otherwise a
   vacancy square looks unchanged on hover.
   The dash MUST be cleared here: a rect has one stroke, so the vacancy's dasharray simply chops
   the black outline into dots instead of drawing it. An empty seat is still obvious from its pale
   fill, and its dash returns the moment the block is no longer hovered/selected. */
/* Highlight = the squares themselves grow a little, about their own centres. No colour is
   touched, so red/blue and the vacancy's dash survive intact in every state, and it reads on any
   background (grey map or the near-white water under Fed/DC/1st/2nd) because it is motion/size,
   not contrast. transform-box:fill-box makes the origin each rect's own box.
   Cap: pitch/edge = 1.3, at which neighbours' edges TOUCH — stay under it.
   The scale VALUES and the 90ms ease live in JS (BLOCK_SCALE_* in court-tracker.js), applied
   as per-frame inline transforms — NOT a CSS transition. A CSS transform transition here
   cannot composite (SVG rect), and Chrome's promotion attempt at its start/end re-rendered
   hairline strokes MAP-WIDE for the animation window: the operator-reported border
   "flicker/tremble" on every hover. Measured: with the transition, ~14,000 px changed across
   the whole map per hover toggle; JS-driven (plain style writes, no Animation object), ~3,600
   px confined to the hovered court. Do not reintroduce a transition on .ctt-sq transform. */
.ctt-sq { transform-box: fill-box; transform-origin: center; }
/* Sits inside a scaled <g>, so these lengths are in LABEL_EM units, not screen px. */
.ctt-block-label {
  fill: var(--ctt-ink); text-anchor: start; font-weight: 700;
  paint-order: stroke; stroke: rgba(255,255,255,0.92); stroke-width: 0.9; stroke-linejoin: round;
}

/* map tooltip (cursor-following) */
.ctt-tooltip {
  position: fixed; z-index: 2147483000; pointer-events: none; background: #1b1f24; color: #fff;
  font-size: 12px; padding: 4px 8px; border-radius: 5px; visibility: hidden; white-space: nowrap;
}

/* info pane */
.ctt-pane {
  position: absolute; left: 0; right: 0; top: 0; height: 100%;
  background: var(--ctt-panel); border-bottom: 1px solid var(--ctt-line);
  box-shadow: 0 8px 24px rgba(0,0,0,0.12); transform: translateY(-100%);
  transition: transform 260ms ease; display: flex; flex-direction: column;
}
/* .ctt-pane--tall is INERT since the pane went full-height (operator call, 2026-07-17:
   the pane covered most of the map anyway, and the docked-detail layout needed the room).
   The class is still applied per drill-in context by JS and asserted in browser-checks;
   it simply no longer needs extra height. */
.ctt-pane.ctt-pane--tall { height: 100%; }
.ctt-pane.ctt-is-open { transform: translateY(0); }
.ctt-pane-body { flex: 1 1 auto; overflow-y: auto; padding: 12px 16px 12px; display: flex; flex-direction: column; }
.ctt-pane-body > * { flex: 0 0 auto; }
/* The stage row absorbs the pane's leftover height, so the docked detail panel's top AND
   bottom are identical in timeline and majority modes (it must not appear to move). */
.ctt-pane-body > .ctt-stage-row { flex: 1 0 auto; }
.ctt-pane-stow {
  position: absolute; bottom: -1px; left: 50%; transform: translate(-50%, 100%);
  border: 1px solid var(--ctt-line); border-top: 0; border-radius: 0 0 8px 8px;
  background: var(--ctt-panel); cursor: pointer; padding: 3px 16px; font-size: 13px; line-height: 1;
}
.ctt-pane-close {
  position: absolute; top: 8px; right: 10px; z-index: 6;
  border: 1px solid #b9c1cc; background: var(--ctt-panel); color: var(--ctt-ink);
  cursor: pointer; width: 32px; height: 32px; border-radius: 50%; font-size: 20px; line-height: 1;
  box-shadow: 0 1px 4px rgba(0,0,0,0.10);
}
.ctt-pane-close:hover { background: var(--ctt-map); border-color: var(--ctt-accent); color: var(--ctt-accent); }
/* Stow-only (▲): sits left of the ×; same round style, slightly smaller glyph. */
.ctt-pane-stowtop { right: 50px; font-size: 14px; }

.ctt-pane-head { margin-bottom: 8px; display: flex; align-items: flex-start; gap: 10px; padding-right: 84px; }
.ctt-pane-headtext { flex: 1 1 auto; min-width: 0; }
.ctt-pane-title { font-size: 16px; font-weight: 650; line-height: 1.25; }
.ctt-pane-meta { font-size: 12px; color: var(--ctt-muted); margin-top: 2px; }
/* Summary > District's national totals can look like they don't add up (active+vacant >
   authorized) — a handful of courts genuinely seat more active judges than authorized via
   shared/roving judgeships. This single-character marker (native title tooltip explains why)
   is deliberately just one character so it can't threaten the caption's pixel-parity with
   Summary > SCOTUS's own meta line (see .ctt-district-caption-row's own comment in the JS). */
.ctt-district-overage-note { color: var(--ctt-accent); font-weight: 700; cursor: help; }
/* Circuit Justice chip slot in the header (timeline view) */
.ctt-justice-slot { flex: 0 0 auto; }
.ctt-judge.ctt-justice-inhead { position: static; width: auto; display: flex; align-items: center; gap: 6px; }
.ctt-judge.ctt-justice-inhead .ctt-avatar { width: 34px; height: 34px; margin: 0; font-size: 12px; }
.ctt-judge.ctt-justice-inhead .ctt-judge-label { margin: 0; text-align: left; max-width: 120px; }

/* flex-wrap-REVERSE (not plain wrap) is the fix for an operator-reported bug: as the pane
   narrows, .ctt-drill ("View districts →") is LAST in DOM/flex order, so under plain wrap it
   lands on the LAST (bottom-most) wrapped line — stacking it below Timeline|Majority and the
   Mark switch, with the separate Seniors switch (.ctt-foldrow, its own element below this one)
   further below still. wrap-reverse keeps each line's own INTERNAL (horizontal) order untouched
   — so a single unwrapped row still reads left-to-right exactly as before, .ctt-drill's own
   margin-left:auto still pushing it to the right of that one row — but REVERSES which line ends
   up on top when there's more than one: the line .ctt-drill wraps onto alone (last in flow) now
   renders FIRST (top-most), since it's the "more important" navigational control (operator ask)
   and should float above the mode/affiliation switches rather than get buried under them. */
.ctt-pane-controls { display: flex; gap: 8px; align-items: center; margin: 6px 0; flex-wrap: wrap-reverse; }
/* Summary > District's caption (title + summary meta line, operator ask 2026-09-08) needs to
   start at EXACTLY the same position as Summary > SCOTUS's own subtitle+meta chain
   (renderSummaryScotus) so the two docked panels measure pixel-identical top-to-bottom (operator
   report, 2026-09-09 — an earlier version nested the caption inside .ctt-pane-controls, whose
   own margin pushed it down 6px and broke that parity).
   flex-wrap (issue #70 follow-up, operator report) is what fixes the narrow-width bug a plain
   position:absolute overlay had: that kept the zoom controls pinned at a fixed width regardless
   of how little room was left, squeezing the title into an increasingly narrow multi-line column
   instead of just dropping the controls to their own line. Now a genuine flex row: when both
   .ctt-district-caption-text (below) and the zoom controls' own fixed width don't fit side by
   side, the controls wrap to a fresh line and the caption text reclaims the full row width. */
.ctt-district-caption-row { display: flex; flex-wrap: wrap; align-items: center; column-gap: 12px; row-gap: 6px; }
/* flex-direction:column (not the .ctt-pane-controls row it used to share) matters here:
   .ctt-summary-subtitle/.ctt-pane-meta's own adjacent margins only stay non-collapsing (4px gap,
   matching SCOTUS) as flex children — in plain block flow they'd collapse to 2px. No padding/
   margin/border of its own, so nesting the title one level deeper here doesn't move its own
   top-left position at all (the pixel-parity regression test's descendant selector still finds it
   fine regardless of nesting depth). min-width is the actual WRAP TRIGGER for the row above: once
   the row can't fit this much room AND the zoom controls' fixed width together, wrapping kicks in. */
.ctt-district-caption-text { flex: 1 1 300px; min-width: 300px; display: flex; flex-direction: column; }
/* flex:0 0 auto (natural width, not a forced fixed box) — three buttons need real room for their
   own readable labels ("Reset"/"Zoom Out"/"Zoom In"), and squeezing them into the old single
   deploy button's exact 232px (tried first — see git history) forced ellipsis-truncated text or
   made the WHOLE group balloon past 232px anyway (a flex item's automatic min-content size can
   override a smaller flex-basis when its content can't shrink further, a real regression caught
   by this file's own alignment test). Still reads as "sharing the same slot" the removed deploy
   button occupied (operator ask, issue #70 follow-up) because captionText's flex-grow eats all
   the space to its LEFT — this group still lands flush against the row's own right edge, same as
   the docked detail panel below does in its own row, just without being forced to its EXACT
   232px width too. */
.ctt-district-zoom-controls { flex: 0 0 auto; display: flex; gap: 6px; }
.ctt-district-zoom-btn { flex: 0 0 auto; text-align: center; padding-left: 10px; padding-right: 10px; white-space: nowrap; }
/* Deliberately smaller/narrower than the +/- buttons (operator ask) — a plain reset action reads
   fine as the smallest of the three. */
.ctt-district-zoom-reset-btn { flex: 0 0 auto; padding-left: 8px; padding-right: 8px; }
/* "hit the limit" feedback (issue #70 spec) — always in sync with live zoom state (see
   updateDistrictZoomButtonStyles), so it reverts the instant zoom moves off that limit, rather
   than being a one-off flash on the wasted click. */
.ctt-district-zoom-btn-limit { opacity: 0.4; cursor: default; }
.ctt-toggle, .ctt-drill {
  border: 1px solid var(--ctt-line); background: var(--ctt-panel); color: inherit; font: inherit;
  font-size: 13px; padding: 5px 12px; border-radius: 6px; cursor: pointer;
}
.ctt-toggle.ctt-is-active { background: var(--ctt-accent); color: #fff; border-color: var(--ctt-accent); }
.ctt-drill { margin-left: auto; color: var(--ctt-accent); font-weight: 600; }
.ctt-foldrow { display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--ctt-muted); margin: 2px 0; }
/* margin-top:auto (not a fixed 6px) pushes the note flush to the BOTTOM of .ctt-stage-main's now-
   stretched column (see that rule's own comment) instead of sitting wherever the stage's own
   natural content height happens to end — this is what was missing in Timeline mode specifically
   (operator report, 2026-09-11): Majority mode's stage height is already explicitly computed to
   nearly fill the available row (majorityStageHeight()), so its note already landed close to the
   bottom by construction; Timeline's stage height (timelineStageHeight()) is sized purely from
   row count, with no such compensation, leaving a gap below a short court's note. Reverts to a
   fixed 6px on mobile (see media query) where .ctt-stage-row stops being a flex row at all, so
   there's no stretched column height for auto to push against. */
.ctt-note { font-size: 11px; color: var(--ctt-muted); margin-top: auto; }
.ctt-majority-note, .ctt-always-note { font-style: italic; }

/* judge stage + icons */
.ctt-judge-stage { position: relative; width: 100%; height: 220px; margin-top: 4px; }
/* Majority view only (issue #50 follow-up): let the arc scroll horizontally instead of reshaping
   itself to always fit. overflow-y can't stay "visible" once overflow-x isn't (the UA computes
   the "visible" axis to "auto" too when the other axis is set) — "hidden" here is deliberate, not
   the forced default, since layoutJudges() already sizes .ctt-judge-stage's height to fit the
   Majority arc's own content (now including the senior band, properly Rmax-bounded) with no
   vertical scroll intended. */
.ctt-judge-stage.ctt-majority-scroll { overflow-x: auto; overflow-y: hidden; }
.ctt-judge {
  position: absolute; top: 0; left: 0; width: 52px; text-align: center;
  transition: transform 480ms cubic-bezier(.4,.1,.2,1), opacity 300ms ease;
}
.ctt-avatar {
  position: relative; z-index: 1;
  width: 44px; height: 44px; margin: 0 auto; border-radius: 50%; border: 3px solid var(--ctt-other);
  display: flex; align-items: center; justify-content: center; overflow: hidden;
  font-size: 14px; font-weight: 600; background: #eef1f5; color: #333;
}
.ctt-avatar.ctt-rep { border-color: var(--ctt-rep); }
.ctt-avatar.ctt-dem { border-color: var(--ctt-dem); }
.ctt-avatar.ctt-other { border-color: var(--ctt-other); }
.ctt-photo { width: 100%; height: 100%; object-fit: cover; }
/* Reported-affiliation marker: the party band goes RIDGED, keeping its red/blue reading while
   the same-president yellow highlight (.ctt-hl, a sibling underneath) still shows through.
   Border width must NOT change: the avatar is border-box, so a thicker border eats the content
   box and visibly shrinks the photo inside. Style only. */
.ctt-affil-marked .ctt-avatar { border-style: dashed; }
.ctt-affil-switch { display: inline-flex; align-items: center; gap: 4px; margin-left: 4px; }
.ctt-affil-switch-label { font-size: 11px; color: var(--ctt-muted); margin-right: 2px; }
/* Segmented: the options read as one control rather than separate buttons. Shared by the
   None|FedSoc|ACS switch and the Timeline|Majority mode switch. */
.ctt-mode-switch { display: inline-flex; }
.ctt-affil-opt, .ctt-mode-opt { border-radius: 0; margin-left: -1px; }
.ctt-affil-opt:first-of-type, .ctt-mode-opt:first-of-type { border-radius: 6px 0 0 6px; margin-left: 0; }
.ctt-affil-opt:last-of-type, .ctt-mode-opt:last-of-type { border-radius: 0 6px 6px 0; }
/* Summary pane: a larger-than-usual SCOTUS|Appellate|District switch (operator ask) — same
   segmented-control shape as .ctt-mode-switch, sized up. */
/* Issue #50 bug 2 (operator ask, 2026-09-08: applies at every width, not just the mobile
   breakpoint below): the selector's OWN top offset from the pane's top edge is fixed
   (.ctt-pane-body's 12px top padding + this element's own margin-top) regardless of viewport
   width — it visibly collides with the close (×)/stow (▲) circles once a tab's TEXT (not just its
   padded button box — the operator's own spec explicitly tolerates box/padding overlap, only
   glyph pixels touching the button is the real bug) reaches down far enough to meet the circles'
   fixed vertical range (.ctt-pane-close: top:8px, 32px diameter, so its bottom edge sits at a
   fixed 40px from the pane's top). The operator's own "align the selector's top with the button's
   vertical center" spec (~+8px from a pure box-center calculation) was the right DIRECTION but
   not enough MARGIN once actually measured in a real browser: a tab that wraps to 2 lines centers
   that text within its own (much taller) box, so an 8px shift of the box's top doesn't move a
   CENTERED text block down by the same 8px. Tuned empirically instead (a fixed calculation would
   need the exact wrapped-line-count/box-height relationship, which itself varies by width) to
   +14px over the original 4px margin-top, verified with a several-px clearance margin across the
   full width range — 380/480/600/900/1180px (both wrapped-2-line mobile tabs and single-line
   desktop ones) — tests/browser-checks.mjs. Taken half from the existing bottom margin (14px ->
   4px) and half as real net growth: `.ctt-pane-body > .ctt-summary-content`'s own
   `flex: 1 1 auto` (see that rule's comment) already shrinks to absorb whatever height this
   selector ends up needing, and since the selector is SHARED by all three Summary sub-tabs,
   growing it shifts SCOTUS's and District's content down by the identical amount — their
   pixel-parity with each other (session (cb)/(cc)/(cl) — see PROGRESS.md) is unaffected. */
.ctt-summary-switch { margin: 18px 0 4px; width: 100%; }
.ctt-summary-switch .ctt-mode-opt {
  font-size: 17px; font-weight: 650; padding: 14px 20px; flex: 1 1 0;
}
/* Standardizes the docked-panel preview height across ALL Summary sub-tabs AND a regular
   circuit/district court's own pane (operator ask, 2026-09-05: District's dock fell short of
   the bottom, unlike SCOTUS's and an ordinary court pane's). Those other two docks reach the
   bottom because their STAGE gets an explicit JS-computed height filling available room
   (majorityStageHeight()) and .ctt-detail's own align-self:stretch matches it — but that
   technique only works because .ctt-pane-body's DIRECT children get `flex:1 0 auto` (see the
   .ctt-pane-body > .ctt-stage-row rule above). Summary's content sits ONE level deeper
   (.ctt-pane-body > .ctt-summary-content > .ctt-stage-row/.ctt-district-layout), which that
   selector never reached — propagate the same "fill leftover height" flex down through both
   levels instead of just the outer one.
   flex-SHRINK is 1 here (not 0, unlike the pre-existing .ctt-pane-body > .ctt-stage-row rule
   above, which can safely stay flex-shrink:0 because ITS stage height is already pre-clamped
   in JS via majorityStageHeight()): a large circuit's table (e.g. the 9th's 15 districts) has
   no such JS-computed ceiling, and flex-shrink:0 let it grow past available room and made the
   whole PANE scroll instead of the table's own inner .ctt-district-table-wrap — visible as the
   docked panel "growing downward" and pushing the rest of the layout around (operator report,
   2026-09-05). flex-shrink:1 lets these rows compress to their allotted space instead, which is
   what actually lets min-height:0 + the table wrap's own overflow-y:auto take over. */
.ctt-pane-body > .ctt-summary-content { flex: 1 1 auto; display: flex; flex-direction: column; min-height: 0; }
/* position:relative anchors Summary > Supreme Court's own FedSoc key (operator ask, 2026-09-11) —
   a plain flex property otherwise, doesn't affect layout on its own. */
.ctt-summary-content { min-height: 200px; position: relative; }
/* Centerward-but-clear-of-the-text placement in the SAME header band as the title/meta text above
   it (issue #69 — a plain right:0 blended into the panel edge and was easy to miss). `left` is
   set by positionScotusAffilKey() in JS, since keeping a real buffer from the title/meta text
   while still favoring the horizontal center needs their actual measured width, not a fixed CSS
   value; this rule only supplies the vertical position and the position:absolute the JS positioning
   requires. */
.ctt-scotus-affil-key {
  position: absolute; top: 8px; display: flex; align-items: center; gap: 6px;
  font-size: 12px; color: var(--ctt-muted);
}
/* Same dashed-ring convention .ctt-affil-marked .ctt-avatar itself uses, at swatch size — a
   neutral ink color since the real ring's color varies by appointing party, which isn't the point
   of this legend. */
.ctt-scotus-affil-key-swatch {
  width: 16px; height: 16px; border-radius: 50%; border: 2px dashed var(--ctt-ink); flex: 0 0 auto;
}
.ctt-summary-content > .ctt-stage-row { flex: 1 1 auto; min-height: 0; }
.ctt-summary-subtitle { font-size: 15px; font-weight: 650; margin-bottom: 2px; }
.ctt-summary-placeholder { color: var(--ctt-muted); font-size: 13px; padding: 24px 0; }
/* Summary > District: a static preview of the cartogram (no map deployment yet — see
   PROGRESS.md). Reuses the map's own seat-block palette classes directly — .ctt-sq-rep/
   .ctt-sq-dem/.ctt-sq-other/.ctt-sq-vacant, defined above for renderSeatBlocks() — rather than
   inventing a second palette that could drift from it. */
/* Cartogram + its docked per-district detail panel, side by side — same row shape as
   .ctt-stage-row (bench + judge detail), stacking on mobile via the same media query.
   flex:1 1 auto here is the SAME fix as .ctt-summary-content above, one level deeper. */
.ctt-district-layout { display: flex; align-items: flex-start; gap: 14px; flex: 1 1 auto; min-height: 0; }
/* align-self:stretch (not the row's own flex-start) + a column flex with justify-content:center
   is what actually centers the cartogram vertically (operator ask) — without stretch, the wrap
   would only ever be as tall as the SVG itself, leaving no extra space to center within. */
/* overflow:hidden turns this into the FIXED VIEWING AREA issue #70's zoom feature clips into —
   the SVG's own scale()/translate() transform (applied in JS, see applyDistrictZoomTransform) is
   purely visual and never affects this box's own layout size, so it clips exactly the geometry
   that's actually panned/zoomed past the edge, nothing more. touch-action:none + user-select:none
   stop a drag-to-pan gesture from also scrolling the page or selecting text underneath it. */
.ctt-district-cartogram-wrap {
  flex: 1 1 auto; min-width: 0; align-self: stretch;
  display: flex; flex-direction: column; justify-content: center; min-height: 0;
  overflow: hidden; touch-action: none; user-select: none;
}
.ctt-district-cartogram-wrap.ctt-zoomed { cursor: grab; }
.ctt-district-cartogram-wrap.ctt-dragging { cursor: grabbing; }
.ctt-district-cartogram { width: 100%; height: auto; max-height: 60vh; display: block; flex: 0 0 auto; }
/* align-self:stretch + min-height:0 for the same reason as .ctt-district-cartogram-wrap above:
   .ctt-district-layout's own align-items:flex-start means a row flex child otherwise just hugs
   its content height — for a large circuit's table (e.g. the 9th's 15 rows) that meant this box
   grew to fit ALL rows rather than being capped to the row's actual available height, so the
   overflow never reached .ctt-district-table-wrap's own overflow-y:auto at all and instead spilled
   out through .ctt-pane-body's ancestor scrollbar (operator-reported bug, 2026-09-06). */
.ctt-district-detail { flex: 0 0 232px; display: flex; flex-direction: column; align-self: stretch; min-height: 0; }
/* .ctt-detail-content is a plain block div everywhere else it's used (the shared judge-detail
   panel) — .ctt-district-table-wrap's own flex:1 1 auto/min-height:0 (below) do NOTHING unless
   their parent is itself a flex container, so without this scoped override the table-wrap just
   rendered at its full natural content height and spilled out past .ctt-district-detail's now-
   bounded box (found chasing the SAME bug report as the align-self fix above, 2026-09-06) — the
   base .ctt-detail-content rule is left untouched so the judge-detail panel is unaffected. */
.ctt-district-detail > .ctt-detail-content { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; }
.ctt-district-detail > .ctt-detail-content > * { flex: 0 0 auto; }
.ctt-district-jump { display: block; width: 100%; margin-top: 8px; text-align: center; }
/* The circuit-wide R/D/vacant table (operator ask, 2026-09-05) — same visual family as
   tools/district-block-builder.html's own District linker table: no header row, abbreviated
   names, a scrollable body so a large circuit (e.g. the 9th's 15 districts) clips instead of
   overflowing the docked panel. */
/* No top margin: the table is the FIRST element in the docked panel now (operator ask,
   2026-09-06 — table on top, name + Jump button anchored at the bottom below it). */
.ctt-district-table-label { font-size: 13px; font-weight: 700; margin: 0 0 4px; }
/* Scoped (not the bare class) so this outranks the sibling-default ".ctt-district-detail >
   .ctt-detail-content > *" rule above by specificity, same technique .ctt-pane-body > .ctt-stage-row
   already uses to outrank .ctt-pane-body > *. */
.ctt-district-detail > .ctt-detail-content > .ctt-district-table-wrap { flex: 1 1 auto; min-height: 0; overflow-y: auto; }
.ctt-district-table { width: 100%; border-collapse: collapse; font-size: 12px; }
.ctt-district-table td { padding: 3px 4px; border-bottom: 1px solid var(--ctt-line); white-space: nowrap; }
.ctt-district-table td:first-child { width: 100%; white-space: normal; }
/* Clicking a district's own row pins it, same as clicking its cartogram block (operator ask,
   2026-09-10) — cursor:pointer signals that; the Total row (a different class entirely) is
   deliberately excluded since it doesn't correspond to any one district. */
.ctt-district-row { cursor: pointer; }
/* The currently shown district's row — reordered to the top in JS (not CSS, see
   renderDistrictCircuitTable), bolded and highlighted. SAME treatment whether it got there via
   a sticky hover or an explicit pin (operator correction, 2026-09-05) — no separate lighter
   "just hovering" style anymore. */
.ctt-district-row-active { background: #eaf1fd; font-weight: 700; }
/* Circuit-wide Total row (operator ask, 2026-09-07) — sits right below the pinned/hovered row.
   Bolded text + heavier (2px, not the ordinary 1px --ctt-line) rules flanking it above AND below
   so it visually reads as a distinct summary rather than just another row in the list;
   border-collapse (see .ctt-district-table) merges each of those with its neighbor's own border
   into one line, so this alone is enough — no separate rule needed on the rows next to it. */
.ctt-district-row-total td { font-weight: 700; border-top: 2px solid var(--ctt-ink); border-bottom: 2px solid var(--ctt-ink); }
/* Count comes BEFORE the swatch (operator preference — reversed from the block-builder tool's
   own swatch-then-count order). Square, not the circular .ctt-dot used elsewhere in this file —
   operator specifically asked for the block-builder tool's SQUARE swatch style. */
.ctt-district-swatch { display: inline-block; width: 9px; height: 9px; border-radius: 2px; margin-left: 5px; vertical-align: -1px; }
.ctt-district-swatch-rep { background: var(--ctt-rep); }
.ctt-district-swatch-dem { background: var(--ctt-dem); }
.ctt-district-swatch-vacant { background: #f0f2f5; border: 1px solid #39414d; }

/* Circuit-drill-in fixed sub-assembly (operator spec): that circuit's own districts only,
   present regardless of the national deployment state. Anchored bottom-left of the local map
   (moved from top-left, operator ask 2026-09-07) — deliberately not draggable/resizable (unlike
   the deployed national assembly, this is a fixed reference view tied to the circuit you're
   looking at, not a moved/customized object). */
.ctt-district-subassembly {
  position: absolute; bottom: 12px; left: 12px; width: 130px; z-index: 3;
  filter: drop-shadow(0 2px 6px rgba(0,0,0,0.18)); pointer-events: none;
}
.ctt-district-subassembly .ctt-district-cartogram { width: 100%; height: auto; display: block; }
.ctt-district-subassembly .ctt-district-sq { pointer-events: auto; }
/* Scale is applied via per-frame INLINE transforms in JS (animateDistrictSquare), NOT a CSS
   transition — same rule as .ctt-sq above and for the same reason (a transform transition on
   an SVG rect can't composite and re-rasterizes every frame, seen here as a brief blur on
   hover — operator report). Do not add a `transition` property back. */
.ctt-district-sq { transform-box: fill-box; transform-origin: center; }
/* Growing same-district cells via transform:scale() around each rect's own center closes the
   cartogram's built-in gutter almost exactly (DISTRICT_SQ_SCALE_HOVER is tuned so grown cells
   overlap by a fraction of a viewBox unit) - but that margin is a fixed VIEWBOX-unit amount, so
   it shrinks right along with everything else once the cartogram itself renders smaller (the
   on-map deployed overlay's default ~280px width for the whole country, the drill-in
   sub-assembly's 130px, or just a narrower host page) - at small enough scale it stops being
   enough to fully cover the seam, showing as a hairline "faint light trace" of the page
   background between cells that should read as one solid block (operator report, 2026-09-06).
   A non-scaling stroke matching the cell's own fill pads the shape by a FIXED SCREEN-PIXEL
   amount instead, regardless of the cartogram's current zoom - the same vector-effect technique
   CLAUDE.md already documents for exactly this "must stay a constant width on screen" need. */
.ctt-district-sq-grown.ctt-sq-rep { stroke: var(--ctt-rep); }
.ctt-district-sq-grown.ctt-sq-dem { stroke: var(--ctt-dem); }
.ctt-district-sq-grown.ctt-sq-other { stroke: var(--ctt-other); }
.ctt-district-sq-grown.ctt-sq-rep, .ctt-district-sq-grown.ctt-sq-dem, .ctt-district-sq-grown.ctt-sq-other {
  stroke-width: 1.5px; vector-effect: non-scaling-stroke;
}
/* Seniors are only PARTIALLY desaturated so the appointing party is still readable (#5). */
.ctt-senior .ctt-avatar { filter: saturate(0.4); opacity: 0.82; }
.ctt-vacant .ctt-avatar-vacant {
  border: 2px dashed var(--ctt-other); background: repeating-linear-gradient(45deg,#f0f2f5,#f0f2f5 4px,#e6e9ee 4px,#e6e9ee 8px);
  color: transparent;
}
/* width:fit-content (not the plain-block default, which always fills the 52px parent) — a block
   that fills its container still wraps normally for text that fits, but a single unbreakable
   word WIDER than the container (e.g. a long surname) then overflows the fixed-width box
   asymmetrically: text-align:center can't apply negative left offset within a box narrower than
   its own content, so Chrome anchors the overflowing line's start at the box's left edge and lets
   it spill only rightward — confirmed directly (issue #50 follow-up): a 56px-wide unbreakable
   line inside the 52px box rendered flush-left (0px left overflow, ~4px right overflow) instead
   of the ~2px/~2px it would need to be genuinely centered. That asymmetry both looks off-centre
   and quietly breaks the collision math below, which measures label extent via a Range around
   the rendered text and then assumes it's symmetric around the icon's x (labelHitsIcon's
   `rect.left = aPos.x - w/2`) — a real mismatch, not just cosmetic. fit-content shrinks the box
   to its own content (still capped/wrapped within the 52px available width for anything that
   fits), and only grows PAST that width for genuinely unbreakable overflow — where margin:auto
   then centers the wider box normally, so the overflow (and the Range measurement of it) is
   symmetric again. */
.ctt-judge-label { font-size: 10px; line-height: 1.15; margin-top: 3px; color: var(--ctt-ink); max-width: 60px; width: -webkit-fit-content; width: fit-content; margin-left: auto; margin-right: auto; }
.ctt-senior .ctt-judge-label { color: var(--ctt-muted); }
.ctt-chief-badge {
  position: absolute; top: -2px; right: 4px; z-index: 2; color: #e0a400; font-size: 14px;
  /* Yellow star -> thin BLACK outline -> WHITE outline wrapping it (operator ask), so the
     star separates from both light and dark backgrounds. Stroke draws the crisp black ring;
     the shadow stack layers a black ring (listed first = on top) inside a white one. */
  -webkit-text-stroke: 0.6px rgba(0, 0, 0, 0.9);
  text-shadow:
    -1px 0 0 rgba(0,0,0,0.7), 1px 0 0 rgba(0,0,0,0.7),
    0 -1px 0 rgba(0,0,0,0.7), 0 1px 0 rgba(0,0,0,0.7),
    -2px 0 0 #fff, 2px 0 0 #fff, 0 -2px 0 #fff, 0 2px 0 #fff,
    -1.5px -1.5px 0 #fff, 1.5px -1.5px 0 #fff, -1.5px 1.5px 0 #fff, 1.5px 1.5px 0 #fff;
}
/* Same-president highlight ring: a sibling of the avatar so the senior grayscale filter
   never touches its yellow (#5). Sits directly over the 44px avatar. */
.ctt-hl {
  position: absolute; top: 0; left: 50%; transform: translateX(-50%);
  width: 44px; height: 44px; border-radius: 50%; z-index: 2; pointer-events: none;
  box-shadow: 0 0 0 3px #ffcf33; opacity: 0; transition: opacity 120ms ease;
}
.ctt-copresident .ctt-hl { opacity: 1; }
.ctt-justice .ctt-avatar { border-color: #7a52c9; }
.ctt-justice .ctt-judge-label { color: #7a52c9; font-weight: 600; }

/* majority overlay */
.ctt-majority-overlay { position: absolute; inset: 0; pointer-events: none; width: 100%; height: 100%; overflow: visible; }
/* Operator ask: half as prominent as it was (opacity 1 -> 0.5) — the dotted majority-line
   divider was competing too much with the icons/labels around it. */
.ctt-majority-line { stroke: #444; stroke-width: 1.5; stroke-dasharray: 4 4; opacity: 0.5; }
.ctt-majority-count { fill: var(--ctt-ink); font-size: 13px; font-weight: 600; font-family: inherit; }
/* Reads a step darker than the panel rules, but stays clearly subordinate to the #444
   majority line so the two dotted strokes don't compete. */
.ctt-arc-guide { fill: none; stroke: #a9b2bd; stroke-width: 1; stroke-dasharray: 2 5; }

/* judge-detail panel. On HOVER it is pointer-events:none so it never blocks hovering the
   icons beneath it (kills the flicker in #8). On CLICK it becomes "pinned" (.ctt-pinned):
   fully interactable so text can be selected and the CourtListener link clicked (#20). */
/* Stage row: the judge stage (+ its notes) on the left, the DOCKED detail panel on the
   right. Replaces the old floating corner-snapping tooltip, which (a) hopped corners
   whenever the cursor crossed the pane's midlines and (b) always covered some judges or
   their same-president outlines wherever it sat. The detail panel now has its own
   dedicated, uninterrupted column; hover updates it in place, click pins it. On mobile the
   row stacks (see the media query): stage, then notes, then detail below them. */
.ctt-stage-row { display: flex; align-items: flex-start; gap: 14px; }
/* align-self:stretch (matching .ctt-detail's own, below) + a column flex so a note that follows
   the judge stage can be pushed to the BOTTOM of the row's full height via margin-top:auto (see
   .ctt-note) instead of sitting immediately after wherever the stage's own natural height ends —
   operator report, 2026-09-11 (a territorial court's always-visible note wasn't bottom-aligned in
   Timeline mode, unlike Majority mode). Without stretch, .ctt-stage-main (like
   .ctt-district-cartogram-wrap before its own equivalent fix) would only ever be as tall as its
   own content, leaving no room for auto to push into. */
.ctt-stage-main { flex: 1 1 auto; min-width: 0; align-self: stretch; display: flex; flex-direction: column; }
.ctt-detail {
  flex: 0 0 232px; width: 232px; align-self: stretch; position: relative;
  background: var(--ctt-panel); border: 1px solid var(--ctt-line); border-radius: 8px;
  padding: 10px 12px; font-size: 12px; line-height: 1.45;
}
.ctt-detail.ctt-pinned { border-color: var(--ctt-accent); box-shadow: 0 2px 10px rgba(0,0,0,0.10); }
.ctt-detail-close { display: none; position: absolute; top: 4px; right: 6px; border: 0; background: transparent; color: var(--ctt-muted); font-size: 16px; line-height: 1; cursor: pointer; }
.ctt-detail.ctt-pinned .ctt-detail-close { display: block; }
.ctt-detail > div, .ctt-detail-content > div { margin: 2px 0; }
/* Large avatar at the top of the docked panel — same party ring / senior tint / initials
   fallback semantics as the stage icons, just bigger. */
.ctt-detail-photo {
  width: 84px; height: 84px; border-radius: 50%; margin: 2px auto 8px; overflow: hidden;
  border: 3px solid var(--ctt-other); background: #e8ebf0; display: flex;
  align-items: center; justify-content: center; font-size: 24px; font-weight: 650; color: #5c6775;
}
.ctt-detail-photo img { width: 100%; height: 100%; object-fit: cover; display: block; }
.ctt-detail-photo.ctt-rep { border-color: var(--ctt-rep); }
.ctt-detail-photo.ctt-dem { border-color: var(--ctt-dem); }
.ctt-detail-photo.ctt-detail-senior { filter: saturate(0.4); opacity: 0.82; }
.ctt-detail-hint { color: var(--ctt-muted); font-style: italic; text-align: center; padding: 18px 4px; }
/* .ctt-detail-content > div's generic 2px top margin (see that rule below) COLLAPSES with
   .ctt-detail-content's OWN 2px margin-top in the shared judge-detail panel (plain block flow),
   landing the hint flush at content's own top edge — but .ctt-district-detail's own
   .ctt-detail-content is display:flex (needed for the table/name/button layout elsewhere in this
   same panel), and flex containers never collapse margins with their children, so the SAME hint
   sat 2px lower there instead (operator report, 2026-09-10: SCOTUS is the standard, District
   should match it). Cancelling the hint's own top margin in this one context reproduces the
   collapsed (flush) position exactly. */
.ctt-district-detail .ctt-detail-hint { margin-top: 0; }
.ctt-detail-name { font-size: 14px; font-weight: 650; margin-bottom: 4px; }
/* Spacing above the name now that the table sits above it (operator ask, 2026-09-06) — scoped to
   the district panel only, so the shared judge-detail panel (name is its first element) is unaffected. */
.ctt-district-detail > .ctt-detail-content > .ctt-detail-name { margin-top: 8px; }
/* Reserve room for the WORST CASE line count ("U.S. District Court for the ..." wraps to 2 or 3
   lines here, in the Summary > District docked tooltip) so the Jump-to-court button below it
   doesn't shift position depending on which district is currently shown (operator ask,
   2026-09-07; corrected 2026-09-09 to apply HERE — the Summary docked tooltip's own name label —
   rather than to every ordinary court pane's .ctt-pane-title, which was never the intent and
   regressed CFC's own pane into an unwanted scrollbar). A shorter, 2-line name simply carries
   unused space below it rather than shrinking the box; the name stays top-aligned (plain block
   flow, nothing centers it) so it always occupies the TOP lines of the reserved space, and the
   table above it (flex:1 1 auto) is what actually gives up the room. Mobile's 1/2-line range
   gets its own reservation in the mobile media query below. */
.ctt-district-detail .ctt-detail-name { min-height: 3.75em; line-height: 1.25; }
.ctt-detail a { color: var(--ctt-accent); }
.ctt-affil { font-style: italic; color: var(--ctt-muted); }
/* Required attribution for CC-licensed portraits — present but visually subordinate. */
.ctt-photo-credit { margin-top: 4px; font-size: 10px; color: var(--ctt-muted); }
.ctt-tag { font-size: 10px; font-weight: 600; background: #eef1f5; color: var(--ctt-muted); border-radius: 4px; padding: 1px 5px; vertical-align: middle; }
.ctt-tag-senior { background: #e6e9ee; }
.ctt-tag-holdover { background: #fbe8c8; color: #6b4a08; }
.ctt-dot { display: inline-block; width: 9px; height: 9px; border-radius: 50%; margin-right: 5px; background: var(--ctt-other); }
.ctt-dot.ctt-rep { background: var(--ctt-rep); }
.ctt-dot.ctt-dem { background: var(--ctt-dem); }

.ctt-status { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; text-align: center; color: var(--ctt-muted); font-size: 13px; padding: 16px; }

/* mobile: selector stacks above map; pane becomes a full-width sheet */
@media (max-width: 640px) {
  .ctt-root { height: auto; min-height: 560px; }
  .ctt-body { flex-direction: column; }
  /* Selector expands to show every option — no tiny inner scrollbar (#10a). */
  .ctt-selector {
    flex: 0 0 auto; overflow: visible; border-right: 0; border-bottom: 1px solid var(--ctt-line);
    display: flex; flex-wrap: wrap; gap: 4px;
  }
  .ctt-selector-group-label { flex-basis: 100%; }
  .ctt-selector-item { width: auto; }
  .ctt-selector-back { flex-basis: 100%; }
  .ctt-map-viewport { min-height: 475px; }   /* ~25% taller so the info pane has more room (#23) */
  .ctt-pane { height: 100%; }
  /* The docked detail panel stacks BELOW the stage and its notes (DOM order does this for
     free once the row stops being a row) — no more bottom-sheet overlay, no reserved
     padding: the panel is in normal flow and can never cover the bench. */
  .ctt-stage-row { display: block; }
  .ctt-note { margin-top: 6px; }   /* .ctt-stage-row stops being a flex row here, so there's no stretched column height left for margin-top:auto to push against */
  .ctt-detail { width: auto; margin-top: 10px; }
  .ctt-district-layout { display: block; }
  .ctt-district-detail .ctt-detail-name { min-height: 2.5em; }   /* mobile: 1-2 lines instead of desktop's 2-3 */
  /* Search bar drops to its own full-width row under the title rather than squeezing beside it. */
  .ctt-title-row { flex-wrap: wrap; row-gap: 6px; }
  .ctt-search { width: 100%; max-width: none; }
  .ctt-search-results { left: 0; right: 0; width: auto; max-width: none; }
  /* Issue #50 bug 1: at this width the FedSoc key's absolute top:8px/right:0 placement (desktop's
     "same header band as the title" — see the base rule's own comment) runs into the title text,
     since the title now spans much more of the narrower panel width. Drop the key to its own
     line, in normal flow right after the title/meta, instead of floating over them. */
  .ctt-scotus-affil-key { position: static; justify-content: flex-end; margin: 2px 0 4px; }
}
