/* ──────────────────────────────────────────────────────────────────────
   device-detail.css — chrome for the device-detail redesign.
   Loaded only by flask_app/templates/devices/device_detail.html.
   PR-3 of the device-detail refactor — see
   memory-bank/tasks/feature-v0.8.0-stage-1/analysis/phase-1/
   DEVICE-DETAIL-PLAN.md.

   Token hygiene:
   - Brand + status colours use --color-* tokens already defined in
     flask_app/static/css/design-system.css (loaded globally).
   - Grayscale uses --device-gray-*, scoped to .device-detail-page so it
     can't leak into other pages. Values match the --cp-gray-* scale
     (compliance/_tokens.css) — when design-system.css grows a global
     gray scale, this block can be deleted and the var() refs will fall
     through to the global definition.
   ────────────────────────────────────────────────────────────────────── */

.device-detail-page,
.devices-page,
.gateways-page,
.gateway-detail-page,
.tenants-page,
.positioning-page,
.network-health-page {
    /* SINGLE source of truth for v0.8.0 analyze-section page chrome
       (device-detail, devices list, gateways list, gateway-detail,
       standalone positioning).
       All pages share:
         - the same --device-gray-* scale (matches --cp-gray-*)
         - the same 88rem max-width (matches compliance .fw-page +
           .compliance-landing)
         - the same left-aligned padding
       The .dd-* component rules (.dd-hero, .dd-kpi*, .dd-stage, .dd-rail*)
       below are not scoped to the wrapper — they apply wherever the
       markup appears. Add new analyze-section pages to this selector
       to inherit the wrapper styling. */
    --device-gray-50: var(--bg-page);
    --device-gray-100: var(--status-neutral-bg);
    --device-gray-200: var(--border-base);
    --device-gray-300: var(--border-medium);
    --device-gray-400: var(--border-strong);
    --device-gray-500: var(--fg-4);
    /* Scoped to .network-health-page, NOT :root — every consumer outside
       that scope MUST keep a fallback or the declaration is dropped. */
    --device-gray-600: var(--fg-3);
    --device-gray-700: var(--fg-body-strong);
    --device-gray-800: var(--fg-heading);
    --device-gray-900: var(--fg-1);

    /* Width + padding come from design-system.css :root variables so
       a single edit there updates every page (compliance + analyze). */
    max-width: var(--page-max-width);
    margin: 0;
    padding: var(--page-padding);
    color: var(--device-gray-900);
    font-family: var(--font-base);
    font-size: var(--fs-base);
    line-height: var(--lh-normal);
}

.device-detail-page h1,
.device-detail-page h2,
.device-detail-page h3,
.devices-page h1,
.devices-page h2,
.devices-page h3,
.gateways-page h1,
.gateways-page h2,
.gateways-page h3,
.gateway-detail-page h1,
.gateway-detail-page h2,
.gateway-detail-page h3,
.tenants-page h1,
.tenants-page h2,
.tenants-page h3,
.positioning-page h1,
.positioning-page h2,
.positioning-page h3,
.network-health-page h1,
.network-health-page h2,
.network-health-page h3 {
    font-family: var(--font-display);
    margin: 0;
}

/* ── Hero header ────────────────────────────────────────────────────── */
.dd-hero {
    background: var(--bg-card);
    border: 1px solid var(--device-gray-200);
    border-radius: 0.75rem;
    padding: 1.125rem 1.25rem;
    margin-bottom: 1rem;
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 1rem;
    flex-wrap: wrap;
}

.dd-hero-title-block {
    min-width: 0;
}

.dd-hero-title {
    /* Canonical page-title typography — Ubuntu Sans Medium 30px, matching
       ``.patitur-main-page-title`` (design-tokens.css §h1) in font/size/weight.
       COLOUR intentionally diverges: detail-hero titles use near-black
       ``--fg-1`` rather than brand-green, because the big green wordmark read
       as a link (link green ``var(--link-internal-fg)`` is a near-identical shade). Keeping the
       type but dropping the green removes that "is this heading clickable?"
       ambiguity while list-page titles keep the brand-green macro. Scoped,
       deliberate deviation from the one-colour title convention — not drift.
       Layout properties (inline-flex + gap + flex-wrap) stay so the title sits
       beside the EUI badge / pill cluster inside the hero card. */
    font-family: var(--font-display);
    font-weight: var(--fw-medium);
    font-size: var(--fs-2xl);
    line-height: var(--lh-snug); /* 39px. Was a bare 28px — a line box SMALLER
       than the 30px text, so a wrapping title overlapped its own next line.
       Matches the h2 rule's --lh-snug and what the four `leading-tight` call
       sites were reaching for; those never applied, because this stylesheet
       loads after Tailwind's CDN and won on source order. */
    color: var(--fg-1);
    letter-spacing: normal;
    margin: 0;
    display: inline-flex;
    align-items: baseline;
    gap: 0.75rem;
    flex-wrap: wrap;
}

/* Slideout / detail-panel header (product-model panel, application devices
   slideout). One shared class replacing the identical ``text-2xl font-bold
   text-[var(--fg-1)]`` string that was hand-rolled in two templates — the
   title/header consolidation (v0.11.0 Finding 1). Keeps the current size/weight
   (24px bold near-black) so the migration is visually a no-op; pair it with the
   ``truncate`` utility on the element for the ellipsis. */
.dd-slideout-title {
    font-family: var(--font-display);
    font-weight: var(--fw-bold);
    font-size: var(--fs-xl);
    line-height: var(--lh-snug);
    color: var(--fg-1);
    margin: 0;
}

.dd-hero-eui {
    font-family:
        ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
    font-size: 0.8125rem;
    font-weight: 500;
    color: var(--device-gray-500);
}

.dd-hero-pills {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    flex-wrap: wrap;
    margin-top: 0.5rem;
}

.dd-hero-actions {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex-wrap: wrap;
    flex-shrink: 0;
}

/* ── Action chip (matches device-detail mockup chip pattern) ────────── */
.dd-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.375rem;
    padding: 0.375rem 0.75rem;
    border-radius: 9999px;
    font-size: var(--fs-sm);
    font-weight: 500;
    border: 1px solid var(--device-gray-300);
    color: var(--device-gray-700);
    background: var(--bg-card);
    cursor: pointer;
    font-family: inherit;
    transition:
        background-color 0.12s,
        border-color 0.12s;
}
.dd-chip:hover {
    background: var(--device-gray-50);
    border-color: var(--device-gray-400);
}
.dd-chip svg {
    width: 13px;
    height: 13px;
    flex-shrink: 0;
}

/* ── Pills (re-uses the production --color-status-pill-* token pairs) */
.dd-pill {
    display: inline-flex;
    align-items: center;
    gap: 0.3125rem;
    padding: 0.1875rem 0.625rem;
    border-radius: 9999px;
    font-size: var(--fs-xs);
    font-weight: 600;
    letter-spacing: 0.02em;
}
/* Modifier — slightly de-emphasized weight for status/window pills
   layered next to a panel title (Signal-tab window count, Map-tab
   gateways-heard tag). Replaces the inline ``style="font-weight: 500;"``
   pattern that used to live on those call sites. */
.dd-pill-medium {
    font-weight: 500;
}
.dd-pill-online {
    background: var(--color-status-pill-bg-online);
    color: var(--color-status-pill-text-online);
}
.dd-pill-offline {
    background: var(--color-status-pill-bg-danger, var(--status-danger-bg));
    color: var(--color-status-pill-text-danger);
}
.dd-pill-warning {
    background: var(--color-status-pill-bg-warning, var(--status-warning-bg));
    color: var(--color-status-pill-text-warning);
}
.dd-pill-info {
    background: var(--color-status-pill-bg-info, var(--status-info-bg));
    color: var(--color-status-pill-text-info);
}
.dd-pill-neutral {
    background: var(--color-status-pill-bg-neutral, var(--status-neutral-bg));
    color: var(--color-status-pill-text-neutral, var(--fg-4));
}
/* Fallback for any importance value without a dedicated rule (e.g. the
   `Unknown` enum member, or a future importance level). Without this, an
   unmapped `dd-pill-importance-*` renders as a shapeless, colorless pill
   because base `.dd-pill` sets no background/color. The specific
   -low/-medium/-high/-critical rules below override this by source order. */
[class*="dd-pill-importance-"] {
    background: var(--color-status-pill-bg-neutral, var(--status-neutral-bg));
    color: var(--color-status-pill-text-neutral, var(--fg-4));
}
.dd-pill-importance-low {
    background: var(--color-status-pill-bg-info, var(--status-info-bg));
    color: var(--color-status-pill-text-info);
}
.dd-pill-importance-medium {
    background: var(--color-status-pill-bg-warning, var(--status-warning-bg));
    color: var(--color-status-pill-text-warning);
}
.dd-pill-importance-high {
    background: var(--color-status-pill-bg-warning, var(--status-warning-bg));
    color: var(--color-status-pill-text-warning);
}
.dd-pill-importance-critical {
    background: var(--color-status-pill-bg-danger, var(--status-danger-bg));
    color: var(--color-status-pill-text-danger);
}
/* v0.9.2 device State — lifecycle pill (operational / pending) + reason chip
   (warn / error / benign). Reuses the status-pill colour tokens. Orthogonal to
   the connectivity badge; raw enum values never rendered (mapped in
   constants/devices.py via DeviceStateView). */
