/* VAPE — sleek, restrained dark UI, modeled on x402scan.com's card layout:
   a clean sans-serif for UI text (titles, labels, body, buttons), monospace
   reserved for real data (addresses, tx hashes, tabular numbers, CLI
   examples) — filled, rounded-corner panels one shade lighter than the page
   background, not flat hairline boxes. Functional data-colors (verdict
   severity, threat level, P&L sign, honeypot flag) are the one deliberate
   exception to "everything else is white/zinc" — they stay wherever the
   markup/JS defines them, since they're real data encoding, not chrome. */

:root {
    --font-sans: 'Inter', ui-sans-serif, system-ui, -apple-system, sans-serif;
    --font-mono: 'JetBrains Mono', ui-monospace, monospace;
    font-family: var(--font-sans);
    /* One accent, used sparingly (live dot, hairline highlights) — every
       other color on the page is white/zinc unless it's encoding real data. */
    --accent: #4ade80;
    --accent-dim: rgba(74, 222, 128, 0.14);
    --border: rgba(255, 255, 255, 0.10);
    /* Page background stays near-black; panels sit one step lighter so they
       read as distinct cards, matching x402scan's dark-grey-on-black offset. */
    --bg-page: #09090b;
    --bg-panel: #131316;
    --bg-panel-sm: #15151a;

    /* ── Reading palette ──────────────────────────────────────────────────
       Semantic text tokens, not raw hex at each call site. The Wire's
       article typography previously hardcoded #f4f4f5, #d4d4d8, #a1a1aa and
       #71717a directly in a dozen rules, which is what made a light mode
       impossible to add: there was no single place to change. These four
       cover the whole hierarchy a story needs.

       The values are tuned for long-form reading on a near-black ground.
       Pure white body text on true black is the classic mistake -- the
       contrast is so high it haloes and tires the eye over several hundred
       words -- so body sits a step below white, and headings sit a step
       BELOW body rather than above it. That inversion is deliberate and is
       what a reader registers as "editorial" rather than "interface": the
       prose is the brightest thing on the page because the prose is the
       point, and the heading is a quieter signpost above it. */
    --text-body: #e6e6e9;
    --text-heading: #b8b8bf;
    --text-muted: #8a8a93;
    --text-source: #9aa4b2;
    --link: #93b4ff;
    --link-hover: #b9cdff;

    /* Long-form measure. Around 68 characters is the span most typography
       research converges on for sustained reading; much wider and the eye
       loses the line return. */
    --measure: 68ch;
    --reading-size: 1.0625rem;
    --reading-leading: 1.72;

    --radius-lg: 14px;
    --radius-md: 10px;
    --radius-sm: 8px;

    /* Security Dashboard's 5-bucket severity taxonomy (see
       agents/build_security_dashboard.py::normalize_severity). A custom
       high-signal ramp (not the dataviz skill's generic green/amber/red
       default, which read as a literal stoplight and washed out the
       dashboard in solid green whenever most lanes were passing) — LOW
       through CRITICAL run blue -> amber -> orange -> magenta, a cohesive
       "instrumentation" palette distinct from the site's own green
       --accent so functional data readouts don't get mistaken for brand
       chrome. LOW was cyan/turquoise originally; swapped to a clean blue
       (same "cool tone anchors the safe end of a warm danger ramp" logic,
       just a different cool hue) since turquoise wasn't wanted on the
       site. Every consumer of these tokens pairs the color with an icon
       + text label (see statusBadge()/secdash-badge), so adjacent-step
       similarity under color-vision deficiency is mitigated rather than
       the sole carrier of meaning. INFO is deliberately NOT part of that
       ramp: it means "no severity signal at all", not "checked and safe",
       so it gets a plain muted-ink gray with zero rating connotation.

       Theme-invariant on purpose, per this file's own opening comment:
       functional data colors are the one exception to the neutral palette
       and do not switch with light/dark. A previous edit accidentally
       spliced this whole block (and the --city-* block below it) inside
       :root[data-theme="light"] by closing the base :root one property
       too early -- which meant these were simply undefined in the
       default dark theme: a black donut, a black gauge, and unlit
       verdict bars on the Security Dashboard, and a washed-out VAPE Ave
       with no building or terrain color at all. Keep this in the base
       :root; only genuinely theme-varying tokens belong in the light
       block below. */
    --sev-critical: #ff3d78;
    --sev-high: #ff8a3d;
    --sev-medium: #ffcf40;
    --sev-low: #3b82f6;
    --sev-info: #71717a;
    /* VAPE Ave's 7 landmark-kind hues (docs/assets/cityscape.js's
       "Overview" data layer — building identity, not status). Validated as
       a set against this panel background (dataviz skill's
       validate_palette.js "#3987e5,#d95926,#199e70,#e66767,#008300,#d55181,
       #9085e9" --mode dark --surface #131316 -> all checks pass, one
       adjacent CVD pair in the legal 6-8 WARN band, mitigated the same way
       every other city building already is: an icon + text label, never
       color alone). Deliberately its own ramp, distinct from --sev-* (the
       "Health" layer's status colors), so the two toggleable layers never
       share a palette to get confused with each other.
       Order fixed: reordering changes which pairs are adjacent and can flip
       a passing set back to failing -- re-run the validator before touching it. */
    --city-precinct: #3987e5;
    --city-foundry: #d95926;
    --city-newsroom: #199e70;
    --city-watchtower: #e66767;
    --city-mint: #008300;
    --city-tower: #d55181;
    --city-vault: #9085e9;
    /* Two more landmark hues added alongside The Skillforge and The Data
       Bureau -- chosen to sit far from the 7 validated above (amber/gold
       vs. every existing hue; steel blue-gray vs. precinct's brighter,
       more saturated blue), but not yet re-run through
       validate_palette.js as a 9-hue set -- do that before relying on
       these two for anything beyond "icon + label" identification, same
       mitigation every building here already carries regardless. */
    --city-skillforge: #c9932f;
    --city-databureau: #6b8fae;
    /* The surrounding world's 4 terrain hues (docs/city.html's full mode
       only) -- atmosphere, not categorical data encoding, so unlike the 7
       hues above these aren't part of a validated adjacency-checked set;
       chosen only to read as a quiet, low-saturation backdrop distinct
       from every building-identity/status color already in use. */
    --city-terrain-ocean: #12283f;
    --city-terrain-river: #1a3a54; /* lighter/more teal than ocean -- reads as fresh water, not a second sea */
    --city-terrain-mountain: #4a4038;
    --city-terrain-farmland: #5c5a34;
    /* Neutralized from #232329 -- carried the same unwanted blue undertone
       real phone screenshots flagged on the road/ground colors in
       cityscape.js's _drawRoads()/_drawDowntownGround(); this is also the
       mid-rotation "spinning" fallback fill, so the tint showed up moving
       through the city, not just sitting still on distant vacant land. */
    --city-terrain-vacant: #232323;
    /* The wider island's own vegetated countryside -- everywhere that
       isn't downtown's reserved-growth margin (which keeps the deliberately
       neutral --city-terrain-vacant above) or one of the named regions
       (ocean/river/mountain/farmland). Distinct from farmland's yellow-olive
       tilled-row look: a plain grassy green, real vegetated land rather than
       flat near-black void. */
    --city-terrain-plains: #38472e;
}

/* Light mode. Only the tokens change -- every rule below is written against
   the token, so the whole site inverts without a second stylesheet.
   Deliberately warm off-white rather than pure #fff, for the same reason the
   dark ground is not pure black: an unbroken maximum-luminance field is
   fatiguing to read against. */
:root[data-theme="light"] {
    --border: rgba(9, 9, 11, 0.12);
    --bg-page: #f7f6f4;
    --bg-panel: #ffffff;
    --bg-panel-sm: #fbfaf9;
    --text-body: #1f1f23;
    --text-heading: #55555f;
    --text-muted: #6f6f79;
    --text-source: #4a5568;
    --link: #2a53c4;
    --link-hover: #1a3ba0;
    --accent-dim: rgba(22, 163, 74, 0.14);
}

/* Site-wide base scale — the previous 16px root made every Tailwind rem-based
   utility (text-sm, text-xs, padding, gaps) read oversized once real content
   density went up (search bars, pagination, dense data rows). 14px root
   tightens all of that proportionally in one place rather than hand-editing
   hundreds of utility classes. */
html { scroll-behavior: smooth; font-size: 87.5%; }

* { box-sizing: border-box; }

body {
    background: var(--bg-page);
    color: #d4d4d8;
    font-family: var(--font-sans);
    -webkit-font-smoothing: antialiased;
}

.font-mono { font-family: var(--font-mono); }

/* Anchor-linked sections need headroom under the sticky nav, or a jump-to-#id
   click hides the heading right behind the bar. */
section[id] { scroll-margin-top: 4.5rem; }

/* Section headings: the primary, bold title for each section — sans-serif,
   clearly heavier/larger than sub-labels (h3s) and body copy, matching
   x402scan's title/sub-label contrast (bold title, small muted labels below
   it) rather than every heading reading at body weight. */
.section-title {
    font-size: 1.0625rem;
    font-weight: 700;
    color: #f4f4f5;
    letter-spacing: -0.01em;
}

/* Hero headline is the single largest, boldest piece of type on the page —
   sans-serif, same "> " prompt prefix kept for identity. */
.hero-title {
    font-family: var(--font-sans);
    font-size: 1.375rem;
    line-height: 1.3;
    font-weight: 800;
    letter-spacing: -0.015em;
    color: #f4f4f5;
}
@media (min-width: 640px) {
    .hero-title { font-size: 1.625rem; }
}
@media (min-width: 1024px) {
    .hero-title { font-size: 2.5rem; line-height: 1.15; }
}

/* Bracket-tag eyebrow — small, muted sub-label ahead of a section title,
   matching x402scan's "Resources / Tags / Activity" treatment. */
.eyebrow {
    font-size: 0.75rem;
    font-weight: 500;
    color: #71717a;
    white-space: normal;
}
.eyebrow i { margin-right: 0.25rem; }

/* A "panel" is a filled, rounded card — one shade lighter than the page
   background — matching x402scan's dark-grey-card-on-black offset, not a
   flat hairline frame. */
.panel {
    background: var(--bg-panel);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    padding: 1.25rem;
}
@media (min-width: 640px) {
    .panel { padding: 1.75rem; }
}
.panel-sm {
    background: var(--bg-panel-sm);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    padding: 0.875rem;
}