.dd-pill-state-operational {
    background: var(--color-status-pill-bg-online);
    color: var(--color-status-pill-text-online);
}
.dd-pill-state-pending {
    background: var(--color-status-pill-bg-warning, var(--status-warning-bg));
    color: var(--color-status-pill-text-warning);
}
.dd-chip-reason {
    display: inline-flex;
    align-items: center;
    padding: 0.125rem 0.4375rem;
    border-radius: 0.375rem;
    font-size: var(--fs-2xs);
    font-weight: 600;
    letter-spacing: 0.02em;
}
.dd-chip-reason-warn {
    background: var(--color-status-pill-bg-warning, var(--status-warning-bg));
    color: var(--color-status-pill-text-warning);
}
.dd-chip-reason-error {
    background: var(--color-status-pill-bg-danger, var(--status-danger-bg));
    color: var(--color-status-pill-text-danger);
}
.dd-chip-reason-benign {
    background: var(--color-status-pill-bg-info, var(--status-info-bg));
    color: var(--color-status-pill-text-info);
}
.dd-pill-dot {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    display: inline-block;
}
.dd-pill-dot.online {
    background: var(--color-chart-status-online);
}
.dd-pill-dot.offline {
    background: var(--color-chart-status-offline);
}
.dd-pill-dot.warning {
    background: var(--color-chart-importance-medium);
}
.dd-pill-dot.acked {
    background: var(--color-chart-importance-low);
}
.dd-pill-dot.resolved {
    background: var(--color-chart-status-online);
}

/* ── KPI strip ──────────────────────────────────────────────────────
   Default is 6 tiles (device-detail). Add ``.dd-kpi-strip-5`` for the
   devices/gateways list pages; ``.dd-kpi-strip-4`` for tighter strips
   (gateways list per PR-8 §3.2). The breakpoint behaviour is shared. */
.dd-kpi-strip {
    display: grid;
    grid-template-columns: repeat(6, minmax(0, 1fr));
    gap: 0.75rem;
    margin-bottom: 1rem;
}
.dd-kpi-strip.dd-kpi-strip-5 {
    grid-template-columns: repeat(5, minmax(0, 1fr));
}
.dd-kpi-strip.dd-kpi-strip-4 {
    grid-template-columns: repeat(4, minmax(0, 1fr));
}
@media (max-width: 1100px) {
    .dd-kpi-strip,
    .dd-kpi-strip.dd-kpi-strip-5,
    .dd-kpi-strip.dd-kpi-strip-4 {
        grid-template-columns: repeat(3, minmax(0, 1fr));
    }
}
@media (max-width: 700px) {
    .dd-kpi-strip,
    .dd-kpi-strip.dd-kpi-strip-5,
    .dd-kpi-strip.dd-kpi-strip-4 {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}

.dd-kpi {
    background: var(--bg-card);
    border: 1px solid var(--device-gray-200);
    border-radius: 0.75rem;
    padding: 0.875rem 1rem;
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
}
.dd-kpi-label {
    font-size: var(--fs-xs); /* matches .kpi-tile-label */
    font-weight: 700;
    color: var(--device-gray-500);
    text-transform: uppercase;
    letter-spacing: 0.05em;
}
.dd-kpi-value {
    font-family: var(--font-display);
    font-weight: 700;
    font-size: var(
        --fs-xl
    ); /* 24px — slight reduction from 1.75rem because 6 tiles cramp */
    color: var(--device-gray-900);
    line-height: 1.15;
}
.dd-kpi-value-unit {
    font-size: var(--fs-sm);
    font-weight: 500;
    color: var(--device-gray-500);
    margin-left: 0.1875rem;
}
.dd-kpi-sub {
    font-size: var(--fs-xs);
    color: var(--device-gray-500);
    margin-top: 0.125rem;
}
.dd-kpi-sub-empty {
    color: var(--device-gray-400);
    font-style: italic;
}

/* ── Stage: tab content + always-on rail ──────────────────────────── */
.dd-stage {
    display: grid;
    /* Rail width comes from --page-rail-width in design-system.css —
       single source of truth shared with future analyze pages. PR-7
       had 320px; PR-8 slimmed to 240px to free horizontal space for
       wide tables. Bump the var to revisit. */
    grid-template-columns: minmax(0, 1fr) var(--page-rail-width);
    gap: 1.125rem;
    /* Rail collapse (plan 03): animate the rail column when
       .rail-collapsed toggles. Expanded template is unchanged. */
    transition: grid-template-columns 0.2s ease;
}
/* Modifier — pages that don't ship a right rail (currently /tenants per
   LIST-PAGES-REDESIGN.md §"Tab structure per page"). Collapses the
   stage to a single full-width column instead of leaving an empty
   rail gutter. */
.dd-stage.dd-stage-no-rail {
    grid-template-columns: minmax(0, 1fr);
}
@media (max-width: 1100px) {
    .dd-stage,
    .dd-stage.rail-collapsed {
        grid-template-columns: 1fr;
    }
}
/* Devices LIST squeeze fix. The list table carries a ~800px min-width; beside
   the 240px rail it drops below that between ~1100-1400px, so its right columns
   (Last Seen / Actions) scroll-clip *under* the rail. Stack the rail below the
   full-width table until the viewport can hold the table's min-width + the rail.
   Scoped to .devices-page only — detail pages keep the 1100px breakpoint, their
   main column is flexible (chart), not min-width-bound. Headroom left for the
   upcoming device-State column (wider table). */
@media (max-width: 1400px) {
    .devices-page .dd-stage,
    .devices-page .dd-stage.rail-collapsed {
        grid-template-columns: 1fr;
    }
}

/* == Right-rail collapse (plan 03 / handoff .generated/handoff-alerts)
   ===================================================================
   Additive only -- the base .dd-stage above keeps its
   minmax(0,1fr) var(--page-rail-width) template and .dd-stage-no-rail
   modifier. One chevron at the top of the rail collapses both cards
   into a ~44px vertical strip, freeing horizontal width for the table.
   Same pattern/glyph as the left-nav + tenant-map gateway-rail
   collapses. NOTE: the handoff snippet redeclared a conflicting base
   .dd-stage in design-system.css; that was intentionally NOT taken
   (the canonical rule lives here) -- see plan 03 sec 2.1. */
.dd-stage.rail-collapsed {
    grid-template-columns: minmax(0, 1fr) 44px;
}

/* Toggle row sits above the cards: right-aligned when expanded,
   centered over the strip when collapsed. */
.dd-rail-toggle-row {
    display: flex;
    justify-content: flex-end;
}
.dd-stage.rail-collapsed > .dd-stage-rail .dd-rail-toggle-row {
    justify-content: center;
}
.dd-rail-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    border: 1px solid var(--border-base);
    background: var(--bg-card);
    border-radius: 6px;
    color: var(--fg-4);
    cursor: pointer;
    padding: 0;
    transition:
        background 0.15s,
        color 0.15s,
        border-color 0.15s;
    flex: none;
}
.dd-rail-toggle:hover {
    background: var(--color-panel-header-tinted);
    color: var(--coverage-strong-fg);
    border-color: var(--policy-stricter-bg);
}
.dd-rail-toggle:focus-visible {
    outline: 2px solid var(--color-brand-green);
    outline-offset: 2px;
}
.dd-rail-toggle svg {
    transition: transform 0.2s;
}
.dd-stage.rail-collapsed > .dd-stage-rail .dd-rail-toggle svg {
    transform: rotate(180deg);
}

/* Collapsed: card body hides; header reorients to a vertical text
   strip (same writing-mode trick as the tenant-map gateway rail).
   The titles are wrapped in a <span> in
   _alerts_page_content_generic.html for this rule to match.

   SCOPING, two rules, both learned the hard way.

   1. Qualify by .dd-stage-rail. The Coverage-map
   card on the Map tab reuses .dd-rail-card* but lives in
   .dd-stage-main; an unscoped `.dd-stage.rail-collapsed
   .dd-rail-card-header` matched it too, so collapsing the rail forced
   the "Coverage map" header to flex-column and `> :not(span)` hid the
   Expand button (the "title + Expand on two lines / chrome breaks when
   collapsed" bug). Rail-only keeps the vertical-strip trick where it
   belongs and leaves main-column cards untouched.

   2. Use the CHILD combinator. A .dd-stage can now contain another one
   -- the device Positioning tab's stage nests inside the page stage --
   and a descendant selector let an OUTER collapsed stage collapse the
   INNER rail, which no toggle could then undo: clicking the inner
   chevron flipped the inner stage's class while the ancestor's rule
   kept forcing it shut. `> .dd-stage-rail` binds each rule to its own
   rail. */
.dd-stage.rail-collapsed > .dd-stage-rail .dd-rail-card-body {
    display: none;
}
.dd-stage.rail-collapsed > .dd-stage-rail .dd-rail-card-header {
    flex-direction: column;
    align-items: center;
    justify-content: flex-start;
    padding: 14px 4px;
    border-bottom: 0;
    min-height: 140px;
}
.dd-stage.rail-collapsed > .dd-stage-rail .dd-rail-card-header > span {
    writing-mode: vertical-rl;
    transform: rotate(180deg);
    letter-spacing: 0.04em;
    font-size: var(--fs-sm);
}
/* Collapsed strip shows only the rotated label -- hide any other
   header children (e.g. the gateway-rail "See all" aux link) so the
   44px strip stays clean. */
.dd-stage.rail-collapsed > .dd-stage-rail .dd-rail-card-header > :not(span) {
    display: none;
}
.dd-stage-main {
    min-width: 0;
    /* Wide tables (devices list table has 7 columns including Actions)
       can exceed the 1fr column at narrow viewports — without
       overflow-x:auto they render *under* the 320px rail and clip the
       Actions column. Scoped to direct table descendants so the rest of
       the page chrome stays in normal flow. */
    overflow-x: hidden;
}
.dd-stage-main > div,
.dd-stage-main .table-container,
.dd-stage-main .table-container-box {
    max-width: 100%;
    overflow-x: auto;
}