/* A "popover" floats over page content, so it always needed a solid fill —
   now rounded to match every other panel. */
.popover {
    border: 1px solid var(--border);
    background: var(--bg-page);
    border-radius: var(--radius-md);
    font-family: var(--font-mono);
}

.term-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    padding: 0.6rem 1.2rem;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    color: #e4e4e7;
    font-size: 0.875rem;
    font-weight: 500;
    background: rgba(255, 255, 255, 0.03);
    transition: border-color 0.15s ease, color 0.15s ease, background-color 0.15s ease;
}
.term-btn:hover {
    border-color: rgba(255, 255, 255, 0.35);
    color: #fff;
}
.term-btn-active {
    border-color: var(--accent);
    color: var(--accent);
    background: var(--accent-dim);
}
.term-btn-sm {
    padding: 0.35rem 0.75rem;
    font-size: 0.75rem;
    border-radius: 6px;
}

/* Live pulse dot — the one place motion + the accent color draw attention,
   since it's a genuine liveness signal, not decoration. */
.live-dot { animation: pulse-ring 1.8s infinite; }
@keyframes pulse-ring {
    0%, 100% { opacity: 1; transform: scale(1); }
    50% { opacity: .4; transform: scale(.85); }
}

/* Numeric/tabular data stays monospace even though the rest of the UI is
   sans-serif now — tabular-nums alone doesn't align digits across a
   proportional-width sans face the way it does in a true monospace one. */
.stat { font-variant-numeric: tabular-nums; font-family: var(--font-mono); }

/* Live stat readout: a wrapping status line (label + tabular-nums value
   pairs), not a grid of individually-boxed tiles — reads as a terminal
   status bar rather than a dashboard of "widgets". */
.stat-line {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem 1.75rem;
}
.stat-pair {
    display: inline-flex;
    align-items: baseline;
    gap: 0.5rem;
    font-size: 0.875rem;
}
.stat-pair-link {
    display: inline-flex;
    align-items: baseline;
    gap: 0.5rem;
    font-size: 0.875rem;
    transition: color 0.15s ease;
}
.stat-pair-link:hover { color: #fff; }
.stat-pair-link:hover .stat-value { text-decoration: underline; }
.stat-label { color: #71717a; }
.stat-value { color: #d4d4d8; font-variant-numeric: tabular-nums; font-family: var(--font-mono); }

/* Plain stacked list divided by hairlines — replaces essentially every
   JS-rendered card grid on the page (protocols, movers, bounty ops,
   reports, investigations, tools, audits, feed rows). Never individually
   boxed/colored cards. */
.diff-list { border-top: 1px solid var(--border); }
/* No `display` here on purpose — every diff-row usage already carries its own
   Tailwind `flex` or `block` utility class alongside diff-row, and Tailwind's
   CDN stylesheet is injected at a DOM position whose load-order relative to
   this file isn't guaranteed (network-timing dependent, worse on slow/mobile
   connections). Declaring display:block here raced Tailwind's `.flex` at
   equal specificity and intermittently won, collapsing every flex row (movers,
   trending, new launches, wallets, protocol rows) into a
   vertical stack on mobile — the exact "chopping off" bug reported live. Bare
   <div class="diff-row"> (no utility) still defaults to block via the UA
   stylesheet, so this is safe to omit entirely. */
.diff-row {
    padding: 0.875rem 0;
    border-bottom: 1px solid var(--border);
}
a.diff-row { transition: opacity 0.15s ease; }
a.diff-row:hover { opacity: 0.75; }
/* A diff-row can also be a real <button> (e.g. a clickable protocol row that
   opens a detail modal) — reset the browser's default button chrome back to
   looking like every other plain row, keep the same hover opacity via .card-h. */
button.diff-row { width: 100%; text-align: left; background: none; border: none;
    border-bottom: 1px solid var(--border); font: inherit; color: inherit; cursor: pointer; }

/* Chart containers keep a substantial, desktop-like height across all
   breakpoints — Chart.js is told maintainAspectRatio:false and fills this. */
.chart-shell {
    position: relative;
    height: clamp(260px, 30vw, 380px);
    width: 100%;
}
.chart-shell-sm {
    position: relative;
    height: clamp(200px, 22vw, 260px);
    width: 100%;
}
/* The x402 Ledger's Volume & Revenue chart is the site's one "advanced
   technical chart" — deliberately taller than .chart-shell so it reads as
   a real desktop trading-terminal panel rather than a small preview sparkline,
   using the extra vertical room .panel-sm's full section width already gives it. */
/* The x402 Ledger's Volume & Revenue chart: a real two-pane trading-terminal
   layout, price-style pane over a volume pane, the same shape as any real
   market chart -- deliberately taller than .chart-shell so it reads as a
   desktop terminal panel rather than a preview sparkline. */
.chart-stack {
    position: relative;
    display: flex;
    flex-direction: column;
    height: clamp(360px, 38vw, 560px);
    width: 100%;
}
.chart-pane-main { flex: 0 0 68%; min-height: 0; }
.chart-pane-volume {
    flex: 1 1 auto;
    min-height: 0;
    /* Visually separates the two panes without a second panel border --
       the volume pane's own grid line at the top already does most of the
       work; this hairline finishes it so the split reads as one instrument
       with two readouts, not two unrelated charts stacked by accident. */
    border-top: 1px solid var(--border);
    margin-top: 4px;
    padding-top: 4px;
}

/* Chart ink. These exist as tokens because the ledger chart draws into a
   canvas: it cannot inherit a color the way the rest of the page does, so
   x402feed.js reads these at draw time and redraws on vape:themechange.
   --chart-up is the one color that does NOT tone down for legibility the
   way the rest of the site's palette does -- a trading chart's hero line is
   supposed to read as vivid at a glance, the way a real ticker does, and a
   muted version of it would undercut the exact "top of the line" feel this
   was rebuilt to have. */
:root {
    --chart-up: #2dd4bf;
    --chart-up-soft: rgba(45, 212, 191, 0.35);
    --chart-ink: #e2e8f0;
    --chart-volume: #60a5fa;
    --chart-volume-fill: rgba(96, 165, 250, 0.55);
    --chart-ma: #f0b429;
    --chart-grid: rgba(100, 116, 139, 0.15);
    --chart-axis: #64748b;
}
:root[data-theme="light"] {
    --chart-up: #0d9488;
    --chart-up-soft: rgba(13, 148, 136, 0.18);
    --chart-ink: #334155;
    --chart-volume: #2563eb;
    --chart-volume-fill: rgba(37, 99, 235, 0.45);
    --chart-ma: #b45309;
    --chart-grid: rgba(71, 85, 105, 0.16);
    --chart-axis: #64748b;
}

/* uPlot's own crosshair (.u-cursor-x/-y) is already a dashed line by
   default -- restyled here to read as a deliberate trading-terminal
   crosshair (theme ink, slightly heavier) rather than the library's stock
   grey. Scoped to the chart stack so it never touches uPlot instances used
   elsewhere on the site. */
.chart-stack .u-cursor-x, .chart-stack .u-cursor-y {
    border-color: var(--chart-axis) !important;
    opacity: 0.55;
}
.chart-stack .u-cursor-pt { z-index: 2; }
/* No built-in legend on either pane -- see the readout below. */
.chart-stack .u-legend { display: none; }

/* The floating top-left readout, the one genuinely "TradingView" element:
   a small fixed badge over the price pane showing exact values, updating
   on hover and holding the latest values at rest, instead of a legend row
   the reader has to look away from the chart to check. */
.chart-readout {
    position: absolute;
    top: 2px;
    left: 4px;
    z-index: 3;
    display: flex;
    flex-wrap: wrap;
    gap: 0 0.85rem;
    font-family: 'JetBrains Mono', ui-monospace, monospace;
    font-size: 11px;
    line-height: 1.6;
    pointer-events: none;
    color: var(--text-muted);
    text-shadow: 0 1px 3px var(--bg-panel-sm, #15151a);
}
.chart-readout .cr-label { color: var(--text-muted); margin-right: 0.3em; }
.chart-readout .cr-value { color: var(--text-body); font-weight: 500; }
.chart-readout .cr-up { color: var(--chart-up); }
.chart-readout .cr-volume { color: var(--chart-volume); }
.chart-readout .cr-ma { color: var(--chart-ma); }

/* Shown when there is nothing to draw, or when the chart library itself
   could not be fetched. An empty box would otherwise read as "no revenue",
   which is the one reading of this panel that must never happen by
   accident. */
.chart-empty {
    display: flex;
    align-items: center;
    justify-content: center;
    height: 100%;
    font-size: 12px;
    color: var(--text-muted, #8a8a93);
    text-align: center;
    padding: 0 1rem;
}

/* Offerings row — side-by-side cards with a visible horizontal scrollbar
   instead of a vertically stacked grid, so the full offering catalog (30+
   entries) doesn't push the rest of the Engagement Options panel down the
   page. Each card gets a fixed width + shrink-0 (see app.js's card
   template) since a flex row, unlike a grid, won't size children on its
   own. Scroll-snap makes the row settle on a card boundary rather than
   stopping mid-card after a swipe/scroll. */
.offerings-scroll {
    display: flex;
    overflow-x: auto;
    scroll-snap-type: x proximity;
    padding-bottom: 0.625rem;
    scrollbar-width: thin;
    scrollbar-color: rgba(255, 255, 255, 0.25) transparent;
}
.offerings-scroll > * {
    scroll-snap-align: start;
}
.offerings-scroll::-webkit-scrollbar {
    height: 6px;
}
.offerings-scroll::-webkit-scrollbar-track {
    background: transparent;
}
.offerings-scroll::-webkit-scrollbar-thumb {
    background: rgba(255, 255, 255, 0.2);
    border-radius: 3px;
}
.offerings-scroll::-webkit-scrollbar-thumb:hover {
    background: rgba(255, 255, 255, 0.35);
}

/* Security Dashboard — blue/white "high-tech console" theme. Two-tone
   glow (blue #60a5fa, already used by Featured Investigation/Bounty
   Command Center, plus a soft near-white for depth/contrast rather than a
   second hue) instead of a single flat accent or the site's global green
   --accent, so this section's chrome — panel glows, the top beam, active
   toggle pills, the lane <select>, lane dots — reads as one deliberately-
   designed console. Severity itself stays on the --sev-* ramp everywhere
   below regardless of this section's theme, since that encodes real data
   and is never reskinned for decoration. */
#security-dashboard {
    --sd-blue: #60a5fa;
    --sd-white: #d4d4d8;
    --sd-glow-blue: rgba(96, 165, 250, 0.14);
    --sd-glow-white: rgba(228, 228, 231, 0.08);
    position: relative;
    overflow: hidden;
}
#security-dashboard::before {
    content: '';
    position: absolute;
    top: 0; left: 0; right: 0;
    height: 2px;
    background: linear-gradient(90deg, transparent, var(--sd-blue) 35%, var(--sd-white) 65%, transparent);
    background-size: 200% 100%;
    animation: top-beam-sweep 7s linear infinite;
}
@keyframes top-beam-sweep {
    0% { background-position: 100% 0; }
    100% { background-position: -100% 0; }
}
@media (prefers-reduced-motion: reduce) { #security-dashboard::before { animation: none; } }
#security-dashboard .secdash-panel {
    position: relative;
    border-color: rgba(148, 163, 250, 0.14) !important;
    background-image: radial-gradient(ellipse 130% 90% at 0% 0%, var(--sd-glow-blue), transparent 60%),
                       radial-gradient(ellipse 120% 90% at 100% 100%, var(--sd-glow-white), transparent 65%);
    transition: border-color 0.2s ease;
}
#security-dashboard .secdash-panel:hover { border-color: rgba(148, 163, 250, 0.32) !important; }
#security-dashboard .eyebrow { color: var(--sd-blue); }
#security-dashboard .term-btn-active {
    border-color: var(--sd-blue);
    color: #c4d7fb;
    background: linear-gradient(90deg, rgba(96, 165, 250, 0.2), rgba(228, 228, 231, 0.12));
}
#security-dashboard .secdash-lanes-filter:focus, #security-dashboard #secdash-lanes-sort:focus {
    border-color: var(--sd-blue);
}

/* Compact panel title — smaller/tighter than the site's default h3 so six
   of these widgets can share one screen ("everything in one area," per
   the reference), not stack as full sections down the page. */
.secdash-panel { display: flex; flex-direction: column; }
.secdash-panel-title {
    font-size: 12px;
    color: #a1a1aa;
    margin-bottom: 0.15rem;
    text-transform: uppercase;
    letter-spacing: 0.02em;
}
/* One-line plain-language caption under each panel/section title, answering
   "what is this for" directly rather than leaving the reader to infer it
   from the widget alone. */
.secdash-panel-desc {
    font-size: 10.5px;
    color: #71717a;
    margin-bottom: 0.5rem;
    line-height: 1.35;
}

/* Threat-level arc gauge. A Chart.js doughnut rotated into a half-circle
   (rotation:-90, circumference:180) is the standard, accepted way to
   render a gauge with Chart.js without a plugin; legitimate here
   specifically because the underlying value is a real ordinal 3-state
   category (LOW/MEDIUM/HIGH from security_sweep.py::compute_threat_level),
   not a fabricated continuous dial — always paired with the explicit label
   below it so the reading is never color-alone. Sized to sit inside a
   compact card alongside the other Row 1 widgets, not as its own
   full-width section. */
.secdash-gauge-wrap {
    position: relative;
    height: 76px;
    width: 100%;
    max-width: 148px;
    margin: 0.25rem auto 0;
}
/* Risk Level: a compact horizontal strip (small gauge beside its label and
   text) rather than its own full-size panel — sized down further and
   paired side-by-side with Findings per Status on desktop (see the
   lg:grid-cols-2 row in index.html) since neither widget has enough real
   content to justify a full-width row on its own. The small live-dot next
   to the title is a genuine liveness cue (this reads every refresh cycle,
   like the x402 Ledger's own live indicators elsewhere on the site), not
   decoration. */
.secdash-gauge-strip { display: flex; flex-direction: row; align-items: center; gap: 0.75rem; padding: 0.65rem !important; height: 100%; }
.secdash-gauge-strip .secdash-gauge-wrap { height: 46px; width: 82px; max-width: 82px; margin: 0; flex-shrink: 0; }
.secdash-gauge-strip-text { flex: 1; min-width: 0; }
.secdash-gauge-strip-text #secdash-gauge-delta,
.secdash-gauge-strip-text #secdash-gauge-context { text-align: left; }
.secdash-health-dot {
    display: inline-block; width: 6px; height: 6px; border-radius: 999px;
    background: var(--sd-blue); margin-left: 0.35rem; vertical-align: middle;
}
.secdash-gauge-label {
    position: absolute;
    left: 50%;
    bottom: 2px;
    transform: translateX(-50%);
    text-align: center;
}

/* Findings Count: a real count+percentage list beside a small donut,
   matching the reference's list-plus-donut composition (more scannable
   than a bottom legend alone). min-width:0 lets the list shrink below its
   content width instead of pushing the fixed-width donut past the card's
   edge on narrow viewports (flex items default to min-width:auto). */
.secdash-sev-split { display: flex; align-items: center; gap: 0.6rem; flex: 1; }
.secdash-sev-list { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 0.4rem; font-size: 11.5px; }
.secdash-sev-list li { display: flex; align-items: center; gap: 0.45rem; }
.secdash-sev-chip { width: 8px; height: 8px; border-radius: 2px; flex-shrink: 0; }
.secdash-sev-label { color: #a1a1aa; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.secdash-sev-count { font-family: var(--font-mono); color: #e4e4e7; flex-shrink: 0; }
.secdash-sev-pct { font-family: var(--font-mono); color: #71717a; width: 3.2em; text-align: right; flex-shrink: 0; }
.secdash-sev-donut-wrap { position: relative; width: 76px; height: 76px; flex-shrink: 0; }
.secdash-sev-donut-total {
    position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center;
    pointer-events: none;
}
.secdash-sev-donut-total span:first-child { font-size: 7.5px; color: #71717a; text-transform: uppercase; }
.secdash-sev-donut-total span:last-child { font-size: 10.5px; color: #e4e4e7; font-family: var(--font-mono); }

.secdash-badge {
    display: inline-flex; align-items: center; gap: 0.35rem; align-self: flex-start;
    font-size: 10.5px; padding: 0.2rem 0.55rem; border-radius: 999px;
    border: 1px solid var(--border); white-space: nowrap;
}

/* Screen-reader-only utility — the visually-hidden real data equivalent
   for a chart, used where a visible always-on list would just repeat what
   the chart already shows sighted users. */
.sr-only {
    position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
    overflow: hidden; clip: rect(0, 0, 0, 0); clip-path: inset(50%); white-space: nowrap; border: 0;
}

/* ── VAPE Ave (docs/assets/cityscape.js) ─────────────────────────────────
   One renderer, two mount sizes: a smaller embedded instance inside
   #security-dashboard, and a huge full-bleed instance on docs/city.html.
   Both read the same data/city-state.json + the same live x402 feed and
   support the exact same interactions (drag/pinch-zoom/rotate/click,
   Overview/Health/Activity layers) -- "compact" only changes the viewport
   size and default zoom, never the capability or the data. */
.city-stage {
    position: relative;
    width: 100%;
    height: clamp(300px, 32vw, 380px);
    border-radius: var(--radius-md);
    overflow: hidden;
    background: radial-gradient(ellipse at 50% 30%, #17171d 0%, #0b0b0e 70%, #060608 100%);
    border: 1px solid var(--border);
    /* Compact and full are the same one interactive grid (drag/pinch-zoom/
       rotate/click), just a smaller embedded viewport -- claim the gesture
       outright here too so a one-finger drag pans the camera instead of
       scrolling the page underneath it. */
    touch-action: none;
    cursor: grab;
}
.city-stage:active { cursor: grabbing; }
/* Full mode is the same interaction, just full-bleed. 168px ~= sticky nav
   (~65px) + this page's eyebrow/title/description block above the stage;
   100dvh (not vh) so mobile browsers' address-bar show/hide doesn't leave
   the toolbar sitting under browser chrome. */
.city-stage--full {
    height: calc(100dvh - 168px);
    min-height: 420px;
    border-radius: 0;
    border-left: none;
    border-right: none;
}
.city-canvas { position: absolute; inset: 0; display: block; width: 100%; height: 100%; }

.city-toolbar {
    position: absolute; top: 0.6rem; left: 0.6rem; right: 0.6rem;
    display: flex; align-items: center; justify-content: space-between; gap: 0.5rem;
    flex-wrap: wrap; z-index: 2; pointer-events: none;
}
.city-toolbar > * { pointer-events: auto; }
/* touch-action: none on every real touch target inside the toolbar --
   not just the canvas underneath. A two-finger pinch on the stage is
   centered near the top of it more often than not, so one finger landing
   on a button group's own padding/background (not just its
   canvas.city-layer-btn) was enough for the browser to treat that touch
   point as eligible for its own native pinch-zoom, fighting the app's
   own gesture and reading as random scale jumps or the whole page's
   rendered bitmap re-rasterizing into a visibly gridded mess mid-gesture. */
.city-layers { display: flex; gap: 0.3rem; background: rgba(9,9,11,0.55); backdrop-filter: blur(6px); border: 1px solid var(--border); border-radius: 999px; padding: 3px; touch-action: none; }
.city-layer-btn {
    font-size: 10px; letter-spacing: 0.02em; color: #a1a1aa; background: transparent; border: none;
    padding: 4px 9px; border-radius: 999px; cursor: pointer; transition: background 0.15s ease, color 0.15s ease;
}
.city-layer-btn:hover { color: #e4e4e7; }
.city-layer-btn.is-active { background: rgba(255,255,255,0.12); color: #f4f4f5; }
.city-zoom { display: flex; gap: 2px; background: rgba(9,9,11,0.55); backdrop-filter: blur(6px); border: 1px solid var(--border); border-radius: 999px; padding: 2px; touch-action: none; }
.city-zoom-btn {
    width: 22px; height: 22px; display: flex; align-items: center; justify-content: center;
    color: #a1a1aa; background: transparent; border: none; border-radius: 999px; cursor: pointer; font-size: 11px;
}
.city-zoom-btn:hover { background: rgba(255,255,255,0.1); color: #f4f4f5; }

/* Camera rotation: two step-arrows flanking a small draggable compass dial.
   Pointer Events unify mouse and touch on this one dial element, so a
   single-finger drag rotates on mobile exactly like a click-hold-drag does
   on desktop -- there's deliberately no two-finger twist gesture on the
   canvas itself: a real twist can't hold pinch distance perfectly
   constant, so it always dragged the zoom along with the rotation and
   read as the view "tilting like a clock" mid-gesture. The canvas's own
   two-finger gesture is pinch-to-zoom only now. */
.city-compass-group { display: flex; align-items: center; gap: 4px; background: rgba(9,9,11,0.55); backdrop-filter: blur(6px); border: 1px solid var(--border); border-radius: 999px; padding: 2px; touch-action: none; }
.city-compass {
    width: 26px; height: 26px; border-radius: 999px; position: relative;
    background: rgba(255,255,255,0.06); border: 1px solid rgba(255,255,255,0.14);
    cursor: grab; touch-action: none; flex-shrink: 0;
}
.city-compass.is-dragging { cursor: grabbing; background: rgba(255,255,255,0.14); }
.city-compass-needle {
    position: absolute; left: 50%; top: 50%; transform: translate(-50%, -100%) rotate(0deg);
    transform-origin: 50% 100%; color: #e4e4e7; font-size: 13px; line-height: 1; pointer-events: none;
    transition: none;
}

.city-hint { position: absolute; bottom: 0.6rem; left: 0.6rem; z-index: 2; font-size: 9.5px; color: #71717a; background: rgba(9,9,11,0.5); border-radius: 999px; padding: 3px 9px; pointer-events: none; }
.city-hint i { margin-right: 0.3rem; }

.city-stat-strip { position: absolute; bottom: 0.6rem; right: 0.6rem; z-index: 2; display: flex; gap: 0.5rem; font-size: 10px; color: #d4d4d8; touch-action: none; }
.city-stat-strip .stat { background: rgba(9,9,11,0.55); backdrop-filter: blur(6px); border: 1px solid var(--border); border-radius: 999px; padding: 3px 10px; }
.city-stat-strip .stat b { font-family: var(--font-mono); color: #f4f4f5; margin-right: 0.3rem; }

.city-detail-card {
    position: absolute; z-index: 3; width: 254px; max-width: calc(100% - 1.2rem);
    background: rgba(19,19,22,0.96); border: 1px solid rgba(255,255,255,0.14); border-radius: var(--radius-md);
    padding: 0.7rem 0.8rem; box-shadow: 0 12px 32px rgba(0,0,0,0.5); backdrop-filter: blur(8px);
    touch-action: none;
}
.city-detail-title { display: flex; align-items: center; gap: 0.4rem; font-weight: 600; color: #f4f4f5; font-size: 12.5px; margin-bottom: 0.15rem; }
.city-detail-title .dot { width: 7px; height: 7px; border-radius: 999px; flex-shrink: 0; }
.city-detail-zone { font-size: 9.5px; color: #a1a1aa; text-transform: uppercase; letter-spacing: 0.04em; margin-bottom: 0.4rem; }
.city-detail-desc { font-size: 10.5px; color: #d4d4d8; line-height: 1.4; margin: 0 0 0.5rem; }
.city-detail-close { margin-left: auto; color: #71717a; background: none; border: none; cursor: pointer; font-size: 10px; }
.city-detail-close:hover { color: #e4e4e7; }
.city-detail-row { display: flex; justify-content: space-between; font-size: 11px; color: #a1a1aa; padding: 1.5px 0; }
.city-detail-row b { color: #e4e4e7; font-family: var(--font-mono); font-weight: 600; }
.city-detail-link { display: inline-flex; align-items: center; gap: 0.3rem; font-size: 10.5px; color: var(--sd-blue, #60a5fa); margin-top: 0.4rem; }
.city-detail-link:hover { text-decoration: underline; }

.city-detail-growth { margin-top: 0.35rem; }
.city-detail-growth-bar { height: 4px; border-radius: 999px; background: rgba(255,255,255,0.08); overflow: hidden; margin-bottom: 0.25rem; }
.city-detail-growth-fill { height: 100%; background: linear-gradient(90deg, #7c93ff, #9db3ff); border-radius: 999px; }
.city-detail-growth span { font-size: 9.5px; color: #a1a1aa; }

.city-detail-events { margin-top: 0.5rem; padding-top: 0.45rem; border-top: 1px solid rgba(255,255,255,0.08); max-height: 150px; overflow-y: auto; }
.city-detail-events-title { font-size: 9.5px; color: #71717a; text-transform: uppercase; letter-spacing: 0.04em; margin-bottom: 0.3rem; }
.city-detail-event { display: block; padding: 3px 0; text-decoration: none; border-bottom: 1px solid rgba(255,255,255,0.05); }
.city-detail-event:last-child { border-bottom: none; }
.city-detail-event-label { display: block; font-size: 10.5px; color: #d4d4d8; line-height: 1.3; }
a.city-detail-event:hover .city-detail-event-label { color: #f4f4f5; text-decoration: underline; }
.city-detail-event-meta { display: flex; align-items: center; gap: 0.4rem; margin-top: 1px; }
.city-detail-event-verdict { font-size: 9px; font-family: var(--font-mono); font-weight: 600; }
.city-detail-event-time { font-size: 9px; color: #71717a; }

.city-legend { display: flex; flex-wrap: wrap; gap: 0.5rem 0.9rem; margin-top: 0.7rem; font-size: 10px; color: #71717a; }
.city-legend-item { display: flex; align-items: center; gap: 0.3rem; }
.city-legend-dot { width: 6px; height: 6px; border-radius: 999px; flex-shrink: 0; }
/* Only shown while its own data-layer is active -- see cityscape.js's
   layer-button handler, which toggles .is-active to match inst.layer. */
.city-legend-item[data-legend-layer] { display: none; }
.city-legend-item[data-legend-layer].is-active { display: flex; }

@media (max-width: 640px) {
    .city-toolbar { flex-direction: column; align-items: stretch; }
    /* Bottom-left hint and bottom-right stat strip both had unconstrained
       widths -- on a narrow viewport their content ran into each other in
       the middle. Stack them full-width instead so they never collide. */
    .city-hint { left: 0.6rem; right: 0.6rem; bottom: 2.5rem; white-space: normal; text-align: center; }
    .city-stat-strip { left: 0.6rem; right: 0.6rem; bottom: 0.6rem; justify-content: center; flex-wrap: wrap; }
}

/* Category Signal Map — six real lanes as a staggered bubble cluster
   (Chart.js `bubble`, real primitive), with a caption list beneath it
   carrying the exact delta text each bubble represents (see
   securitydashboard.js::_categorySignal(), the one place that computes
   this — the chart and the caption list both read from it so they can't
   drift out of sync with each other). */
/* Both bubble charts below get a horizontal-scroll wrapper (same
   overflow-x:auto convention as .secdash-ledger-scroll's wide table) —
   securitydashboard.js sets a real min-width on the inner chart element
   sized from the actual category/day count, so bubbles keep a legible,
   non-overlapping fixed size and scroll instead of being crushed together
   on a narrow viewport. */
.secdash-signalmap-scroll, .secdash-timeline-scroll { overflow-x: auto; }

/* Faint concentric "radar" rings behind the bubble cluster (pure CSS
   background, no extra markup/JS) — echoes the reference screenshot's
   orbit motif without hand-rolled physics; the bubbles themselves stay a
   real Chart.js `bubble` dataset (see securitydashboard.js). */
.secdash-signalmap-chart {
    position: relative; height: 190px; width: 100%; min-width: 340px;
    background-image:
        radial-gradient(circle at 50% 58%, var(--sd-glow-white), transparent 42%),
        repeating-radial-gradient(circle at 50% 58%, rgba(148, 163, 250, 0.07) 0px, rgba(148, 163, 250, 0.07) 1px, transparent 1px, transparent 34px);
    border-radius: var(--radius-md);
}
.secdash-signalmap-legend {
    display: grid; grid-template-columns: repeat(2, 1fr); gap: 0.4rem 0.75rem; margin-top: 0.5rem;
}
/* Each legend item doubles as the click target for the expandable detail
   panel below (see .secdash-signalmap-detail) — same real per-category
   signal a bubble click triggers, since the legend item and its bubble
   both come from the one _categorySignal() computation. */
.secdash-signalmap-item {
    display: flex; align-items: center; gap: 0.4rem; font-size: 10.5px; min-width: 0;
    cursor: pointer; padding: 0.2rem; margin: -0.2rem; border-radius: 6px; transition: background-color 0.15s ease;
}
.secdash-signalmap-item:hover, .secdash-signalmap-item.is-active { background: rgba(255, 255, 255, 0.04); }
.secdash-signalmap-letter {
    display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0;
    width: 17px; height: 17px; border-radius: 999px; font-size: 8px; font-weight: 600;
    font-family: var(--font-mono); border: 1px solid currentColor;
}
.secdash-signalmap-body { min-width: 0; overflow: hidden; }
.secdash-signalmap-title { color: #d4d4d8; display: block; }
.secdash-signalmap-delta { color: #71717a; display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Expandable per-category detail — clicking a bubble or its matching legend
   item opens this with that category's real underlying lane rows (and, for
   On-Chain Intel, the real uncovered attack-pattern list) instead of only
   ever showing the one-line delta text. */
.secdash-signalmap-detail {
    margin-top: 0.6rem; padding: 0.7rem 0.75rem; border-radius: var(--radius-md);
    border: 1px solid rgba(148, 163, 250, 0.16);
    background: linear-gradient(135deg, var(--sd-glow-blue), var(--sd-glow-white));
    font-size: 11px;
}
.secdash-signalmap-detail-title { color: #e4e4e7; font-weight: 600; margin-bottom: 0.4rem; display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; }
.secdash-signalmap-detail-close { color: #71717a; cursor: pointer; font-size: 10px; }
.secdash-signalmap-detail-close:hover { color: #e4e4e7; }
.secdash-signalmap-detail-row { display: flex; align-items: baseline; gap: 0.5rem; padding: 0.3rem 0; border-top: 1px solid rgba(255, 255, 255, 0.05); font-size: 10.5px; }
.secdash-signalmap-detail-row:first-of-type { border-top: none; }
.secdash-signalmap-detail-label { color: #d4d4d8; flex-shrink: 0; }
.secdash-signalmap-detail-value { color: #71717a; flex: 1; min-width: 0; }
.secdash-signalmap-detail-gaps { margin-top: 0.4rem; display: flex; flex-wrap: wrap; gap: 0.3rem; }
.secdash-signalmap-detail-gap { font-size: 9.5px; color: var(--sev-high); background: rgba(236, 131, 90, 0.1); border: 1px solid rgba(236, 131, 90, 0.3); border-radius: 999px; padding: 1px 7px; }

/* Signal Timeline legend — small inline severity chips beneath the bubble
   timeline chart (the "By Severity" toggle state only; hidden for the
   Threat Level line-chart state, which has no per-bucket severity to key). */
.secdash-timeline-legend { display: flex; flex-wrap: wrap; gap: 0.6rem; margin-top: 0.5rem; }
.secdash-timeline-legend-item { display: flex; align-items: center; gap: 0.3rem; font-size: 10px; color: #a1a1aa; }

.secdash-chart-empty {
    position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
    text-align: center; padding: 0 1rem; font-size: 13px; color: #71717a;
}

/* Findings per Status — a compact per-verdict bar list (dot + label +
   proportional fill bar + count + %) instead of a full Chart.js canvas.
   The previous bar chart was a plain 3-bar chart stretched across a
   200-260px .chart-shell-sm — a lot of chrome for three numbers. This is
   a purpose-built small component (still ≥2 series with a legend, i.e.
   the label beside each bar, so identity is never color-alone) sized to
   sit beside the equally-compact Risk Level strip instead of dominating
   its own row. */
.secdash-verdict-body { display: flex; flex-direction: column; gap: 0.5rem; justify-content: center; height: 100%; min-height: 64px; }
.secdash-verdict-row { display: flex; align-items: center; gap: 0.55rem; font-size: 11px; }
.secdash-verdict-dot { width: 7px; height: 7px; border-radius: 999px; flex-shrink: 0; }
.secdash-verdict-label { color: #a1a1aa; width: 54px; flex-shrink: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.secdash-verdict-bar-wrap { flex: 1; height: 6px; border-radius: 999px; background: rgba(255, 255, 255, 0.06); overflow: hidden; min-width: 0; }
.secdash-verdict-bar { height: 100%; border-radius: 999px; transition: width 0.4s ease; }
.secdash-verdict-count { font-family: var(--font-mono); color: #e4e4e7; width: 34px; text-align: right; flex-shrink: 0; }
.secdash-verdict-pct { font-family: var(--font-mono); color: #71717a; width: 40px; text-align: right; flex-shrink: 0; }

/* Findings Ledger — a real, searchable/filterable/paginated table over the
   most recent ~150 real findings.jsonl rows (security-dashboard.json's
   findings_recent). Each row's sibling detail row expands in place; kept
   as a real <tr> (not an absolutely-positioned overlay) so it pushes the
   rows below it down rather than covering them. */
.secdash-ledger-scroll { overflow-x: auto; }
.secdash-ledger-table { width: 100%; border-collapse: collapse; font-size: 11.5px; }
.secdash-ledger-table thead th {
    text-align: left; font-size: 10px; text-transform: uppercase; letter-spacing: 0.03em;
    color: #71717a; font-weight: 500; padding: 0 0.5rem 0.4rem; border-bottom: 1px solid var(--border);
}
.secdash-ledger-row { cursor: pointer; transition: background-color 0.12s ease; }
.secdash-ledger-row:hover { background: rgba(255, 255, 255, 0.03); }
.secdash-ledger-row td { padding: 0.55rem 0.5rem; border-bottom: 1px solid rgba(255, 255, 255, 0.05); vertical-align: middle; white-space: nowrap; }
/* Subtle severity wash on the first cell only (never the sole conveyor of
   meaning — the badge's icon+text label right beside it always carries
   the real signal) via a CSS var each row sets inline from its own real
   severity color, plus a small pulsing dot for CRITICAL rows reusing the
   site's existing live-dot animation as a genuine "needs attention" cue. */
.secdash-ledger-row td:first-child { border-left: 2px solid var(--row-sev, transparent); background: var(--row-sev-wash, transparent); }
.secdash-ledger-health-dot { display: inline-block; width: 5px; height: 5px; border-radius: 999px; background: currentcolor; margin-left: 0.3rem; animation: pulse-ring 1.8s infinite; }
.secdash-ledger-title {
    color: #d4d4d8; max-width: 260px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.secdash-ledger-source { color: #71717a; font-family: var(--font-mono); font-size: 10.5px; }
.secdash-ledger-tags { max-width: 200px; overflow: hidden; }
.secdash-ledger-tag {
    display: inline-block; font-size: 9.5px; color: #a1a1aa; background: var(--bg-panel-sm);
    border: 1px solid var(--border); border-radius: 999px; padding: 1px 7px; margin: 0 2px 2px 0;
}
.secdash-ledger-time { color: #71717a; font-family: var(--font-mono); font-size: 10.5px; display: flex; align-items: center; gap: 0.4rem; }
.secdash-ledger-chevron { font-size: 8px; color: #52525b; transition: transform 0.15s ease; }
.secdash-ledger-row.is-expanded .secdash-ledger-chevron { transform: rotate(180deg); }
.secdash-ledger-detail { display: none; }
.secdash-ledger-detail.is-open { display: table-row; }
.secdash-ledger-detail td { border-bottom: 1px solid rgba(255, 255, 255, 0.05); padding: 0; white-space: normal; }
/* Larger expanding detail pane — a bordered, glow-tinted card (matching
   this section's blue-violet theme) with a real field grid instead of a
   plain flex-wrap text line, so the expanded record reads as a genuine
   detail view rather than an afterthought. */
.secdash-ledger-detail-inner {
    display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: 0.7rem 1rem;
    padding: 0.9rem 1rem; margin: 0.3rem 0.5rem 0.7rem;
    font-size: 11px; color: #d4d4d8;
    background: linear-gradient(135deg, var(--sd-glow-blue), var(--sd-glow-white));
    border: 1px solid rgba(148, 163, 250, 0.16); border-radius: var(--radius-md);
}
.secdash-ledger-detail-field { display: block; }
.secdash-ledger-detail-label { display: block; font-size: 9px; text-transform: uppercase; letter-spacing: 0.04em; color: #71717a; margin-bottom: 0.2rem; }
.secdash-ledger-detail-tags { grid-column: 1 / -1; display: flex; flex-wrap: wrap; gap: 0.3rem; }
.secdash-ledger-detail-link { grid-column: 1 / -1; }

/* Ledger header mini-sparkline — real daily finding-volume trend (the same
   findings_timeline data backing the Signal Timeline chart, just summed
   per day), reusing the x402 Ledger Activity tiles' bar-gradient look at a
   much smaller scale since this is a glance-value accent beside the panel
   title, not a primary chart. */
.secdash-ledger-spark { display: inline-flex; align-items: flex-end; gap: 2px; height: 22px; margin-left: 0.6rem; vertical-align: middle; }
.secdash-ledger-spark-bar {
    flex: 0 0 auto; width: 3px; min-width: 3px; border-radius: 1px 1px 0 0;
    background: linear-gradient(180deg, rgba(191, 219, 254, 0.75) 0%, var(--sd-blue) 45%, var(--sd-white) 100%);
}
.secdash-ledger-spark-bar.is-latest { filter: brightness(1.25); box-shadow: 0 0 5px rgba(167, 139, 250, 0.6); }

/* x402 Activity tiles — Transactions / Volume / Buyers, each a big number
   plus a small bar-sparkline of the same real per-day data backing the main
   Volume & Revenue chart below. Deliberately its own compact tri-widget
   layout (not a re-skin of .chart-shell) — the reference this was modeled
   on (a wallet-app "Overview" activity module) reads at a glance precisely
   because each metric gets its own small, focused chart rather than sharing
   one busy multi-series one. */
.activity-tile {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
}
.activity-tile-label {
    font-size: 0.6875rem;
    color: #71717a;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.activity-tile-value {
    font-size: 1.5rem;
    font-family: var(--font-mono);
    color: #f4f4f5;
    font-variant-numeric: tabular-nums;
    line-height: 1.1;
}
.activity-sparkline {
    display: flex;
    align-items: flex-end;
    gap: 3px;
    height: 112px;
    margin-top: 0.5rem;
    /* Bars are fixed-width (not shrink-to-fit), so a wide bucket count
       scrolls instead of squeezing every bar down toward an unreadable
       sliver on a narrow mobile tile — the actual cause of "bars are too
       small" on real phones even though a desktop-width screenshot looked
       fine. Defaults scrolled to the most-recent (rightmost) bars, see
       x402feed.js's _renderSparkline(). */
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-width: thin;
    scrollbar-color: rgba(255, 255, 255, 0.25) transparent;
}
.activity-sparkline::-webkit-scrollbar {
    height: 4px;
}
.activity-sparkline::-webkit-scrollbar-track {
    background: transparent;
}
.activity-sparkline::-webkit-scrollbar-thumb {
    background: rgba(255, 255, 255, 0.2);
    border-radius: 2px;
}
.activity-sparkline-bar {
    flex: 0 0 auto;
    width: 9px;
    min-width: 9px;
    /* "Sophisticated shading" — a light-to-dark gradient fill (matching the
       reference widget's own bars) instead of a flat solid color, giving
       each bar visible depth rather than a flat block. Top stop toned down
       from an earlier near-white (rgba(219,234,254,0.95)) — read as too
       bright/blown-out against the dark panel — to a softer light blue that
       still reads as "lit from above" without glaring. */
    background: linear-gradient(180deg, rgba(191, 219, 254, 0.7) 0%, rgba(96, 165, 250, 0.8) 45%, rgba(37, 99, 235, 0.6) 100%);
    border-radius: 3px 3px 0 0;
    transition: filter 0.15s ease, box-shadow 0.15s ease;
    cursor: pointer;
}
/* Auto margin on the first bar only: when the bucket count is narrower than
   the tile (no scrolling needed — e.g. a wide desktop viewport with a modest
   bar count), the free space collapses onto this margin instead of being
   left dangling after the last (most recent) bar, so the chart still ends
   flush against the tile's right edge. When the bucket count DOES overflow
   the tile, an auto margin has no free space to absorb and computes to 0,
   so this has zero effect on the horizontal scrolling above — confirmed
   empirically (justify-content:flex-end looked like the obvious fix here,
   but Chromium doesn't register flex-end's start-side overflow as
   scrollable at all, which would have made older bars permanently
   unreachable; this margin trick avoids that failure mode entirely). */
.activity-sparkline-bar:first-child {
    margin-left: auto;
}
.activity-sparkline-bar:hover,
.activity-sparkline-bar.is-latest {
    filter: brightness(1.15) saturate(1.1);
    box-shadow: 0 0 10px rgba(96, 165, 250, 0.55);
}

/* Custom "sleek popup" shown on hover/touch for a sparkline bar, replacing
   the plain native title-attribute tooltip — see x402feed.js's
   _showSparklineTooltip()/_wireSparklineTooltips(). Styled to match the
   Volume & Revenue Chart.js tooltip just below it (same near-black fill,
   same hairline border) rather than as a bare one-line label — a small
   muted date/time line on top, a bolder labeled metric value below, the
   same title/value hierarchy Chart.js's own tooltip callback renders. */
.activity-tooltip {
    position: fixed;
    z-index: 60;
    padding: 0.5rem 0.75rem;
    background: rgba(9, 9, 11, 0.96);
    border: 1px solid rgba(255, 255, 255, 0.12);
    border-radius: 8px;
    font-family: var(--font-mono);
    white-space: nowrap;
    pointer-events: none;
    opacity: 0;
    transform: translateY(4px);
    transition: opacity 0.12s ease, transform 0.12s ease;
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.45);
}
.activity-tooltip.is-visible {
    opacity: 1;
    transform: translateY(0);
}
.activity-tooltip-date {
    font-size: 10px;
    color: #71717a;
    margin-bottom: 0.2rem;
}
.activity-tooltip-value {
    font-size: 12.5px;
    font-weight: 600;
    color: #f4f4f5;
    font-variant-numeric: tabular-nums;
}

.skeleton {
    background: linear-gradient(90deg, rgba(255,255,255,.04) 25%, rgba(255,255,255,.09) 37%, rgba(255,255,255,.04) 63%);
    background-size: 400% 100%;
    animation: sh 1.4s ease infinite;
}
@keyframes sh { 0% { background-position: 100% 50%; } 100% { background-position: 0 50%; } }

a { transition: color .15s; }
.card-h { transition: opacity .15s ease; }
.card-h:hover { opacity: 0.75; }

/* ── Scroll reveal ─────────────────────────────────────────────────────── */
.reveal {
    opacity: 0;
    transform: translateY(16px);
    transition: opacity .6s cubic-bezier(.2,.8,.2,1), transform .6s cubic-bezier(.2,.8,.2,1);
}
.reveal.in-view { opacity: 1; transform: none; }
.reveal-1.in-view { transition-delay: .06s; }
.reveal-2.in-view { transition-delay: .12s; }
.reveal-3.in-view { transition-delay: .18s; }
@media (prefers-reduced-motion: reduce) {
    .reveal { transition: none; opacity: 1; transform: none; }
    html { scroll-behavior: auto; }
    .live-dot, .secdash-ledger-health-dot { animation: none; }
}

/* ── Featured Investigation spotlight ─────────────────────────────────────
   The one section deliberately styled as a showcase rather than a flat
   data row — VAPE's best real, verifiable work gets a genuine focal point.
   Uses its own blue accent (#60a5fa, already used elsewhere for wallet
   chips) rather than --accent, so it reads as "this is the
   highlight" and not another verdict/status color. */
.spotlight-section {
    position: relative;
    border: 1px solid rgba(96, 165, 250, 0.25);
    border-radius: var(--radius-lg);
    background-color: var(--bg-panel);
    background-image: radial-gradient(ellipse 120% 100% at 50% 0%, rgba(96, 165, 250, 0.08), transparent 60%);
    padding: 1.5rem;
    overflow: hidden;
}
@media (min-width: 640px) { .spotlight-section { padding: 2.25rem; } }
.spotlight-section::before {
    content: '';
    position: absolute;
    top: 0; left: 0; right: 0;
    height: 2px;
    background: linear-gradient(90deg, transparent, #60a5fa, transparent);
}
.spotlight-card {
    position: relative;
    border: 1px solid rgba(255, 255, 255, 0.12);
    border-radius: var(--radius-md);
    background: var(--bg-panel-sm);
    padding: 1.25rem;
    transition: border-color 0.2s ease;
}
.spotlight-card:hover { border-color: rgba(96, 165, 250, 0.45); }
.spotlight-kicker {
    font-size: 0.6875rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: #60a5fa;
}
.spotlight-kicker i { margin-right: 0.25rem; }

/* ── Bounty Command Center console treatment ──────────────────────────────
   Scoped to just this section (not a global --accent change) — swaps its
   green touches (audit-filed / VAPE-fit / hire-CTA highlights, previously
   text-emerald-*) for the same blue as .spotlight-* above, and gives the
   section itself a left accent bar + tinted live indicator so it reads as
   an operations console rather than another flat data panel. */
#bounty-command-center {
    border-left: 2px solid rgba(96, 165, 250, 0.35);
}
#bounty-command-center .eyebrow { color: #60a5fa; }
#bounty-command-center #bcc-live { color: #60a5fa; }

/* ── Wallet chip / profile ────────────────────────────────────────────── */
.wallet-chip {
    border: 1px solid var(--border);
    transition: border-color .15s ease;
}
.wallet-chip:hover { border-color: rgba(255,255,255,.35); }
.chain-badge-warn { animation: chainpulse 1.6s infinite; }
@keyframes chainpulse { 0%,100%{opacity:1} 50%{opacity:.6} }

/* Threat ticker: allow the browser to handle vertical page scroll while our
   own JS drag/swipe handler owns horizontal movement (swipe to the next/
   previous headline) — without this, touch-dragging the ticker on mobile
   fights the page scroll instead of just navigating the ticker. */
#attack-ticker-line { touch-action: pan-y; user-select: none; }
/* Chromium treats a plain <a> as natively draggable — a mouse press-drag
   over one starts native link drag-and-drop, which cancels the in-flight
   Pointer Event sequence (a real `pointercancel`, confirmed while testing
   the swipe gesture below) before our own drag logic ever sees a pointerup.
   draggable="false" on the element is the primary fix; -webkit-user-drag
   covers the same case for engines that only honor the CSS property. */
#attack-ticker { -webkit-user-drag: none; user-drag: none; }

/* ── VAPE Wire (news-intel section) ──────────────────────────────────────── */
#news-ticker-line a { color: inherit; text-decoration: none; }
#news-ticker-line a:hover { color: #fff; }

.news-card {
    display: flex;
    flex-direction: column;
    background: var(--bg-panel-sm);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    overflow: hidden;
    text-decoration: none;
    color: inherit;
    transition: border-color 0.15s ease, opacity 0.15s ease;
}
.news-card:hover { border-color: rgba(255, 255, 255, 0.25); opacity: 0.92; }
.news-card-img { position: relative; width: 100%; height: 128px; background: rgba(255, 255, 255, 0.04); overflow: hidden; }
.news-card-img img { width: 100%; height: 100%; object-fit: cover; display: block; }
.news-card-img img.news-card-img-fallback { object-fit: contain; padding: 1.75rem; opacity: 0.55; }
.news-card-body { padding: 0.875rem 1rem 1rem; display: flex; flex-direction: column; flex: 1; }
.news-card-topic {
    align-self: flex-start;
    font-size: 9px;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: #a1a1aa;
    background: rgba(255, 255, 255, 0.05);
    border: 1px solid var(--border);
    border-radius: 4px;
    padding: 2px 6px;
    margin-bottom: 0.5rem;
}
.news-card-title { font-size: 0.875rem; line-height: 1.35; margin-bottom: 0.375rem; }
.news-card-dek { font-size: 0.75rem; color: #71717a; line-height: 1.5; flex: 1; }
.news-card-meta {
    font-size: 10px;
    color: #52525b;
    margin-top: 0.75rem;
    padding-top: 0.5rem;
    border-top: 1px solid rgba(255, 255, 255, 0.05);
    display: flex;
    align-items: center;
    justify-content: space-between;
}

/* ── article.html (VAPE Wire full article reader) ────────────────────────── */
.article-hero {
    width: 100%;
    aspect-ratio: 16 / 9;
    border-radius: var(--radius-lg);
    overflow: hidden;
    background: rgba(255, 255, 255, 0.04);
    margin-bottom: 1.5rem;
}
.article-hero img { width: 100%; height: 100%; object-fit: cover; display: block; }
.article-headline {
    font-family: var(--font-sans);
    font-size: 1.75rem;
    line-height: 1.25;
    font-weight: 800;
    letter-spacing: -0.015em;
    color: var(--text-heading);
    margin-bottom: 0.75rem;
}
@media (min-width: 640px) { .article-headline { font-size: 2.125rem; } }
.article-dek { font-size: 1.0625rem; color: var(--text-muted); line-height: 1.55; margin-bottom: 1.25rem; }
.article-byline {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 1rem;
    font-size: 12px;
    color: var(--text-muted);
    padding-bottom: 1.5rem;
    margin-bottom: 1.5rem;
    border-bottom: 1px solid var(--border);
}
/* The reading column itself. Sized and measured for prose rather than for
   an interface: 17px at 1.72 leading inside a ~68ch column. */
.article-body {
    font-size: var(--reading-size);
    line-height: var(--reading-leading);
    max-width: var(--measure);
    color: var(--text-body);
}
.article-body p { margin-bottom: 1.15rem; color: var(--text-body); }
/* Subheads sit BELOW the body in brightness -- see the token note. */
.article-body h2, .article-body h3 {
    color: var(--text-heading);
    font-weight: 650;
    letter-spacing: -0.01em;
    line-height: 1.3;
    margin: 2rem 0 0.75rem;
}
.article-body h2 { font-size: 1.25rem; }
.article-body h3 { font-size: 1.0625rem; }
.article-body a { color: var(--link); text-decoration: none; border-bottom: 1px solid rgba(147, 180, 255, 0.3); }
.article-body a:hover { color: var(--link-hover); border-bottom-color: currentColor; }
.article-body strong { color: var(--text-body); font-weight: 650; }
.article-body code {
    font-family: var(--font-mono);
    font-size: 0.9em;
    padding: 0.1em 0.35em;
    border-radius: 4px;
    background: rgba(255, 255, 255, 0.07);
}
:root[data-theme="light"] .article-body code { background: rgba(9, 9, 11, 0.06); }
.article-body blockquote {
    margin: 1.25rem 0;
    padding-left: 1rem;
    border-left: 2px solid var(--border);
    color: var(--text-muted);
}

/* Sources read as apparatus, not as prose: a cooler tint and a step down in
   size, so a reader scanning the body is not pulled into the citation list
   but can still find and trust it. */
.article-sources, .article-body h2.sources-heading ~ ul {
    font-size: 0.9375rem;
    color: var(--text-source);
    line-height: 1.6;
}
.article-sources a { color: var(--text-source); border-bottom: 1px solid rgba(154, 164, 178, 0.35); }
.article-sources a:hover { color: var(--link-hover); }

/* GFM-style tables emitted by simpleMarkdownToHtml() (report.js) — shared by
   article.html and investigation.html, wherever a report's real markdown
   body contains a table (e.g. an investigation's per-category score table). */
/* overflow-x: auto is a safety net for a genuinely wide table, not the
   primary fix -- th/td used to force white-space: nowrap on every cell,
   so a long Rationale/Category cell (the Scoring Dashboard table's own
   prose columns) inflated the whole table far past the viewport instead
   of wrapping, and on mobile that left the reader looking at whichever
   slice of columns happened to be in the scrolled view rather than the
   table's own left edge. Cells wrap by default now; only genuinely short,
   single-token cells (a number, a short label) stay on one line. */
.md-table-wrap { overflow-x: auto; margin: 0.75rem 0 1.25rem; }
.md-table { width: 100%; border-collapse: collapse; font-size: 0.85em; table-layout: fixed; }
.md-table th, .md-table td { padding: 0.45rem 0.65rem; border: 1px solid var(--border); text-align: left; white-space: normal; overflow-wrap: break-word; word-break: break-word; }
.md-table th { background: rgba(255, 255, 255, 0.04); color: var(--text-heading); font-weight: 600; }
.md-table td { color: var(--text-body); }
:root[data-theme="light"] .md-table th { background: rgba(9, 9, 11, 0.04); }

/* ── investigation.html (full on-site investigation report reader) ──────── */
.inv-header {
    display: flex;
    align-items: flex-start;
    gap: 0.875rem;
    padding-bottom: 1.5rem;
    margin-bottom: 1.5rem;
    border-bottom: 1px solid var(--border);
}
.inv-icon { width: 52px; height: 52px; border-radius: 999px; background: rgba(255, 255, 255, 0.05); object-fit: cover; flex-shrink: 0; }
.inv-icon-fallback {
    width: 52px; height: 52px; border-radius: 999px; flex-shrink: 0;
    background: rgba(255, 255, 255, 0.05); border: 1px solid var(--border);
    display: flex; align-items: center; justify-content: center; color: #71717a; font-size: 1rem;
}
.inv-meta-row { display: flex; flex-wrap: wrap; align-items: center; gap: 0.6rem; font-size: 11px; color: #71717a; margin-top: 0.4rem; }

/* ── investigations.html (Featured Investigations expanding ledger) ─────── */
.invl-scroll { overflow-x: auto; }
.invl-table { width: 100%; border-collapse: collapse; font-size: 11.5px; }
.invl-table thead th {
    text-align: left; font-size: 9.5px; text-transform: uppercase; letter-spacing: 0.04em;
    color: #71717a; padding: 0.5rem 0.6rem; border-bottom: 1px solid var(--border); white-space: nowrap;
}
.invl-sort-btn {
    background: none; border: none; padding: 0; margin: 0; font: inherit; text-transform: inherit;
    letter-spacing: inherit; color: inherit; cursor: pointer;
}
.invl-sort-btn:hover, .invl-sort-btn:focus-visible { color: #d4d4d8; }
.invl-sort-arrow { font-size: 8px; margin-left: 0.3rem; opacity: 0.6; }
.invl-row { cursor: pointer; transition: background-color 0.12s ease; }
.invl-row:hover { background: rgba(255, 255, 255, 0.03); }
.invl-row td { padding: 0.6rem 0.6rem; border-bottom: 1px solid rgba(255, 255, 255, 0.05); vertical-align: middle; white-space: nowrap; }
.invl-row td:first-child { border-left: 2px solid var(--row-verdict, transparent); background: var(--row-verdict-wash, transparent); }
.invl-icon { width: 22px; height: 22px; border-radius: 999px; background: rgba(255, 255, 255, 0.06); object-fit: cover; flex-shrink: 0; }
.invl-title-cell { display: flex; align-items: center; gap: 0.5rem; }
.invl-title { color: #e4e4e7; font-weight: 500; max-width: 180px; overflow: hidden; text-overflow: ellipsis; }
.invl-addr { color: #71717a; font-family: var(--font-mono); font-size: 10.5px; }
.invl-verdict-pill { display: inline-block; padding: 1px 8px; border-radius: 999px; font-size: 9.5px; border: 1px solid currentcolor; white-space: nowrap; }
.invl-score-cell { display: inline-flex; align-items: center; gap: 0.45rem; }
.invl-score-bar-wrap { width: 54px; height: 4px; border-radius: 999px; background: rgba(255, 255, 255, 0.08); overflow: hidden; }
.invl-score-bar-fill { height: 100%; border-radius: 999px; }
.invl-time { color: #71717a; font-family: var(--font-mono); font-size: 10.5px; }
.invl-chevron-cell { display: flex; align-items: center; justify-content: flex-end; gap: 0.4rem; }
.invl-chevron { font-size: 8px; color: #52525b; transition: transform 0.15s ease; }
.invl-row.is-expanded .invl-chevron { transform: rotate(180deg); }
.invl-detail { display: none; }
.invl-detail.is-open { display: table-row; }
.invl-detail td { border-bottom: 1px solid rgba(255, 255, 255, 0.05); padding: 0; white-space: normal; }
.invl-detail-inner {
    padding: 0.9rem 1rem 1.1rem 1rem; display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
    gap: 0.75rem 1.5rem; background: rgba(255, 255, 255, 0.015);
}
.invl-detail-summary { grid-column: 1 / -1; color: #a1a1aa; font-size: 11.5px; line-height: 1.55; }
.invl-detail-factors { grid-column: 1 / -1; display: flex; flex-direction: column; gap: 0.3rem; margin-top: 0.15rem; }
.invl-factor-row { display: flex; align-items: center; gap: 0.6rem; font-size: 10.5px; }
.invl-factor-label { flex: 1; min-width: 0; color: #a1a1aa; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.invl-factor-bar-wrap { width: 96px; height: 6px; background: rgba(255, 255, 255, 0.06); border-radius: 3px; position: relative; overflow: hidden; flex-shrink: 0; }
.invl-factor-bar { position: absolute; top: 0; bottom: 0; border-radius: 3px; }
.invl-factor-delta { width: 32px; text-align: right; font-family: var(--font-mono); flex-shrink: 0; }
.invl-detail-actions { grid-column: 1 / -1; display: flex; flex-wrap: wrap; gap: 0.6rem; margin-top: 0.2rem; }

.invl-spark { display: inline-flex; align-items: flex-end; gap: 2px; height: 22px; }
.invl-spark-bar { width: 5px; background: rgba(255, 255, 255, 0.18); border-radius: 1px 1px 0 0; transition: background 0.2s ease; }
/* Uncolored buckets (no scored investigations that period) still get the
   plain highlight; scored buckets are tinted by renderSparkline()'s inline
   background (avg score for that window), so brightness -- not a flat
   override -- is what marks the most recent bucket without erasing the
   score color underneath it. */
.invl-spark-bar.is-latest { filter: brightness(1.45); }
/* Larger standalone sparkline (own row, not squeezed into a stat pill) used
   by both this page's own "Activity" strip and the main-site preview —
   renderSparkline()/wireSparkRangeToggle() in investigations-ledger.js are
   shared between the two, so this is literally the same chart, just given
   more room here. */
.invl-spark-lg { height: 64px; gap: 3px; width: 100%; }
.invl-spark-lg .invl-spark-bar { width: 7px; flex: 1 1 auto; max-width: 14px; }

.invl-range-toggle { display: inline-flex; gap: 2px; background: rgba(255, 255, 255, 0.04); border: 1px solid var(--border); border-radius: 999px; padding: 2px; }
.invl-range-btn {
    font-size: 10.5px; padding: 0.25rem 0.65rem; border-radius: 999px; color: #a1a1aa;
    background: none; border: none; cursor: pointer; transition: background-color 0.15s ease, color 0.15s ease;
}
.invl-range-btn:hover { color: #e4e4e7; }
.invl-range-btn.is-active { background: rgba(255, 255, 255, 0.1); color: #f4f4f5; }

/* Verdict Breakdown donut — real 2D proportions underneath (Chart.js draws
   the arcs; nothing here reweights a slice), styled with a shallow CSS 3D
   tilt + ambient shadow + a soft highlight for a beveled "instrument" look
   instead of the flat, tiny side-panel version this replaced. The tilt is a
   uniform perspective transform of the whole chart, not a per-slice
   expansion the way a fake-3D pie distorts area — angles stay honest. */
.invl-donut-wrap {
    position: relative; width: 100%; max-width: 260px; height: 260px; margin: 0 auto;
    filter: drop-shadow(0 18px 30px rgba(0, 0, 0, 0.5));
    transform: perspective(700px) rotateX(6deg);
    transition: transform 0.25s ease;
}
.invl-donut-wrap:hover { transform: perspective(700px) rotateX(2deg) scale(1.02); }
.invl-donut-wrap::before {
    content: ''; position: absolute; inset: -16px; border-radius: 50%; pointer-events: none;
    background: radial-gradient(circle at 32% 26%, rgba(255, 255, 255, 0.1), transparent 60%);
}
.invl-donut-center { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; pointer-events: none; }
.invl-donut-total-value { font-size: 1.75rem; font-family: var(--font-mono); color: #f4f4f5; font-weight: 700; line-height: 1.1; }
.invl-donut-total-label { font-size: 9.5px; color: #71717a; text-transform: uppercase; letter-spacing: 0.05em; margin-top: 0.15rem; }
@media (max-width: 640px) {
    .invl-donut-wrap, .invl-donut-wrap:hover { transform: none; }
}
@media (prefers-reduced-motion: reduce) {
    .invl-donut-wrap { transition: none; }
    .invl-donut-wrap:hover { transform: perspective(700px) rotateX(6deg); }
}

/* Each row is a real per-verdict data readout (count, share, average
   score) AND a filter control — clicking it drives the same
   this._verdictFilter the plain <select> above the ledger uses. */
.invl-verdict-legend { display: flex; flex-direction: column; gap: 0.45rem; }
.invl-verdict-row {
    display: flex; align-items: center; gap: 0.65rem; width: 100%; text-align: left; font-size: 12px;
    padding: 0.65rem 0.85rem; border-radius: var(--radius-md); border: 1px solid var(--border);
    background: rgba(255, 255, 255, 0.02); cursor: pointer; transition: border-color 0.15s ease, background-color 0.15s ease;
}
.invl-verdict-row.is-static { cursor: default; }
.invl-verdict-row:not(.is-static):hover { border-color: rgba(255, 255, 255, 0.22); background: rgba(255, 255, 255, 0.045); }
.invl-verdict-row.is-active { border-color: rgba(148, 163, 250, 0.4); background: rgba(148, 163, 250, 0.08); }
.invl-verdict-row-dot { width: 10px; height: 10px; border-radius: 999px; flex-shrink: 0; }
.invl-verdict-row-label { color: #e4e4e7; flex: 1 1 auto; min-width: 0; }
.invl-verdict-row-count { font-family: var(--font-mono); color: #f4f4f5; font-variant-numeric: tabular-nums; }
.invl-verdict-row-pct { font-family: var(--font-mono); color: #71717a; width: 3.4em; text-align: right; flex-shrink: 0; }
.invl-verdict-row-avg { font-family: var(--font-mono); color: #71717a; font-size: 10.5px; flex-shrink: 0; width: 5.5em; text-align: right; }

/* Full-report popup — same .popover/.fixed backdrop convention app.js's
   openProtocolReport() established, sized larger since a full markdown
   investigation report needs real reading room. */
#invl-report-modal .popover { max-width: 48rem; width: 100%; }
.invl-report-score-row { display: flex; align-items: center; gap: 0.75rem; margin-bottom: 1.25rem; }
.invl-report-score-bar-wrap { flex: 1; height: 8px; border-radius: 999px; background: rgba(255, 255, 255, 0.08); overflow: hidden; }
.invl-report-score-bar-fill { height: 100%; border-radius: 999px; }

code, pre { font-family: 'JetBrains Mono', ui-monospace, monospace; }

::selection { background: rgba(74, 222, 128, 0.25); }

/* Nav — stays a scrollable row of real links rather than collapsing behind
   a hamburger's worth of hidden information (the toggle here opens a full
   mobile menu panel, distinct from the desktop inline row). */
#site-nav-links a { white-space: nowrap; }

/* ── Icon Dropdowns (GitHub-style monochrome grey/white) ────────────────
   Used throughout the site for action menus on modifiable sections. */
.icon-dropdown {
    position: relative;
    display: inline-block;
}
/* Outlined rounded-square icon button — modeled on x402scan.com's nav/utility
   icon buttons (globe/search/wallet/GitHub/theme row): a visible border at
   rest, not just on hover, so the button reads as a real control immediately
   rather than a bare glyph that happens to get a border when touched. */
.icon-dropdown-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 30px;
    height: 30px;
    padding: 0;
    background: rgba(255, 255, 255, 0.03);
    border: 1px solid var(--border);
    color: #a1a1aa;
    cursor: pointer;
    transition: all 0.15s ease;
    border-radius: 8px;
}
.icon-dropdown-btn:hover {
    color: #e4e4e7;
    border-color: rgba(255, 255, 255, 0.25);
    background: rgba(255, 255, 255, 0.07);
}
.icon-dropdown-menu {
    position: absolute;
    top: 100%;
    right: 0;
    margin-top: 6px;
    min-width: 160px;
    background: #09090b;
    border: 1px solid var(--border);
    border-radius: 8px;
    box-shadow: 0 4px 12px rgba(0,0,0,0.5);
    opacity: 0;
    visibility: hidden;
    transform: translateY(-4px);
    transition: all 0.15s ease;
    z-index: 1000;
    overflow: hidden;
}
.icon-dropdown.active .icon-dropdown-menu {
    opacity: 1;
    visibility: visible;
    transform: translateY(0);
}
.icon-dropdown-menu a,
.icon-dropdown-menu button {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    text-align: left;
    padding: 8px 12px;
    background: none;
    border: none;
    color: #d4d4d8;
    font-size: 13px;
    font-family: 'JetBrains Mono', ui-monospace, monospace;
    cursor: pointer;
    transition: all 0.15s ease;
}
.icon-dropdown-menu i { width: 14px; text-align: center; color: #71717a; font-size: 11px; flex-shrink: 0; }
.icon-dropdown-menu a:hover,
.icon-dropdown-menu button:hover {
    background: rgba(255, 255, 255, 0.08);
    color: #f4f4f5;
}
.icon-dropdown-menu a:first-child,
.icon-dropdown-menu button:first-child {
    border-radius: 3px 3px 0 0;
}
.icon-dropdown-menu a:last-child,
.icon-dropdown-menu button:last-child {
    border-radius: 0 0 3px 3px;
}
.icon-dropdown-divider {
    height: 1px;
    background: var(--border);
    margin: 4px 0;
}

/* ── Modifiable Sections Container ──────────────────────────────────── */
.modifiable-section {
    background: var(--bg-panel);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    padding: 1.25rem;
    transition: border-color 0.2s ease;
}
.modifiable-section:hover {
    border-color: rgba(255, 255, 255, 0.2);
}
@media (min-width: 640px) {
    .modifiable-section { padding: 1.75rem; }
}

/* ── Enhanced Search/Sort Container ─────────────────────────────────── */
.search-sort-bar {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    margin-bottom: 1rem;
    align-items: center;
}
.search-sort-bar input {
    background: var(--bg-panel-sm);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    color: #d4d4d8;
    padding: 6px 10px;
    font-family: var(--font-sans);
    font-size: 12px;
    transition: border-color 0.15s ease, background-color 0.15s ease;
}
.search-sort-bar input:focus {
    outline: none;
    border-color: rgba(255, 255, 255, 0.3);
}
.search-sort-bar input::placeholder {
    color: #71717a;
}

/* ── Pill Dropdown Selects (x402scan-style: rounded pill, chevron, dark-grey
   offset background) — used for every sort/filter/chain select site-wide. ── */
select.select-pill {
    appearance: none;
    -webkit-appearance: none;
    -moz-appearance: none;
    background-color: var(--bg-panel-sm);
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6' fill='none'%3E%3Cpath d='M1 1L5 5L9 1' stroke='%23a1a1aa' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 10px center;
    background-size: 10px 6px;
    border: 1px solid var(--border);
    border-radius: 999px;
    color: #d4d4d8;
    font-family: var(--font-sans);
    font-size: 12px;
    font-weight: 500;
    padding: 6px 28px 6px 12px;
    cursor: pointer;
    transition: border-color 0.15s ease, background-color 0.15s ease;
}
select.select-pill:hover {
    border-color: rgba(255, 255, 255, 0.25);
    background-color: rgba(255, 255, 255, 0.06);
}
select.select-pill:focus {
    outline: none;
    border-color: rgba(255, 255, 255, 0.35);
}
select.select-pill-lg {
    padding: 12px 32px 12px 14px;
    font-size: 14px;
    border-radius: var(--radius-md);
    background-position: right 14px center;
    background-size: 11px 7px;
}

/* ── Pagination Controls ───────────────────────────────────────────── */
.pagination-info {
    font-size: 11px;
    color: #a1a1aa;
    margin: 8px 0;
}
.pagination-controls {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 11px;
}

/* ── Offering Cards with Contextual Icons ──────────────────────────────── */
/* Each offering card icon is contextual (shield for security, chart for data,
   microscope for audit, etc.) with subtle color accents on hover that stay
   professional within the monochrome palette. */

[data-offering*="audit"],
[data-offering*="deep_dive"],
[data-offering*="exploit"],
[data-offering*="check"] {
    --icon-hover-color: #f87171;
}

[data-offering*="market"],
[data-offering*="chart"],
[data-offering*="volume"],
[data-offering*="intel"],
[data-offering*="yield"] {
    --icon-hover-color: #60a5fa;
}

[data-offering*="wallet"],
[data-offering*="treasury"],
[data-offering*="liquidity"],
[data-offering*="fees"],
[data-offering*="pnl"] {
    --icon-hover-color: #34d399;
}

[data-offering*="broadcast"],
[data-offering*="website"],
[data-offering*="globe"] {
    --icon-hover-color: #a78bfa;
}

.offering-card-icon {
    color: #71717a;
    transition: color 0.25s ease;
}

.offering-card:hover .offering-card-icon {
    color: var(--icon-hover-color, #d4d4d8);
}


/* ── Light mode: neutralising the hardcoded dark chrome ──────────────────
   The Wire's page shell is Tailwind utilities written when dark was the only
   theme -- text-zinc-200 on <body>, bg-zinc-950 on the nav, border-white/10
   throughout. Those are literal colours, so they do not follow the tokens
   and would leave a light page with dark chrome and invisible borders.
   Rewriting every utility across the templates would be a large, churny diff
   for no behavioural gain, so light mode overrides them here instead, in one
   place, where the reasoning stays next to the tokens it is defending.
   Scoped under the theme attribute, so dark mode is untouched. */
:root[data-theme="light"] body { background: var(--bg-page); }
:root[data-theme="light"] body.text-zinc-200 { color: var(--text-body); }
:root[data-theme="light"] .bg-zinc-950\/80,
:root[data-theme="light"] .bg-zinc-950 { background-color: rgba(255, 255, 255, 0.82) !important; }
:root[data-theme="light"] .bg-zinc-900,
:root[data-theme="light"] .bg-zinc-900\/50 { background-color: var(--bg-panel) !important; }
:root[data-theme="light"] .border-white\/10,
:root[data-theme="light"] .border-white\/5 { border-color: var(--border) !important; }
:root[data-theme="light"] .text-zinc-100 { color: var(--text-body) !important; }
:root[data-theme="light"] .text-zinc-400,
:root[data-theme="light"] .text-zinc-500 { color: var(--text-muted) !important; }
:root[data-theme="light"] .text-zinc-600 { color: #7a7a84 !important; }
:root[data-theme="light"] .hover\:text-zinc-100:hover { color: var(--text-body) !important; }

/* The card grid and any panel that paints its own dark ground. */
:root[data-theme="light"] .panel,
:root[data-theme="light"] .news-card { background: var(--bg-panel); border-color: var(--border); }