/* Off-state uses :not(.active) so its specificity beats Tailwind
   single-class utilities like ``flex`` / ``block`` that callers may
   put on the wrapper (the tenant-detail Map sub-tabs do, so the map
   fills the column height when active). The active state has no
   explicit display rule on purpose — the element falls back to its
   natural display (``block`` for sections/divs) or to whatever
   utility class the caller applied (``flex`` for the heat-map
   wrapper that needs the column-height fill). */
.dd-tab-content:not(.active) {
    display: none;
}

/* ── Right rail card chrome (filled by PR-4 / PR-5) ───────────────── */
.dd-rail-card {
    background: var(--bg-card);
    border: 1px solid var(--device-gray-200);
    border-radius: 0.75rem;
    overflow: hidden;
}
.dd-rail-card + .dd-rail-card {
    margin-top: 0.875rem;
}
.dd-rail-card-header {
    padding: 0.75rem 0.875rem;
    font-size: var(--fs-sm);
    font-weight: 700;
    color: var(--device-gray-500);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    border-bottom: 1px solid var(--device-gray-100);
    display: flex;
    align-items: center;
    justify-content: space-between;
}
/* Body padding mirrors the header so the rail content has a visible
   12/14 px gutter on every side. Without this rule the body text sat
   flush to the card border (operator feedback: "no spacing"). Cards
   that need a different inner layout (e.g. dd-rail-bar-row with its
   own padded rows) can override on more specific selectors. */
.dd-rail-card-body {
    padding: 0.75rem 0.875rem;
}

/* A rail card whose body is a list long enough to need its own scrollbar.
   Opt-in, because most rail cards are short read-outs and capping those would
   crop them for nothing.

   The problem it solves: the clusters list runs to 30-plus rows on a busy
   gateway, and with the rail growing to fit, reaching row 30 scrolled the PAGE
   — which carried the map out of the viewport. Selecting a cluster from the
   bottom of the list then meant scrolling back up to see what had been
   selected, which is the one thing the rail exists to make immediate.

   The height is capped against the viewport rather than the map: the map is in
   a sibling column and reading its height from CSS is not possible, while the
   rail is sticky and so is measured from the same viewport the operator is
   looking at. overscroll-behavior keeps a flick at the end of the list from
   handing the scroll back to the page and moving the map after all. */
.dd-rail-card--scroll {
    display: flex;
    flex-direction: column;
    max-height: calc(100vh - 12rem);
    /* A flex child will not shrink below its content without this, which is
       what makes an overflowing body scroll rather than push the card taller. */
    min-height: 0;
}
.dd-rail-card--scroll > .dd-rail-card-body {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
}
.dd-rail-card--scroll > .dd-rail-card-header,
.dd-rail-card--scroll > .pos-band-filter {
    flex: 0 0 auto;
}

/* The signal-band filter in the clusters rail. Sits between the card header and
   the scrolling list so it stays put while the list moves under it — a count you
   are filtering by must remain on screen.

   Borderless and full-width: it is a strip inside a .dd-rail-card, not a
   free-standing control, so the card's own border is the only one needed.

   Filed here rather than with the other .pos-* rules in
   positioning/positioning_map.css, which the prefix would suggest. It modifies
   .dd-rail-card, defined directly above, and consumes --device-gray-100, which
   is declared in this file on the page-level selectors. Moving it would split a
   modifier from its base AND leave it reading a token declared elsewhere; the
   prefix is the weaker claim of the two. */
.pos-band-filter {
    padding: 0.5rem 0.875rem;
    border-bottom: 1px solid var(--device-gray-100);
}

/* Leaflet-managed containers should never grow a UA scrollbar. Same
   pattern as the .patitur-tab-bar fix above: a CORS-blocked third-
   party stylesheet (Flowbite, likely its responsive-table preset)
   applies ``overflow-x: auto`` on map containers and the small extra
   tile/SVG-pane content past the viewport triggers a 2 px scrollbar
   gutter on the bottom edge. Leaflet does its own clipping via
   absolute positioning on the panes; the container itself never
   needs scrollbars. ``!important`` because the third-party rule has
   higher cascade specificity. */
.leaflet-container {
    overflow: hidden !important;
}

/* Configuration-tab card width — the .dd-rail-card / .dd-rail-row
   chrome was designed for the 240 px right-rail (label + value
   tight against each other). On the gateway-detail Configuration
   tab those same cards live in the full-width .dd-stage-main, where
   ``justify-content: space-between`` smears the label and value to
   opposite ends with a huge empty gap between. Pinning the card to a
   readable max-width keeps the operator's eye-track short. Scoped to
   the Configuration tab only — other tabs (Map, Devices, Alerts,
   Positioning) host full-width content that the rule should NOT
   touch. */
#gw-tabpanel-config .dd-rail-card,
#device-tabpanel-config .dd-rail-card {
    max-width: 640px;
}

/* Positioning-toolbar mode buttons — the Method / Clusters / Radial-
   scatter trio. Previously only had Tailwind utilities (px-3 py-1.5
   font-medium), so the inactive Clusters button rendered as plain
   text and read like a label. These rules give the unpressed state a
   hover affordance and the aria-pressed=true state a brand-tinted fill
   so the operator can tell at a glance which mode is active. */
.pos-mode-btn {
    background: var(--bg-card);
    color: var(--device-gray-700, var(--bg-sidebar-hover));
    cursor: pointer;
    transition:
        background 0.12s,
        color 0.12s;
}
.pos-mode-btn:hover:not(:disabled):not([aria-pressed="true"]) {
    background: var(--device-gray-50, var(--bg-page));
    color: var(--device-gray-900, var(--fg-1));
}
.pos-mode-btn[aria-pressed="true"] {
    background: color-mix(in srgb, var(--color-brand-green) 12%, transparent);
    color: var(--color-brand-green);
}
.pos-mode-btn:disabled {
    cursor: not-allowed;
}
.dd-rail-card-header-aux {
    font-family: var(--font-display);
    font-weight: 700;
    font-size: var(--fs-xs);
    color: var(--device-gray-700);
    letter-spacing: 0;
    text-transform: none;
}
.dd-rail-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    padding: 0.5rem 0.875rem;
    font-size: var(--fs-sm);
    border-bottom: 1px solid var(--device-gray-50);
}
.dd-rail-row:last-child {
    border-bottom: none;
}
.dd-rail-row-label {
    color: var(--device-gray-500);
    flex-shrink: 0;
}
.dd-rail-row-value {
    color: var(--device-gray-900);
    font-weight: 500;
    text-align: right;
    min-width: 0;
    overflow-wrap: anywhere;
}
/* Second line inside a rail value — the attribution a one-line row cannot
   carry. Used by Reception ratio to name the cluster the figure belongs to,
   since the row sits on a DEVICE page. */
.dd-rail-row-note {
    display: block;
    font-size: var(--fs-xs);
    font-weight: 400;
    color: var(--fg-5);
}
.dd-rail-row-value.mono {
    font-family:
        ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
    font-size: var(--fs-xs);
}

/* Standalone monospace utility — same stack as .dd-rail-row-value.mono
   but usable on any element that doesn't already carry the rail-row
   value chrome (e.g. inline coordinate strings inside a flex row). */
.dd-mono {
    font-family:
        ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
}

/* ── Rail proportion-bar rows (Status / Importance breakdown cards on
   the devices + gateways list rails — PR-8). Two-line row: top has a
   label on the left and ``count · pct`` on the right; below sits a 4 px
   track with a coloured fill. Inline ``style="width: N%"`` drives the
   fill width; class drives the colour. */
.dd-rail-bar-row {
    padding: 0.5rem 0.875rem;
    border-bottom: 1px solid var(--device-gray-50);
    font-size: var(--fs-sm);
}
.dd-rail-bar-row:last-child {
    border-bottom: none;
}
.dd-rail-bar-row-meta {
    display: flex;
    justify-content: space-between;
    margin-bottom: 0.25rem;
}
.dd-rail-bar-row-label {
    color: var(--device-gray-700);
    font-weight: 500;
}
.dd-rail-bar-row-count {
    color: var(--device-gray-500);
    font-size: var(--fs-xs);
    font-variant-numeric: tabular-nums;
}
.dd-rail-bar-track {
    height: 4px;
    background: var(--device-gray-100);
    border-radius: 2px;
    overflow: hidden;
}
.dd-rail-bar-fill {
    height: 100%;
    background: var(--device-gray-400);
    transition: width 0.2s ease;
}

.dd-rail-bar-fill-online {
    background: var(--color-status-pill-text-online);
}
.dd-rail-bar-fill-offline {
    background: var(--color-status-pill-text-danger);
}
.dd-rail-bar-fill-critical {
    background: var(--color-status-pill-text-danger);
}
.dd-rail-bar-fill-high {
    background: var(--sev-dot-high);
}
.dd-rail-bar-fill-medium {
    background: var(--color-status-pill-text-warning);
}
.dd-rail-bar-fill-low {
    background: var(--color-status-pill-text-info);
}
.dd-rail-bar-fill-unknown {
    background: var(--device-gray-400);
}

/* ── Empty-state placeholder (Alerts tab + rail before PR-4 / PR-5) ── */
/* A compact empty state, for a panel too short for the default 2.5rem block.
   Was an inline padding override on 10 of the 45 call sites. */
.dd-empty-state--compact {
    padding: 1rem 0.875rem;
}

/* A pagination chip that cannot be followed. The markup already says so with
   aria-disabled, so the appearance follows the attribute rather than a second
   hand-written style. */
.dd-chip[aria-disabled="true"] {
    opacity: 0.5;
    cursor: not-allowed;
}

.dd-empty-state {
    background: var(--bg-card);
    border: 1px dashed var(--device-gray-300);
    border-radius: 0.75rem;
    padding: 2.5rem 1.5rem;
    text-align: center;
    color: var(--device-gray-500);
    font-size: var(--fs-base);
}
.dd-empty-state .dd-empty-state-title {
    font-family: var(--font-display);
    font-weight: 600;
    color: var(--device-gray-700);
    margin-bottom: 0.375rem;
    font-size: 0.9375rem;
}
/* Modifier — compact variant for rail cards and KPI tile sub-areas
   where the full 2.5rem padding feels heavy. Replaces the inline
   ``style="padding:1.25rem 0.875rem;"`` + ``style="font-size:var(--fs-xs);"``
   patterns that used to live on those call sites. */
.dd-empty-state-compact {
    padding: 1.25rem 0.875rem;
}
/* Scoped to the component, not to one compact variant: the codebase carries
   BOTH .dd-empty-state-compact and .dd-empty-state--compact, and this rule only
   matched the first, so the same subtitle rendered at 11px in the device rail
   and at the inherited 14px on the devices page — which is why those call sites
   still carried an inline font-size. */
.dd-empty-state .dd-empty-state-aux {
    font-size: var(--fs-xs);
}

/* ── Signal-tab per-gateway chrome (Wave-0) ───────────────────────────
   Field-tab bar / window selector / mode bar / gateway chip bar / per-
   gateway summary cards. All scoped to .dd-signal-panel so they don't
   leak into other panels that use the same dashboard-card chrome.
   See memory-bank/tasks/feature-v0.8.0-stage-1/analysis/phase-2/
   DEVICE-DETAIL-WAVE-0-PLAN.md §3.6 for the design and
   device-detail-redesigned.html lines 271-398 for the visual target. */

.dd-field-bar {
    display: flex;
    align-items: center;
    gap: 0.375rem;
    flex-wrap: wrap;
    padding: 0.875rem 0 0.75rem;
    border-bottom: 1px solid var(--device-gray-100);
    margin-bottom: 0.875rem;
}
.dd-field-bar-label {
    font-size: var(--fs-xs);
    font-weight: 700;
    color: var(--device-gray-500);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    margin-right: 0.25rem;
}
.dd-field-bar-divider {
    width: 1px;
    align-self: stretch;
    background: var(--device-gray-200);
    margin: 0 0.375rem;
}
.dd-field-unit {
    font-size: var(--fs-2xs);
    opacity: 0.7;
    font-family:
        ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
}

.dd-mode-bar {
    display: inline-flex;
    background: var(--device-gray-100);
    border-radius: 0.625rem;
    padding: 0.1875rem;
    gap: 0.125rem;
}
.dd-mode-bar button {
    padding: 0.375rem 0.875rem;
    font-size: var(--fs-sm);
    font-weight: 500;
    border-radius: 0.5rem;
    color: var(--device-gray-500);
    background: transparent;
    border: none;
    cursor: pointer;
    font-family: inherit;
}
.dd-mode-bar button.active {
    background: var(--color-brand-green);
    color: var(--fg-on-dark);
}
.dd-mode-bar button:disabled {
    opacity: 0.4;
    cursor: not-allowed;
}

.dd-gw-chip-bar {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex-wrap: wrap;
    padding: 0.875rem 0;
    border-bottom: 1px solid var(--device-gray-100);
    background: var(--device-gray-50);
    margin: 0 -0.875rem 0.875rem;
    padding-left: 0.875rem;
    padding-right: 0.875rem;
}
.dd-gw-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.375rem 0.75rem;
    border-radius: 9999px;
    border: 1.5px solid var(--device-gray-300);
    background: var(--bg-card);
    font-size: var(--fs-sm);
    font-weight: 600;
    color: var(--device-gray-700);
    cursor: pointer;
    user-select: none;
    font-family: inherit;
    transition:
        opacity 0.12s,
        border-color 0.12s;
}
.dd-gw-chip-dot {
    width: 0.625rem;
    height: 0.625rem;
    border-radius: 50%;
    flex-shrink: 0;
    /* The chip carries its gateway colour as --gw-color (set by .series-N);
       the dot is the one component in that family that never read it, so it
       painted nothing and the chips lost the colour that ties them to their
       chart lines. Same property its own border uses, one rule above. */
    background: var(--gw-color, var(--fg-5));
}
.dd-gw-chip:not(.on) {
    opacity: 0.45;
    text-decoration: line-through;
    background: var(--device-gray-50);
}
.dd-gw-chip.on {
    /* Per-gateway colour piped via inline ``--gw-color`` custom prop
       in the template — keeps the palette server-side per
       constants/signal_metrics.py:GATEWAY_PALETTE without inflating
       this stylesheet with N hard-coded colours. */
    border-color: var(--gw-color, var(--device-gray-300));
}
.dd-gw-chip-aggregate {
    border-style: dashed;
}

.dd-gw-summary-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
    gap: 0.75rem;
    padding-top: 0.875rem;
    border-top: 1px solid var(--device-gray-100);
    margin-top: 0.875rem;
}
.dd-gw-summary {
    border: 1px solid var(--device-gray-200);
    border-radius: 0.625rem;
    padding: 0.75rem 0.875rem;
    transition: opacity 0.12s;
}
.dd-gw-summary.off {
    opacity: 0.45;
}
.dd-gw-summary-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 0.5rem;
    margin-bottom: 0.5rem;
}
.dd-gw-summary-name {
    font-family: var(--font-display);
    font-weight: 700;
    font-size: 0.8125rem;
    color: var(--device-gray-900);
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
}
.dd-gw-summary-dot {
    width: 0.625rem;
    height: 0.625rem;
    border-radius: 50%;
    flex-shrink: 0;
    /* Same omission the chip dot had: .series-N puts the gateway colour on the
       card as --gw-color and this never read it, so the summary named a gateway
       with no way to tie it to its chart line. The two dots are the same idea in
       two places and now paint from the same property. */
    background: var(--gw-color, var(--fg-5));
}
.dd-gw-summary-eui {
    font-family:
        ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
    font-size: var(--fs-2xs);
    color: var(--device-gray-500);
    margin-top: 0.125rem;
    margin-left: 1.125rem;
}
.dd-gw-summary-stats {
    display: grid;
    grid-template-columns: repeat(5, minmax(0, 1fr));
    gap: 0.5rem;
}
/* The gateway-summary stat is the KPI tile at a smaller scale: same label,
   value and unit structure, 15px instead of 24px. It had its own three class
   names (.dd-gw-stat-label / -value / -unit) describing the same three parts,
   so .dd-kpi--sm folds it in and the old names become aliases of these rules
   rather than a fourth vocabulary.

   The 9px label is not on the type scale — 10px is the smallest step — so it
   comes up to --fs-2xs like every other off-scale size did. */
.dd-kpi--sm .dd-kpi-label {
    font-size: var(--fs-2xs);
}

.dd-kpi--sm .dd-kpi-value {
    font-family: var(--font-display);
    font-size: var(--fs-base);
    margin-top: 0.125rem;
}

.dd-kpi--sm .dd-kpi-value-unit {
    font-size: var(--fs-2xs);
    margin-left: 0.125rem;
}

.dd-gw-stat-label {
    font-size: 0.5625rem;
    font-weight: 700;
    color: var(--device-gray-500);
    text-transform: uppercase;
    letter-spacing: 0.05em;
}
.dd-gw-stat-value {
    font-family: var(--font-display);
    font-weight: 700;
    font-size: 0.9375rem;
    color: var(--device-gray-900);
    margin-top: 0.125rem;
}
.dd-gw-stat-unit {
    font-size: var(--fs-2xs);
    font-weight: 500;
    color: var(--device-gray-500);
    margin-left: 0.125rem;
}

.dd-gw-status-tag {
    font-size: var(--fs-2xs);
    font-weight: 700;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    padding: 0.125rem 0.5rem;
    border-radius: 9999px;
    flex-shrink: 0;
}
/* Signal-band swatches (mirror flask_app/constants/signal_metrics.py
   BAND_COLORS — single source of truth across Python, CSS, and the
   gateway-marker JS which reads ``entry.band_color`` directly).
   Strong/fair share their tints with --color-status-pill-*-online and
   --color-status-pill-*-warning; weak intentionally uses a softer red
   (var(--danger-strong-fg)) than the full-danger pill (var(--status-danger-fg)) so a gateway in the
   Weak band reads distinct from a fully offline device. Keep the
   literals — consolidating into shared CSS vars here would couple the
   "weak signal" semantic to "offline device", which they aren't. */
.dd-gw-status-tag-strong {
    background: var(--status-online-bg);
    color: var(--status-online-fg);
}
.dd-gw-status-tag-fair {
    background: var(--status-warning-bg);
    color: var(--status-warning-fg);
}
.dd-gw-status-tag-weak {
    background: var(--status-danger-bg);
    color: var(--danger-strong-fg);
}
.dd-gw-status-tag-unknown {
    background: var(--device-gray-100);
    color: var(--device-gray-500);
}

/* Rail "Gateways heard" card rows — one row per gateway, click-through
   to gateway-detail. Compact two-column stats footer keeps the row
   under 70px so 5 rows fit in the rail without scroll. */
.dd-rail-gateway {
    display: block;
    padding: 0.625rem 0.875rem;
    border-bottom: 1px solid var(--device-gray-100);
    text-decoration: none;
    color: inherit;
    transition: background-color 0.12s;
}
.dd-rail-gateway:last-child {
    border-bottom: none;
}
.dd-rail-gateway:hover {
    background: var(--device-gray-50);
}
.dd-rail-gateway-name {
    display: block;
    font-family: var(--font-display);
    font-weight: 700;
    font-size: 0.8125rem;
    color: var(--device-gray-900);
    margin-bottom: 0.125rem;
}
.dd-rail-gateway-eui {
    display: block;
    font-family:
        ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
    font-size: var(--fs-2xs);
    color: var(--device-gray-500);
    margin-bottom: 0.375rem;
}
.dd-rail-gateway-stats {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0.375rem;
    font-size: var(--fs-xs);
}
.dd-rail-gateway-stat-label {
    color: var(--device-gray-500);
    margin-right: 0.25rem;
}
.dd-rail-gateway-stat-value {
    color: var(--device-gray-900);
    font-family: var(--font-display);
    font-weight: 700;
}

/* ── Network Health (stage-1 phase-1 §3.2 Lite) ────────────────────
   Standalone Network Health page chrome. Mirrors the analyze-section
   wrapper rule above; the KPI grid + placeholder pill below are
   page-specific so they don't leak. The phase-2 tiles render in a
   muted state so the operator sees what's coming without confusing
   "0" with "not implemented." */
/* Network Health KPI tiles use the canonical design-system .dd-kpi
   classes verbatim (same look as /gateways + /devices + /alerts) —
   the prior bespoke .nh-kpi-grid / .nh-kpi-tile / .nh-kpi-label /
   .nh-kpi-value / .nh-kpi-fraction / .nh-kpi-sub variants were
   removed in favour of .dd-kpi-strip / .dd-kpi / .dd-kpi-label /
   .dd-kpi-value / .dd-kpi-value-unit / .dd-kpi-sub. Only the
   icon-inline-with-label modifier + accent variants live here. */
.nh-kpi-label-icon {
    display: inline-flex;
    align-items: center;
    margin-right: 0.375rem;
    color: var(--device-gray-400, var(--fg-5));
    vertical-align: -2px;
}
/* Headline-tile colour accents — applied to the 5 .dd-kpi tiles that
   answer health questions (Gateways online, Open alerts, Mean RSSI,
   Resilient positioning, Fragile cells). Two reinforcing channels
   per tile (coloured value text + tinted leading icon). The other
   4 count-context tiles (Active devices, Devices located, Good-
   signal band, Signal clusters) stay neutral so colour means
   something. Hexes mirror SIGNAL_BAND_STRONG/FAIR/WEAK_COLOR in
   constants/signal_metrics.py — same palette the hero verdict and
   the redundancy map cells read against. Flat fills only, no
   asymmetric thick edges or shadows (those read as bevels). */
.dd-kpi.nh-kpi-accent-green .dd-kpi-value,
.dd-kpi.nh-kpi-accent-green .nh-kpi-label-icon {
    color: var(--status-online-fg);
}
.dd-kpi.nh-kpi-accent-amber .dd-kpi-value,
.dd-kpi.nh-kpi-accent-amber .nh-kpi-label-icon {
    color: var(--status-warning-fg);
}
.dd-kpi.nh-kpi-accent-red .dd-kpi-value,
.dd-kpi.nh-kpi-accent-red .nh-kpi-label-icon {
    color: var(--danger-strong-fg);
}
.nh-kpi-tile--accent-neutral {
    /* Sentinel — applied when the underlying value is None (the
       headline tile literally has nothing to verdict). Same look as
       the base .nh-kpi-tile so the absence reads as "no data yet";
       icon stays in its neutral grey default. */
}

/* Hero verdict banner — single coloured strip above the KPI grid,
   answers "should I worry?" at a glance. Driven by the same
   resilient_positioning_pct the map cells aggregate against.
   Compact one-line layout: bold label + trailing summary on the
   same row, no stacked eyebrow row. Flat 1px border — no
   asymmetric thick edge (read as a bevel); the verdict pop comes
   from the bg tint + the label colour. */
.nh-verdict {
    border: 1px solid;
    border-radius: 0.5rem;
    padding: 0.625rem 1rem;
    margin-bottom: 1rem;
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.625rem;
}
.nh-verdict-label {
    font-family: var(--font-display);
    font-weight: 700;
    font-size: var(--fs-md);
    letter-spacing: 0.04em;
    text-transform: uppercase;
    flex-shrink: 0;
}
.nh-verdict-summary {
    font-size: var(--fs-base);
    color: var(--device-gray-700, var(--bg-sidebar-hover));
    margin: 0;
    flex: 1;
    min-width: 0;
}
.nh-verdict--robust {
    background: var(--status-online-bg);
    border-color: var(--status-online-fg);
}
.nh-verdict--robust .nh-verdict-label {
    color: var(--status-online-fg);
}
.nh-verdict--mixed {
    background: var(--banner-warning-bg);
    border-color: var(--status-warning-fg);
}
.nh-verdict--mixed .nh-verdict-label {
    color: var(--status-warning-fg);
}
.nh-verdict--fragile {
    background: var(--sev-high-bg);
    border-color: var(--danger-strong-fg);
}
.nh-verdict--fragile .nh-verdict-label {
    color: var(--danger-strong-fg);
}
.nh-verdict--unknown {
    background: var(--device-gray-50, var(--bg-page));
    border-color: var(--device-gray-300, var(--border-medium));
}
.nh-verdict--unknown .nh-verdict-label {
    color: var(--device-gray-600, var(--fg-3));
}

/* Org-page worst-tenants table — chrome uses Tailwind utility
   classes inline in the template (matches the design-system pattern
   the policies + glossary + compliance tables use). Only the two
   table-specific bits that don't reduce cleanly to Tailwind live
   here: the sortable column-header button + the per-row verdict
   pill colour variants. */
.nh-sort-btn {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    padding: 0;
    background: transparent;
    border: 0;
    font: inherit;
    color: inherit;
    text-transform: inherit;
    letter-spacing: inherit;
    cursor: pointer;
}
.nh-sort-btn:hover,
.nh-sort-btn:focus-visible {
    color: var(--device-gray-900, var(--fg-1));
    outline: none;
}
.nh-sort-arrow {
    min-width: 0.75rem;
    text-align: center;
    color: var(--device-gray-400, var(--fg-5));
    font-weight: 700;
}
/* Per-row verdict pill — same palette the hero banner + map cells
   use, just shrunk for the table cell. */
.nh-org-tenants-pill {
    display: inline-block;
    padding: 0.125rem 0.5rem;
    border-radius: 9999px;
    font-size: var(--fs-xs);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.nh-org-tenants-pill--robust {
    background: var(--status-online-bg);
    color: var(--status-online-fg);
}
.nh-org-tenants-pill--mixed {
    background: var(--banner-warning-bg);
    color: var(--status-warning-fg);
}
.nh-org-tenants-pill--fragile {
    background: var(--sev-high-bg);
    color: var(--danger-strong-fg);
}
.nh-org-tenants-pill--unknown {
    background: var(--device-gray-100, var(--status-neutral-bg));
    color: var(--device-gray-600, var(--fg-3));
}

/* Phase-2 pill — mirrors the alert-map "Per-device disabled" affordance:
   visible-but-not-faked. Operator reads "we know this exists, it's not
   yet wired" without seeing a zero that could be misread as a real
   value. */
.nh-phase-pill {
    display: inline-flex;
    align-items: center;
    padding: 0.125rem 0.5rem;
    border-radius: 9999px;
    background: var(--device-gray-100, var(--status-neutral-bg));
    color: var(--device-gray-600, var(--fg-3));
    font-size: var(--fs-2xs);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

/* Map-area placeholder card — same dashed-border / muted-bg posture
   as the phase-2 KPI tiles. */
.nh-map-placeholder {
    background: var(--device-gray-50, var(--bg-page));
    border: 1px dashed var(--device-gray-300, var(--border-medium));
    border-radius: 0.75rem;
    padding: 2.5rem 1.25rem;
    text-align: center;
}
.nh-map-placeholder-inner {
    max-width: 38rem;
    margin: 0 auto;
}
.nh-map-placeholder-icon {
    width: 2.5rem;
    height: 2.5rem;
    color: var(--device-gray-300, var(--border-medium));
    margin: 0 auto 0.75rem;
    display: block;
}
.nh-map-placeholder-title {
    font-family: var(--font-display);
    font-weight: 700;
    font-size: var(--fs-md);
    color: var(--device-gray-700, var(--bg-sidebar-hover));
    margin: 0 0 0.5rem;
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
}
.nh-map-placeholder-body {
    font-size: 0.8125rem;
    color: var(--device-gray-500, var(--fg-4));
    margin: 0;
    line-height: var(--lh-normal);
}
.nh-map-placeholder-actions {
    margin: 1rem 0 0;
    font-size: 0.8125rem;
}
.nh-heatmap-container,
.nh-redundancy-container {
    min-height: 540px;
    border: 1px solid var(--device-gray-200, var(--border-base));
    border-radius: 0.75rem;
    background: var(--bg-card);
    overflow: hidden;
}
.nh-heatmap-footnote {
    margin: 0.5rem 0 0;
    font-size: var(--fs-sm);
    color: var(--device-gray-500, var(--fg-4));
    text-align: right;
}

/* ============================================================
   Cluster Posterior tab (FE 1.4, v0.9.0) — tier hero, posterior
   bars, factor waterfall. Flat fills only (no bevels). Tokens
   from design-tokens.css.
   ============================================================ */

.dd-layout {
    display: grid;
    grid-template-columns: 1fr;
    gap: 16px;
}

/* ── Tier hero ─────────────────────────────────────────────── */
.tier-hero {
    border: 1px solid var(--border-base);
    border-radius: 10px;
    padding: 16px 18px;
    background: var(--bg-page);
    margin-bottom: 16px;
}
.tier-hero-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    flex-wrap: wrap;
}
.tier-hero-pill {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 6px 12px;
    border-radius: 9999px;
    font-weight: var(--fw-semibold);
    border: 1px solid var(--border-base);
    color: var(--fg-2);
}
.tier-hero-pill .shape-i {
    display: inline-flex;
}
.tier-hero-pill.definitely {
    color: var(--color-brand-green);
    border-color: var(--color-brand-green);
}
.tier-hero-pill.probably {
    color: var(--sev-medium-fg);
    border-color: var(--sev-medium-fg);
}
.tier-hero-pill.weakly {
    color: var(--sev-high-fg);
    border-color: var(--sev-high-fg);
}
.tier-hero-pill.unclustered {
    color: var(--fg-4);
    border-color: var(--border-base);
}
.tier-hero-cluster-link {
    font-weight: var(--fw-semibold);
    color: var(--color-brand-green);
}
.tier-hero-confidence {
    font-family: var(--font-mono);
    font-weight: 700;
    font-size: var(--fs-lg, 1.125rem);
    color: var(--fg-1);
    text-align: right;
}
.tier-hero-confidence-sub {
    display: block;
    font-family: var(--font-base, inherit);
    font-size: var(--fs-xs);
    font-weight: 400;
    color: var(--fg-4);
}
.tier-hero-secondary {
    display: flex;
    flex-wrap: wrap;
    gap: 6px 22px;
    margin-top: 12px;
    font-size: var(--fs-sm);
    color: var(--fg-3);
}

/* ── Card with head ────────────────────────────────────────── */
.card-with-head {
    border: 1px solid var(--border-base);
    border-radius: 10px;
    margin-bottom: 16px;
    background: var(--bg-card);
}
.card-with-head .head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    padding: 12px 16px;
    border-bottom: 1px solid var(--border-base);
}
.card-with-head .head h3 {
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    color: var(--fg-1);
    margin: 0;
}
.card-with-head .head .sub {
    font-size: var(--fs-xs);
    color: var(--fg-4);
}
.card-with-head .body {
    padding: 14px 16px;
}

/* ── Posterior bars ────────────────────────────────────────── */
.posterior-row {
    display: grid;
    grid-template-columns: 22px 1fr 2fr 44px;
    align-items: center;
    gap: 10px;
    padding: 5px 0;
    font-size: var(--fs-sm);
}
.posterior-row .icon {
    width: 22px;
    height: 22px;
    border-radius: 50%;
    background: var(--bg-page);
    color: var(--fg-3);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: var(--fs-xs);
    border: 1px solid var(--border-base);
}
.posterior-row.most-likely .icon {
    background: var(--color-brand-green);
    color: var(--fg-on-dark);
    border-color: var(--color-brand-green);
}
.posterior-row .name {
    font-weight: 500;
    color: var(--fg-2);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.posterior-row .name .muted {
    opacity: 0.7;
    font-style: italic;
}
.posterior-row.most-likely .name {
    color: var(--color-brand-green);
    font-weight: 600;
}
.posterior-row .bar {
    height: 8px;
    background: var(--bg-page);
    border-radius: 9999px;
    overflow: hidden;
}
.posterior-row .fill {
    height: 100%;
    /* Server-sent share, read from a custom property rather than written as a
       declaration in the markup. */
    width: var(--bar-pct, 0%);
    background: var(--sev-medium-fg);
    border-radius: 9999px;
}
.posterior-row.most-likely .fill {
    background: var(--color-brand-green);
}
.posterior-row.background .fill {
    background: var(--fg-5);
}
.posterior-row .pct {
    text-align: right;
    font-family: var(--font-mono);
    font-weight: 700;
    font-size: var(--fs-sm);
    color: var(--fg-1);
}
.posterior-entropy {
    display: flex;
    justify-content: space-between;
    gap: 12px;
    margin-top: 12px;
    padding-top: 10px;
    border-top: 1px solid var(--border-base);
    font-size: var(--fs-xs);
    color: var(--fg-4);
}
.posterior-entropy .val {
    font-family: var(--font-mono);
    color: var(--fg-2);
    font-weight: 600;
}

/* ── Factor waterfall ──────────────────────────────────────── */
.waterfall-row {
    display: grid;
    grid-template-columns: 1fr 2fr 56px;
    align-items: center;
    gap: 10px;
    padding: 4px 0;
    font-size: var(--fs-sm);
}
.waterfall-row .label {
    color: var(--fg-2);
}
.waterfall-track {
    height: 8px;
    background: var(--bg-page);
    border-radius: 4px;
    position: relative;
    overflow: hidden;
}
.waterfall-fill {
    display: block;
    height: 100%;
    border-radius: 4px;
}
.waterfall-fill.pos {
    background: var(--color-brand-green);
}
.waterfall-fill.neg {
    background: var(--sev-high-fg);
}
.waterfall-row .val {
    text-align: right;
    font-family: var(--font-mono);
    font-weight: 700;
}
.waterfall-row .val.pos {
    color: var(--color-brand-green);
}
.waterfall-row .val.neg {
    color: var(--sev-high-fg);
}
.waterfall-total {
    display: grid;
    grid-template-columns: 1fr 56px;
    gap: 10px;
    margin-top: 10px;
    padding-top: 10px;
    border-top: 1px solid var(--border-base);
    font-weight: var(--fw-semibold);
}
.waterfall-total .val {
    text-align: right;
    font-family: var(--font-mono);
    font-weight: 700;
    color: var(--fg-1);
}
.waterfall-footer {
    margin-top: 12px;
    font-size: var(--fs-xs);
    color: var(--fg-4);
}

/* ── Position quality (positioning rail) ────────────────────────────────────
   The method's own colour on a dot, the tier name muted beside its rank, and a
   confidence that reads as a figure rather than a boolean. Flat fills only. */
.pos-quality-method {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-weight: 500;
}
.pos-quality-dot {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    flex: none;
    /* Set by .pos-method--{key} on the same element, the palette the fix trail
       also reads — one method is one colour across the tab. */
    background: var(--pos-tier-c, var(--fg-5));
}
.pos-quality-tier-name {
    display: block;
    font-size: var(--fs-xs);
    font-weight: 400;
    color: var(--fg-5);
}
.pos-quality-conf {
    font-variant-numeric: tabular-nums;
}
.pos-quality-conf.is-low {
    color: var(--status-warning-fg);
    font-weight: 600;
}
.pos-quality-conf.is-none {
    color: var(--fg-5);
}

/* ── Per-method colour (the fix trail) ──────────────────────────────────────
   ``.pos-method--{key}`` carries a method's colour as --pos-tier-c, which the
   Fix-quality trail's bars and its legend swatches read.

   These were defined with the tier chip and outlived it: the chip is gone, the
   trail still colours by method. Deleting them with the rest greyed the trail
   out through the var() fallback, silently — the design-drift gate caught it as
   an undefined token, which is exactly the failure that gate is for. */
.pos-method--none {
    --pos-tier-c: var(--fg-5);
} /* not computed */
.pos-method--radial {
    --pos-tier-c: var(--tier-rad);
} /* 1 gateway — blue */
.pos-method--bilateration {
    --pos-tier-c: var(--tier-bi);
} /* 2 gateways — orange */
.pos-method--trilateration {
    --pos-tier-c: var(--tier-tri);
} /* 3+ gateways — green */
.pos-method--cluster {
    --pos-tier-c: var(--tier-cluster);
} /* cluster prior — violet */
.pos-method--terrain {
    --pos-tier-c: var(--tier-terrain);
} /* terrain + habitat — teal (strongest) */

/* ── Placement caveats (positioning rail) ───────────────────────────────────
   Why a fix is uncertain, in the engine's own customer wording, beside the
   accuracy_m the Inspector reports (never a bare number). Calm, flat tints,
   1px borders, no bevels. Tone is the severity axis: review=red, caution=amber,
   info=neutral — a single-gateway fix is informational, never a warning. */
.pos-caveat {
    display: flex;
    gap: 8px;
    align-items: baseline;
    border: 1px solid var(--border-base);
    border-radius: 8px;
    padding: 7px 10px;
    margin-bottom: 6px;
}
.pos-caveat-sev {
    flex: none;
    font-size: var(--fs-xs); /* was 10.5px -> 11px */
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    padding: 1px 7px;
    border-radius: 999px;
    border: 1px solid currentColor;
    white-space: nowrap;
}
.pos-caveat-text {
    font-size: var(--fs-sm); /* was 12.5px -> 12px */
    line-height: 1.45;
    color: var(--fg-body-strong);
}
.pos-caveat.is-review {
    border-color: var(--danger-soft-fg);
    background: var(--sev-high-bg);
}
.pos-caveat.is-review .pos-caveat-sev {
    color: var(--danger-strong-fg);
}
.pos-caveat.is-caution {
    border-color: var(--policy-looser-bg);
    background: var(--sev-medium-bg);
}
.pos-caveat.is-caution .pos-caveat-sev {
    color: var(--warning-strong-fg);
}
.pos-caveat.is-info {
    border-color: var(--border-base);
    background: var(--bg-page);
}
.pos-caveat.is-info .pos-caveat-sev {
    color: var(--fg-3);
}
.pos-caveat-more {
    margin-top: 2px;
}
.pos-caveat-more > summary {
    cursor: pointer;
    font-size: var(--fs-sm); /* was 11.5px -> 12px */
    font-weight: 600;
    color: var(--fg-4);
    padding: 2px 0 6px;
    list-style: none;
}
.pos-caveat-more > summary::-webkit-details-marker {
    display: none;
}
.pos-caveat-more > summary::before {
    content: "\25B8";
    display: inline-block;
    margin-right: 6px;
    transition: transform 0.12s ease;
}
.pos-caveat-more[open] > summary::before {
    transform: rotate(90deg);
}

.pos-tier-terrain-badge {
    display: inline-block;
    font-size: var(--fs-sm); /* was 11.5px -> 12px */
    font-weight: 600;
    color: var(--color-brand-green-hover);
    border: 1px solid var(--pos-terrain-border);
    border-radius: 8px;
    padding: 4px 9px;
    margin-bottom: 8px;
}
.pos-tier-cluster {
    font-size: var(--fs-sm); /* was 12.5px -> 12px */
    color: var(--fg-body-strong);
    margin-bottom: 8px;
}
.pos-tier-prov {
    font-family: ui-monospace, Menlo, monospace;
    font-size: var(--fs-xs);
    color: var(--fg-5);
}

/* ── Habitat & placement pills (device-class catalog metadata) ──────────────
   Flat fills, 1px borders, no bevels; per-value colour via the .dd-pp--<v>
   modifier. Mirrors design/03-device-habitat-placement-pills.html. */
.dd-pp-group {
    display: inline-flex;
    flex-wrap: wrap;
    gap: 6px;
    align-items: center;
}
.dd-pp {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    font-size: var(--fs-sm);
    font-weight: 500;
    padding: 2px 9px;
    border-radius: 999px;
    border: 1px solid var(--border-base);
    background: var(--bg-card);
    color: var(--fg-3);
    white-space: nowrap;
}
.dd-pp i {
    font-size: var(--fs-xs);
}
.dd-pp--land {
    background: var(--status-resolved-bg);
    color: var(--pos-land-fg);
    border-color: var(--flash-success-bg);
}
.dd-pp--water {
    background: var(--status-info-bg);
    color: var(--pos-water-fg);
    border-color: var(--info-bg);
}
.dd-pp--amph {
    background: var(--status-online-bg);
    color: var(--status-online-fg);
    border-color: var(--banner-soft-end);
}
.dd-pp--indoor {
    background: var(--row-highlight);
    color: var(--pos-indoor-fg);
    border-color: var(--pos-indoor-border);
}
.dd-pp--outdoor {
    background: var(--status-resolved-bg);
    color: var(--pos-land-fg);
    border-color: var(--flash-success-bg);
}
.dd-pp--mixed {
    background: var(--status-warning-bg);
    color: var(--status-warning-fg);
    border-color: var(--sev-medium-border);
}
.dd-pp--fixed {
    background: var(--status-neutral-bg);
    color: var(--fg-3);
    border-color: var(--border-base);
}
.dd-pp--nomadic {
    background: var(--status-warning-bg);
    color: var(--status-warning-fg);
    border-color: var(--sev-medium-border);
}
.dd-pp--mobile {
    background: var(--status-info-bg);
    color: var(--pos-water-fg);
    border-color: var(--info-bg);
}
.dd-pp--unknown {
    background: var(--bg-page);
    color: var(--fg-5);
    border-color: var(--border-base);
    border-style: dashed;
}

/* ── Positioning tier trail (device Positioning tab) ────────────────────────
   Sparkline of recent fixes over time; bar height = tier rank (the climb),
   colour = the pos-tier--<rank> ladder (shared with the chip). */
/* ── Narrowing chain ──────────────────────────────────────────────────────
   The funnel that explains one fix: every candidate cell the engine started
   with and the rule that removed each slice. Sits between the tier chip and
   the map, so it shares the chip's card shape rather than inventing one.

   Each row's two segments are sized by the view model against the STARTING
   count, not the step's own input — otherwise a step halving 40 cells would
   draw wider than the one that removed 3,000. */
.pos-narrowing {
    border: 1px solid var(--border-base);
    border-radius: var(--radius-lg);
    background: var(--bg-card);
    padding: 12px 16px 14px;
    margin-bottom: 16px;
}
.pos-narrowing-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 8px;
    margin-bottom: 8px;
}
.pos-narrowing-title {
    font-family: var(--font-display);
    font-weight: 500;
    font-size: var(--fs-base);
    color: var(--fg-1);
}
.pos-narrowing-sub {
    font-size: var(--fs-sm);
    color: var(--fg-5);
    font-variant-numeric: tabular-nums;
}
.pos-narrowing-headline {
    display: flex;
    align-items: baseline;
    gap: 8px;
    flex-wrap: wrap;
    margin-bottom: 10px;
}
.pos-narrowing-big {
    font-family: var(--font-display);
    font-size: var(--fs-lg);
    font-variant-numeric: tabular-nums;
    color: var(--fg-1);
}
.pos-narrowing-arrow,
.pos-narrowing-pct {
    font-size: var(--fs-sm);
    color: var(--fg-5);
}
.pos-narrowing-steps {
    list-style: none;
    margin: 0;
    padding: 0;
}
.pos-narrowing-step {
    display: grid;
    grid-template-columns: 26px 1fr;
    gap: 10px;
    padding: 6px 0;
    border-top: 1px solid var(--border-base);
}
.pos-narrowing-step:first-child {
    border-top: 0;
}
.pos-narrowing-ord {
    font-size: var(--fs-xs);
    color: var(--fg-5);
    font-variant-numeric: tabular-nums;
    padding-top: 4px;
}
.pos-narrowing-bar {
    position: relative;
    display: block;
    height: 20px;
    border-radius: var(--radius-xs);
    background: var(--status-neutral-bg);
    overflow: hidden;
}
/* Survivors then removed, left to right: the eye reads the bar as the set
   shrinking rather than as two unrelated quantities. */
.pos-narrowing-kept {
    position: absolute;
    inset: 0;
    width: var(--keep-pct, 0%);
    background: var(--color-brand-green);
}
.pos-narrowing-cut {
    position: absolute;
    top: 0;
    bottom: 0;
    left: var(--keep-pct, 0%);
    width: var(--cut-pct, 0%);
    background: var(--status-neutral-fg);
}
.pos-narrowing-lbl {
    display: flex;
    justify-content: space-between;
    gap: 10px;
    flex-wrap: wrap;
    margin-top: 3px;
    font-size: var(--fs-sm);
}
.pos-narrowing-name {
    font-weight: 600;
    color: var(--fg-1);
}
.pos-narrowing-nums {
    color: var(--fg-5);
    font-variant-numeric: tabular-nums;
}
.pos-narrowing-why {
    color: var(--fg-5);
    font-size: var(--fs-xs);
}
/* A step that declined to empty the set is rare and consequential, so it
   reads at danger weight rather than as another grey note. */
.pos-narrowing-refused {
    color: var(--status-danger-fg);
    font-size: var(--fs-xs);
    font-weight: 600;
}
/* A rule that never fired is still worth seeing — dimmed, not hidden. */
.pos-narrowing-step--noop .pos-narrowing-name {
    color: var(--fg-5);
    font-weight: 500;
}
.pos-narrowing-foot {
    margin: 10px 0 0;
    font-size: var(--fs-sm);
    color: var(--fg-5);
}

.pos-trail {
    border: 1px solid var(--border-base);
    border-radius: var(--radius-lg);
    background: var(--bg-card);
    padding: 12px 16px 14px;
    margin-bottom: 16px;
}
.pos-trail-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 8px;
    margin-bottom: 10px;
}
.pos-trail-title {
    font-family: var(--font-display);
    font-weight: 500;
    font-size: var(--fs-base);
    color: var(--fg-1);
}
.pos-trail-sub {
    font-size: var(--fs-sm); /* was 11.5px -> 12px */
    color: var(--fg-5);
    font-variant-numeric: tabular-nums;
}
.pos-trail-track {
    display: flex;
    align-items: flex-end;
    gap: 2px;
    /* Half the original 64px. The bars carry one variable — accuracy scaled
       across the series — so halving costs travel, not meaning: 22px of range
       instead of 44px. The overall shape survives; differences between adjacent
       fixes stop being readable, which is the accepted trade for 84px of a
       page whose subject is the map. */
    height: 32px;
    overflow-x: auto;
}
.pos-trail-bar {
    flex: 1 0 4px;
    min-width: 4px;
    border-radius: 2px 2px 0 0;
    background: var(--pos-tier-c, var(--fg-5));
    /* Floor keeps the worst fix visible; a zero-height bar reads as missing
       data rather than as the widest reading in the series. Halved with the
       track so the proportions are unchanged. */
    height: calc(7px + var(--q, 0) * 22px);
}

/* The bar heights are relative to this device's own best and worst fix, so
   the scale is printed under the track. */
.pos-trail-scale {
    margin: 8px 0 0;
    font-size: var(--fs-sm);
    color: var(--fg-5);
    font-variant-numeric: tabular-nums;
}

/* Colour key beneath the trail — reads the same method palette as the bars,
   so the operator never has to hover to learn what a colour means. */
.pos-trail-legend {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px 14px;
    margin-top: 12px;
    font-size: var(--fs-sm); /* was 11.5px -> 12px */
    color: var(--fg-4); /* muted */
}
.pos-trail-key {
    display: inline-flex;
    align-items: center;
    gap: 6px;
}
.pos-trail-sw {
    width: 10px;
    height: 10px;
    border-radius: 2px;
    background: var(--pos-tier-c, var(--fg-5));
    flex: 0 0 10px;
}
.pos-trail-legend .link-glossary {
    margin-left: auto;
}

/* ── Per-gateway colour coding (device Activity/Signal) ───────────────────────
   Gateway EUIs differ by one character, so colour is the real differentiator.
   ``--gw`` carries the gateway's chart-palette colour (set inline); the dot +
   pill outline use it so the rail card, receptions table and signal chart stay
   colour-matched for the same gateway. */
.dd-gw-swatch {
    display: inline-block;
    width: 9px;
    height: 9px;
    border-radius: 50%;
    background: var(--gw, var(--color-gray-600));
    flex: 0 0 9px;
}
.dd-gw-pill {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 2px 10px;
    border-radius: 9999px;
    border: 1.5px solid var(--gw, var(--border-base));
    background: var(--bg-card);
    font-size: var(--fs-sm);
    color: var(--bg-sidebar);
    white-space: nowrap;
}
/* The rail gateway name is a green internal link; keep that colour even though
   it sits inside the card-wide anchor, and let the colour dot sit before it. */
.dd-rail-gateway .dd-rail-gateway-name.link-internal {
    color: var(--link-internal-fg);
}

/* Last-activity staleness colour coding. act-fresh keeps the default ink; only
   a stale (>24h, amber) or old (>7d, red) device draws the eye. */
.act-stale {
    color: var(--status-warning-fg);
}
.act-old {
    color: var(--status-danger-fg);
}
.act-never {
    color: var(--fg-5);
}

/* ── Gateway calibration state pill (v0.10.5) ─────────────────────────────
   The calibration-state badge, shared by the gateway list state column and the
   detail Configuration card's "Calibration" row (the card itself is the shared
   info_panel chrome). Flat fills only — bordered tint pills, no
   bevels/shadows/gradients. Icon + label, never colour alone (house rule). */
.cal-state {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 3px 9px;
    border-radius: 9999px;
    border: 1px solid transparent;
    font-size: var(--fs-sm);
    font-weight: 600;
    line-height: 1.4;
    white-space: nowrap;
}
.cal-state i {
    font-size: var(--fs-xs);
}
.cal-tone-success {
    background: var(--status-online-bg);
    border-color: var(--policy-stricter-bg);
    color: var(--color-brand-green-deep);
}
.cal-tone-warning {
    background: var(--banner-warning-bg);
    border-color: var(--banner-warning-border);
    color: var(--warning-strong-fg);
}
.cal-tone-muted {
    background: var(--status-neutral-bg);
    border-color: var(--border-base);
    color: var(--fg-4);
}
.cal-tone-neutral {
    background: var(--bg-page);
    border-color: var(--border-base);
    border-style: dashed;
    color: var(--fg-5);
}

/* ── Predicted-cell breakdown (positioning-map-breakdown.js) ────────────
   The 49 stored r11 rows inside one drawn r9 hexagon, in the 240 px rail.
   Authored against the 210 px content box (.dd-rail-card-body's padding), so
   every width here is a fraction and nothing assumes a wider column.

   At 49 cells each hex is ~28 px, which is under the 44 px touch target. That
   is accepted rather than solved by paging or zooming: the grid's job is to
   show the WHOLE cell at once — a paged grid would hide exactly the thing it
   exists to reveal. Below 1100 px the stage collapses to one column and the
   rail goes full width, so the touch case gets the larger rendering.

   Colours come from the --device-gray-* scale and --color-brand-green already
   in this sheet. The only new values are the metric ramp, which lives in JS
   (BREAKDOWN_RAMP) because it is data-driven, not chrome. */
/* First block in the inspector, so no top rule or gap — the divider belongs
   BELOW it, separating the grid from the summary it explains. */
.pm-bd {
    margin-bottom: 0.75rem;
    padding-bottom: 0.625rem;
    border-bottom: 1px solid var(--device-gray-100);
}
.pm-bd-h {
    margin: 0;
    font-size: var(--fs-sm);
    font-weight: 600;
    color: var(--device-gray-900);
}
.pm-bd-sub {
    margin: 0.125rem 0 0.5rem;
    font-size: var(--fs-xs);
    line-height: 1.45;
    color: var(--device-gray-500);
}
.pm-bd-metric {
    width: 100%;
    margin-bottom: 0.5rem;
    padding: 0.3125rem 0.4375rem;
    font: inherit;
    font-size: var(--fs-sm);
    color: var(--device-gray-900);
    background: var(--bg-card);
    border: 1px solid var(--device-gray-200);
    border-radius: 0.375rem;
}
.pm-bd-metric:focus-visible {
    outline: 2px solid var(--color-brand-green);
    outline-offset: 1px;
}
.pm-bd-comb {
    display: block;
    width: 100%;
    height: auto;
}
/* Stroke is the card background, so adjacent cells read as separate hexes
   without a border colour competing with the fill. */
.pm-bd-hx {
    stroke: var(--gw-marker-halo);
    stroke-width: 0.5;
    cursor: pointer;
}
/* A cell the read never returned: outline only, so a partial hexagon looks
   partial. Drawing it filled — or omitting it — would both say the read
   covered ground it did not. */
.pm-bd-hx.is-absent {
    fill: none;
    stroke: var(--device-gray-300);
    stroke-width: 0.4;
    stroke-dasharray: 1 1;
    cursor: default;
}
.pm-bd-hx:hover {
    opacity: 0.7;
}
.pm-bd-hx:focus-visible,
.pm-bd-hx.is-selected {
    outline: none;
    stroke: var(--device-gray-900);
    stroke-width: 1.4;
}
/* The row the rollup printed as the whole hexagon's value. */
.pm-bd-crown {
    fill: none;
    stroke: var(--device-gray-900);
    stroke-width: 1;
    pointer-events: none;
}
.pm-bd-legend {
    display: flex;
    align-items: center;
    gap: 0.25rem;
    margin: 0.375rem 0 0;
    font-size: var(--fs-2xs);
    line-height: 1.45;
    color: var(--device-gray-500);
}
.pm-bd-dot {
    flex: none;
    width: 0.375rem;
    height: 0.375rem;
    border: 1px solid var(--device-gray-900);
    border-radius: 9999px;
}
/* The gradient strip. An earlier revision printed the range as text only, so
   the ramp the cells were painted with was never actually shown. */
.pm-bd-rampbar {
    display: flex;
    align-items: center;
    gap: 0.375rem;
    margin: 0.375rem 0 0;
}
.pm-bd-strip {
    flex: 1;
    min-width: 3.75rem;
    height: 0.4375rem;
    border-radius: 0.25rem;
}
/* A field whose spread is below its epsilon: greyed, because drawing a live
   gradient over 0.00006 of variation is the map overstating what it knows. */
.pm-bd-strip.is-flat {
    background: var(--device-gray-300);
}
.pm-bd-end {
    font-size: 0.5625rem;
    font-variant-numeric: tabular-nums;
    color: var(--device-gray-500);
    white-space: nowrap;
}
.pm-bd-flat {
    margin: 0.25rem 0 0;
    font-size: 0.5625rem;
    font-style: italic;
    line-height: 1.4;
    color: var(--device-gray-500);
}
.pm-bd-sect {
    margin: 0.5rem 0 0.25rem;
    font-size: 0.5625rem;
    font-weight: 700;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--device-gray-500);
}
/* Range bar: the span across all children, marked where this one falls. Not a
   min/median/max bar — a child here is ONE row, so the question is how it
   compares to its siblings, not how varied it is inside itself. */
.pm-bd-row {
    margin-bottom: 0.4375rem;
}
.pm-bd-row-h {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.5rem;
    margin-bottom: 0.125rem;
    font-size: var(--fs-2xs);
    color: var(--device-gray-500);
}
/* The zone a value falls in, beside its label — the colour's meaning in
   words, so the strip is not the only way to read the grid. */
.pm-bd-zone {
    font-style: normal;
    font-size: 0.5625rem;
    letter-spacing: 0.03em;
    text-transform: uppercase;
    color: var(--device-gray-400);
}
.pm-bd-row-h b {
    font-size: var(--fs-xs);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    color: var(--device-gray-900);
}
.pm-bd-row.is-active .pm-bd-row-h {
    color: var(--color-brand-green-deep);
}
.pm-bd-track {
    position: relative;
    height: 0.375rem;
    border-radius: 0.1875rem;
    background: var(--device-gray-100);
}
.pm-bd-fill {
    position: absolute;
    inset: 0;
    border-radius: 0.1875rem;
    background: var(--color-brand-green);
    opacity: 0.28;
}
.pm-bd-mark {
    /* The marker's position is the datum; the renderer hands it over as
       --pm-bd-at rather than writing a left rule from JS. */
    left: calc(var(--pm-bd-at, 0%) - 1px);
    position: absolute;
    top: -0.125rem;
    width: 2px;
    height: 0.625rem;
    border-radius: 1px;
    background: var(--color-brand-green-hover);
}
/* A flat field gets a hatched empty track: there is no position to mark, and a
   marker at an arbitrary point would invent one. */
.pm-bd-row.is-flat .pm-bd-track {
    background: repeating-linear-gradient(
        90deg,
        var(--device-gray-100) 0 3px,
        transparent 3px 6px
    );
}
.pm-bd-ends {
    display: flex;
    justify-content: space-between;
    margin-top: 0.0625rem;
    font-size: 0.5625rem;
    font-variant-numeric: tabular-nums;
    color: var(--device-gray-500);
}
.pm-bd-tag {
    margin-left: 0.3125rem;
    padding: 0.0625rem 0.3125rem;
    font-size: 0.5rem;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    vertical-align: 0.0625rem;
    color: var(--device-gray-700);
    border: 1px solid var(--device-gray-300);
    border-radius: 9999px;
}
.pm-bd-child {
    margin: 0.625rem 0 0;
    font-size: var(--fs-sm);
    font-weight: 600;
    color: var(--device-gray-900);
}
.pm-bd-id {
    margin: 0.0625rem 0 0.375rem;
    font-size: var(--fs-2xs);
    color: var(--device-gray-500);
    overflow-wrap: anywhere;
}
.pm-bd-hint,
.pm-bd-note {
    margin: 0.25rem 0 0;
    font-size: var(--fs-2xs);
    line-height: 1.45;
    color: var(--device-gray-500);
}
.pm-bd-hint {
    font-style: italic;
}
@media (prefers-reduced-motion: no-preference) {
    .pm-bd-hx {
        transition: opacity 0.12s ease;
    }
}

/* A dot-plus-count run inside a detail header, e.g. "* 3 active". */
.dd-inline-meta {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
}

/* The aux figure in a rail card header, at normal weight. The header is bold
   display type; a running total beside it is not a heading. The call site used
   to write font-weight inline and restate text-transform and letter-spacing
   that .dd-rail-card-header-aux already sets. */
.dd-rail-card-header-aux--light {
    font-weight: 500;
}
