/* Prism — "Refraction"
   Cool graphite ground on which the five platform hues become the only colour.
   Typography: Bricolage Grotesque (display/UI) / Newsreader (document prose)
   / JetBrains Mono (data).

   THE RULE: hue means platform. Every saturated colour in this file belongs to
   one of the five platform advocates, so a coloured pixel always answers
   "which platform?". Interactive affordances (buttons, links, focus) are ink,
   not colour. There is deliberately no house accent.

   One documented exception: --color-success stays green. Green is the one hue no
   platform owns (aws=orange, azure/gcp=blue, databricks=red, snowflake=cyan),
   so it can mean "complete" without ever being mistaken for a platform. Every
   other former status hue (warn, danger, info) is now ink-based, differentiated
   by weight, border style and a mono label instead of by hue.
*/

/* ---- Design tokens ----

   The palette itself lives in input.css's `@theme` block, because Tailwind v4 is
   CSS-first: every value declared there is emitted as a `--color-*` custom
   property AND generates the matching utilities, so there is exactly one
   definition feeding both. Retheming means editing input.css and nothing else.

   This file only aliases those into the `--prism-*` names its own rules use, so
   the ~200 existing declarations below did not have to be rewritten.

   Theme switching is `light-dark()` in input.css keyed off `color-scheme`, which
   is set below. That replaced two duplicated blocks of ~20 declarations each —
   one per theme — and it is only possible on v4: v3 compiled opacity modifiers
   by interpolating an alpha into `rgb(<channels> / <alpha>)`, so a
   `light-dark()` value would have broken every `/opacity` utility. v4 uses
   color-mix() instead.
*/
:root {
    /* Graphite is Prism's identity, not a preference, so it is the default for
       everyone. Light is opt-in via /settings — there is deliberately no
       prefers-color-scheme branch, because most machines report light and
       honouring it would mean almost nobody saw the product's actual look. */
    color-scheme: dark;

    --prism-bg: var(--color-canvas);
    --prism-surface: var(--color-surface);
    --prism-surface-alt: var(--color-surface-alt);
    --prism-border: var(--color-hairline);
    --prism-border-strong: var(--color-hairline-strong);
    --prism-text: var(--color-ink);
    --prism-text-soft: var(--color-ink-soft);
    --prism-text-muted: var(--color-ink-muted);
    --prism-text-subtle: var(--color-ink-subtle);
    --prism-accent: var(--color-accent);
    --prism-accent-hover: var(--color-accent-hover);
    --prism-accent-pressed: var(--color-accent-pressed);
    --prism-accent-soft: color-mix(in oklab, var(--color-accent) 8%, transparent);
    --prism-accent-ring: color-mix(in oklab, var(--color-accent) 18%, transparent);

    --prism-shadow-sm: 0 1px 2px rgb(0 0 0 / 0.30);
    --prism-shadow-md: 0 4px 14px -2px rgb(0 0 0 / 0.34),
                       0 2px 6px -1px rgb(0 0 0 / 0.30);
    --prism-shadow-lg: 0 14px 32px -8px rgb(0 0 0 / 0.44),
                       0 6px 14px -4px rgb(0 0 0 / 0.34);
    --prism-radius: 0.75rem;
    --prism-radius-sm: 0.5rem;

    /* Platform hues come from @theme too; these aliases keep the existing
       [data-platform] rules and their `var(--platform-*)` references working. */
    --platform-aws: var(--color-platform-aws);
    --platform-azure: var(--color-platform-azure);
    --platform-gcp: var(--color-platform-gcp);
    --platform-databricks: var(--color-platform-databricks);
    --platform-snowflake: var(--color-platform-snowflake);
    --platform-pdm: var(--color-platform-pdm);
    --platform-pdm-soft: var(--color-platform-pdm-soft);
    --platform-pdm-border: var(--color-platform-pdm-border);
    --platform-default: var(--color-platform);
}

/* Attribute-based platform styling. Add data-platform="aws" to any
   element and it inherits the right hue. Works with base names and
   with the "pdm:xxx" compound keys we use for Partner Advisors. */
[data-platform="aws"],
[data-platform="pdm:aws"] {
    --p-color: var(--platform-aws);
}
[data-platform="azure"],
[data-platform="pdm:azure"] {
    --p-color: var(--platform-azure);
}
[data-platform="gcp"],
[data-platform="pdm:gcp"] {
    --p-color: var(--platform-gcp);
}
[data-platform="databricks"],
[data-platform="pdm:databricks"] {
    --p-color: var(--platform-databricks);
}
[data-platform="snowflake"],
[data-platform="pdm:snowflake"] {
    --p-color: var(--platform-snowflake);
}
/* Derived once for every platform. `--p-color` is set by the blocks above;
   these three follow from it, which is why adding a platform means adding one
   line rather than four. */
[data-platform] {
    --p-soft: color-mix(in oklab, var(--p-color, var(--platform-default)) 18%, var(--color-surface));
    --p-border: color-mix(in oklab, var(--p-color, var(--platform-default)) 45%, var(--color-surface));
    /* Contrast-safe text variant: lighten on the dark ground, darken on light. */
    --p-ink: oklch(from var(--p-color, var(--platform-default)) 0.78 c h);
}
:root[data-theme="light"] [data-platform] {
    --p-ink: oklch(from var(--p-color, var(--platform-default)) 0.45 c h);
}

/* No `[data-platform]` catch-all rule here, deliberately.
   A custom property cannot reference itself: `--p-color: var(--p-color, …)`
   is invalid at computed-value time and resolves to the guaranteed-invalid
   value. Because `[data-platform]` has the same specificity (0,1,0) as
   `[data-platform="aws"]`, such a rule placed after the blocks above would
   win the cascade and wipe out every platform hue — which is exactly the bug
   that made all five platforms render terracotta.
   Defaults come from the inline `var(--p-color, var(--platform-default))`
   fallback at each use site below, which also covers dynamically onboarded
   platforms whose slug matches none of the blocks above. */

/* Utility classes that let templates opt in without per-platform CSS. */
.platform-dot {
    display: inline-block;
    width: 0.55rem;
    height: 0.55rem;
    border-radius: 999px;
    background: var(--p-color, var(--platform-default));
    /* Hairline ring so the dot's *shape* is defined by ink rather than by hue.
       Needed because some brand hues cannot clear 3:1 as graphic objects on
       every ground — on the light theme aws (1.98), snowflake (2.19) and gcp
       (2.36) all fall short. With the ring, the hue is identification and the
       ring is legibility, so neither has to do both jobs. */
    box-shadow: 0 0 0 1px color-mix(in oklab, var(--color-ink) 35%, transparent);
    margin-right: 0.4rem;
    vertical-align: baseline;
    flex-shrink: 0;
}
.platform-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    padding: 0.15rem 0.65rem;
    border-radius: 999px;
    font-size: 0.75rem;
    font-weight: 500;
    /* --p-ink, not --p-color: the raw brand hue on its own tint measured
       1.96-3.98:1 for all five platforms, i.e. every chip failed AA. */
    color: var(--p-ink, var(--color-ink));
    background: var(--p-soft, var(--color-surface-alt));
    border: 1px solid var(--p-border, var(--color-hairline));
    text-transform: capitalize;
}
/* .platform-accent-border is declared *after* .card further down this file —
   see the note there. Placing it here would let .card's `border` shorthand
   reset the platform stripe at equal specificity. */

/* ---- Fonts ----
   Three roles, each doing a job the others cannot:

     display  Bricolage Grotesque  headings, nav, buttons, labels
     document Newsreader           debate arguments and report prose — the only
                                   genuinely long-form reading in the app, and a
                                   record of an argument, so a reading face earns
                                   its place
     data     JetBrains Mono       scores, prices, groundedness tags, source
                                   URLs, session ids, timers

   All self-hosted variable woff2 (refresh with `npm run build:fonts`), so there
   are no third-party connections on the critical path. The document face is
   confined to prose containers — it must not leak into chrome or forms, since
   that split is the point.
*/
@font-face {
    font-family: 'Bricolage Grotesque var';
    font-style: normal;
    font-display: swap;
    font-weight: 200 800;
    src: url('/static/fonts/bricolage-grotesque.woff2') format('woff2-variations');
}
@font-face {
    font-family: 'Newsreader var';
    font-style: normal;
    font-display: swap;
    font-weight: 200 800;
    src: url('/static/fonts/newsreader.woff2') format('woff2-variations');
}
@font-face {
    font-family: 'JetBrains Mono var';
    font-style: normal;
    font-display: swap;
    font-weight: 100 800;
    src: url('/static/fonts/jetbrains-mono.woff2') format('woff2-variations');
}
@font-face {
    font-family: 'Inter var';
    font-style: normal;
    font-display: swap;
    font-weight: 100 900;
    src: url('/static/fonts/inter-variable.woff2') format('woff2-variations');
}

:root {
    --font-display: 'Bricolage Grotesque var', 'Inter var', system-ui,
                    -apple-system, 'Segoe UI', Roboto, sans-serif;
    --font-body: 'Inter var', system-ui, -apple-system, 'Segoe UI', Roboto,
                 sans-serif;
    --font-doc: 'Newsreader var', Georgia, 'Times New Roman', serif;
    --font-data: 'JetBrains Mono var', ui-monospace, SFMono-Regular, Menlo,
                 monospace;
}

/* ---- Font stack ---- */
body {
    font-family: var(--font-body);
    font-feature-settings: "cv02", "cv11", "ss01";
    letter-spacing: -0.005em;
    color: var(--prism-text);
    background: var(--prism-bg);
    /* Column layout so the footer's `mt-auto` actually works. Without a flex
       context it was inert, leaving up to 414px of dead canvas below the
       footer on content-light routes (history, analytics, login).
       `svh` rather than `vh` so mobile browser chrome is accounted for. */
    display: flex;
    flex-direction: column;
    min-height: 100svh;
    /* Flat ground. The two radial terracotta washes that used to sit here
       were decoration competing with the platform hues. */
}

/* ---- Dark theme — manual + system preference ----

   Both themes are declared once, in input.css's `@theme`, as the two arguments
   of light-dark(). All that remains here is flipping `color-scheme`.

   Kept as a note because the failure mode was subtle: an earlier version of
   this file redefined the palette locally and overrode only the `--prism-*`
   aliases, so this stylesheet's rules went dark while every Tailwind utility
   kept its light value — `text-ink` rendered near-black on a dark surface at
   1.01:1, and ~70 lines of `!important` existed to paper over it.
*/
:root[data-theme="dark"] { color-scheme: dark; }

/* Light ("cool paper"). Every value is the second argument of a
   light-dark() in input.css; flipping color-scheme is the whole switch. */
:root[data-theme="light"] { color-scheme: light; }



/* Form controls follow the tokens. These are element selectors rather than
   utilities because inputs are styled inconsistently across templates; once
   the intake form is restructured this can be narrowed to a .form-input
   component class. No !important needed — the old override block existed only
   to fight hard-coded hexes in templates, which the token sweep removed. */
input,
select,
textarea {
    background-color: var(--prism-surface);
    color: var(--prism-text);
    border-color: var(--prism-border);
}
input::placeholder,
textarea::placeholder {
    color: var(--prism-text-subtle);
}

h1, h2, h3, h4, h5, h6 {
    font-family: var(--font-display);
    font-weight: 600;
    /* Bricolage has a width axis; widen headings slightly so they read as set
       rather than defaulted. */
    font-stretch: 105%;
    letter-spacing: -0.015em;
}

/* Document voice — apply to debate arguments and report prose only. */
.prose-doc {
    font-family: var(--font-doc);
    font-size: 1.0625rem;
    line-height: 1.65;
    /* Newsreader can read thin on a dark ground; carry a touch more weight. */
    font-weight: 420;
    font-optical-sizing: auto;
}
.prose-doc p + p { margin-top: 0.85em; }

/* Data voice — scores, prices, citations, ids, timers. */
.data,
.tabular {
    font-family: var(--font-data);
    font-variant-numeric: tabular-nums;
    font-feature-settings: "zero" 1;
    letter-spacing: -0.01em;
}

/* Balance headline ragging and avoid orphans in prose. */
h1, h2, h3 { text-wrap: balance; }
p, li { text-wrap: pretty; }

/* ---- Emphasis ----
   `.gradient-text` used to paint a three-stop terracotta gradient into
   headline text. Gradient-filled type is the single most common tell of a
   templated design, and it competed with the platform hues. Emphasis is now
   weight and scale. The class is kept as a no-op alias so the ~20 call sites
   keep working until they are cleaned up per-page. */
.gradient-text {
    color: var(--color-ink);
    font-weight: 700;
}

/* ---- Buttons ----
   Flat ink fill, canvas-coloured text. No gradient, no house hue: on graphite
   this reads as a near-white key surrounded by dark, which is more emphatic
   than a coloured button and keeps colour reserved for platforms.
   `.btn-gradient` is retained as an alias for existing call sites. */
.btn,
.btn-gradient {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    font-family: var(--font-display);
    background: var(--color-accent);
    color: var(--color-on-accent);
    border: 1px solid transparent;
    border-radius: var(--prism-radius-sm);
    padding: 0.625rem 1.4rem;
    font-weight: 600;
    cursor: pointer;
    transition: background 0.15s ease, transform 0.12s ease, box-shadow 0.2s ease;
}
.btn:hover,
.btn-gradient:hover {
    background: var(--color-accent-hover);
    transform: translateY(-1px);
    box-shadow: var(--prism-shadow-md);
}
.btn:active,
.btn-gradient:active { transform: translateY(0); }
.btn:focus-visible,
.btn-gradient:focus-visible {
    outline: 2px solid var(--color-ink);
    outline-offset: 2px;
}

/* Secondary: hairline outline, no fill. */
.btn-quiet {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    font-family: var(--font-display);
    background: transparent;
    color: var(--color-ink);
    border: 1px solid var(--color-hairline-strong);
    border-radius: var(--prism-radius-sm);
    padding: 0.625rem 1.4rem;
    font-weight: 500;
    cursor: pointer;
    transition: background 0.15s ease, border-color 0.15s ease;
}
.btn-quiet:hover {
    background: var(--color-surface-alt);
    border-color: var(--color-ink-muted);
}

/* ---- Card component ---- */
.card {
    background-color: var(--prism-surface);
    border: 1px solid var(--prism-border);
    border-radius: var(--prism-radius);
    box-shadow: var(--prism-shadow-sm);
    transition: box-shadow 0.25s ease, border-color 0.25s ease, transform 0.25s ease;
}
.card:hover {
    box-shadow: var(--prism-shadow-md);
    border-color: var(--prism-border-strong);
}

/* Platform stripe — MUST stay after .card.
   .card sets the `border` shorthand, which resets all four sides. Both
   selectors are specificity (0,1,0), so whichever comes last wins. Declared
   here, and with longhand properties, so the platform hue survives. */
.platform-accent-border {
    border-top-width: 3px;
    border-top-style: solid;
    border-top-color: var(--p-color, var(--platform-default));
}

/* ---- Phase indicator — the 4-step stepper ---- */
#phase-indicator {
    border-bottom: 1px solid var(--prism-border);
    /* Was rgba(255,255,255,0.72) — an opaque-ish white regardless of theme,
       which is why the phase bar rendered as a light grey band across the top
       of every page in dark mode. Now derived from the canvas token. */
    background: color-mix(in oklab, var(--color-canvas) 72%, transparent);
    backdrop-filter: saturate(160%) blur(8px);
    -webkit-backdrop-filter: saturate(160%) blur(8px);
}
/* Colour comes from the utility classes that phase_class() emits
   (text-accent / text-success / text-ink-subtle), so these rules carry only
   the shape. They previously restated the colour with !important purely to
   outrank those utilities — a fight the cascade did not need to have. */
#phase-indicator .current {
    background: var(--prism-accent-soft);
    padding: 0.25rem 0.75rem;
    border-radius: 999px;
    box-shadow: inset 0 0 0 1px var(--prism-accent-ring);
}

/* ---- Focus ring for any focusable element ---- */
:where(a, button, input, textarea, select, summary):focus-visible {
    outline: 2px solid var(--color-ink);
    outline-offset: 2px;
    border-radius: 0.4rem;
}

/* ---- Subtle scrollbar refinement (webkit only) ---- */
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
    background: color-mix(in oklab, var(--color-ink) 18%, transparent);
    border-radius: 999px;
    border: 2px solid transparent;
    background-clip: padding-box;
}
::-webkit-scrollbar-thumb:hover { background-color: color-mix(in oklab, var(--color-ink) 30%, transparent); }

/* ---- Selection ---- */
::selection { background: color-mix(in oklab, var(--color-ink) 22%, transparent); color: var(--color-ink); }

/* ---- Animations ---- */
@keyframes fade-in {
    from { opacity: 0; }
    to   { opacity: 1; }
}
@keyframes slide-up {
    from { opacity: 0; transform: translateY(0.5rem); }
    to   { opacity: 1; transform: translateY(0); }
}
@keyframes scale-in {
    from { opacity: 0; transform: scale(0.97); }
    to   { opacity: 1; transform: scale(1); }
}
@keyframes shimmer {
    0% { background-position: -200% 0; }
    100% { background-position: 200% 0; }
}

.animate-fade-in  { animation: fade-in  0.35s ease both; }
.animate-slide-up { animation: slide-up 0.35s cubic-bezier(0.22, 0.61, 0.36, 1) both; }
.animate-scale-in { animation: scale-in 0.25s ease both; }

.shimmer {
    background: linear-gradient(
        90deg,
        transparent 0%,
        color-mix(in oklab, var(--color-ink) 7%, transparent) 50%,
        transparent 100%
    );
    background-size: 200% 100%;
    animation: shimmer 2.4s ease-in-out infinite;
}

/* Respect reduced motion preferences. */
@media (prefers-reduced-motion: reduce) {
    .animate-fade-in,
    .animate-slide-up,
    .animate-scale-in,
    .shimmer {
        animation: none !important;
    }
}

/* ---- The spectrum spine ------------------------------------------------
   See partials/spectrum_spine.html for the encoding rationale. Colour is
   identification; rank is carried by order, printed number and texture, so the
   rail stays readable with no colour perception at all. */
.spine {
    background: var(--color-canvas);
    padding: 0.5rem 0 0;
}
.spine__rail {
    display: flex;
    gap: 2px;
    height: 0.5rem;
    margin: 0 auto;
    max-width: 80rem;
    padding: 0 1rem;
    align-items: stretch;
}
.spine__band {
    position: relative;
    flex: 0 1 var(--band-w, 20%);
    min-width: 0.75rem;
    border-radius: 999px;
    /* The unfilled track has to be visibly a track. On graphite
       --color-surface-sunken sits within a couple of steps of the canvas, so a
       part-filled band read as a floating segment rather than progress. An
       inset hairline gives the band an edge without adding a hue. */
    background: var(--color-surface-alt);
    box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--color-ink) 10%, transparent);
    /* Deliberately NOT overflow:hidden. The labels hang below the rail, and
       clipping them hid the score numerals entirely — the fill clips itself via
       border-radius instead. */
    /* Width is the only animated property, and only when state changes. */
    transition: flex-basis 0.55s cubic-bezier(0.22, 0.61, 0.36, 1);
}
.spine__band--whole {
    flex: 1 1 100%;
    background: linear-gradient(90deg,
        color-mix(in oklab, var(--color-ink) 35%, transparent),
        color-mix(in oklab, var(--color-ink) 12%, transparent));
}
/* The fill is the platform hue. On the research phase it grows with progress;
   elsewhere it is full. */
.spine__band-fill {
    position: absolute;
    inset: 0 auto 0 0;
    width: var(--band-fill, 100%);
    background: var(--p-color, var(--platform-default));
    border-radius: inherit;
    transition: width 0.4s ease;
}
/* Redundant encoding: a per-platform texture so bands remain distinguishable
   without colour. Applied over the hue at low contrast so it reads as material
   rather than decoration. */
.spine__band[data-texture="1"] .spine__band-fill {
    background-image: repeating-linear-gradient(45deg,
        rgb(0 0 0 / 0.22) 0 2px, transparent 2px 5px);
}
.spine__band[data-texture="2"] .spine__band-fill {
    background-image: radial-gradient(rgb(0 0 0 / 0.28) 1px, transparent 1.4px);
    background-size: 5px 5px;
}
.spine__band[data-texture="3"] .spine__band-fill {
    background-image: repeating-linear-gradient(90deg,
        rgb(0 0 0 / 0.24) 0 2px, transparent 2px 6px);
}
.spine__band[data-texture="4"] .spine__band-fill {
    background-image: repeating-linear-gradient(-45deg,
        rgb(0 0 0 / 0.20) 0 1.5px, transparent 1.5px 4px),
        repeating-linear-gradient(45deg,
        rgb(0 0 0 / 0.20) 0 1.5px, transparent 1.5px 4px);
}
/* Labels sit under the rail, in the data face, always printed. */
.spine__band-label {
    position: absolute;
    top: 0.75rem;
    left: 0;
    display: flex;
    align-items: baseline;
    gap: 0.3rem;
    font-family: var(--font-data);
    font-size: 0.625rem;
    letter-spacing: 0.02em;
    white-space: nowrap;
    color: var(--color-ink-muted);
    pointer-events: none;
}
.spine__band-name { text-transform: uppercase; }
.spine__band-score {
    color: var(--color-ink);
    font-weight: 600;
}
/* Leave room for the labels that hang below the rail. */
.spine[data-phase="debate"] .spine__rail,
.spine[data-phase="report"] .spine__rail,
.spine[data-phase="research"] .spine__rail { margin-bottom: 1.1rem; }

/* Report: the winner recombines. Its band takes the room and the others
   compress into a dissent strip — the same object that showed the process now
   shows the outcome. */
.spine[data-phase="report"] .spine__band { flex-basis: 6%; opacity: 0.55; }
.spine[data-phase="report"] .spine__band.is-winner {
    flex-basis: 62%;
    opacity: 1;
}
/* The dissent bands are ~6% wide, so their labels collided into each other.
   Only the winner is labelled inline; the rest stay inspectable via title, and
   the full standings are in the report's score table right below. */
.spine[data-phase="report"] .spine__band:not(.is-winner) .spine__band-label {
    display: none;
}

@media (prefers-reduced-motion: reduce) {
    .spine__band,
    .spine__band-fill { transition: none; }
}

/* Cross-document morph between phases where supported. Each band carries a
   view-transition-name (set inline, per platform), so the browser animates the
   rail from one page to the next rather than JavaScript doing it. */
@media (prefers-reduced-motion: no-preference) {
    ::view-transition-group(*) { animation-duration: 0.45s; }
}

/* ---- The verdict ------------------------------------------------------
   The report's payoff. Leads with the recommendation and, immediately after,
   how well evidenced it is — the critic's aggregate used to be computed and
   thrown away. The winning platform's own hue appears here (and only here) as
   a top rule, so the verdict is chromatically tied to the spine band that
   expanded to produce it. */
.verdict {
    border-top: 3px solid var(--p-color, var(--color-ink));
}
.verdict__eyebrow {
    font-size: 0.6875rem;
    text-transform: uppercase;
    letter-spacing: 0.09em;
    color: var(--color-ink-muted);
    margin-bottom: 0.5rem;
}
.verdict__headline {
    display: flex;
    align-items: baseline;
    gap: 0.6rem;
    flex-wrap: wrap;
}
.verdict__name {
    font-family: var(--font-display);
    font-size: clamp(1.75rem, 4vw, 2.5rem);
    font-weight: 700;
    font-stretch: 108%;
    letter-spacing: -0.02em;
    line-height: 1.05;
    color: var(--color-ink);
}
.verdict__score {
    font-size: clamp(1.25rem, 2.5vw, 1.75rem);
    font-weight: 600;
    color: var(--p-ink, var(--color-ink));
}
.verdict__support {
    margin-top: 0.75rem;
    font-size: 0.9375rem;
    line-height: 1.6;
}
.verdict__ground {
    font-weight: 600;
    color: var(--color-ink);
}

/* Rebuttal attribution. The left rule carries the contesting platform's hue,
   so an argument card visibly shows who is arguing against it. */
.rebuttal-from {
    border-left-color: var(--p-color, var(--color-hairline-strong));
}

/* ---- Disclosure ---------------------------------------------------------
   Drill-down that is present but not pre-scrolled. The report used to open with
   every section expanded — round-by-round tables, cost breakdowns, CRM context —
   so the recommendation competed with ~3,000px of detail below it. These are
   still one interaction away, and still in the DOM for search and for tests. */
.disclosure {
    border: 1px solid var(--color-hairline);
    border-radius: var(--prism-radius);
    background: var(--color-surface);
}
.disclosure__summary {
    font-family: var(--font-display);
    font-size: 0.9375rem;
    font-weight: 600;
    color: var(--color-ink);
    padding: 0.9rem 1.25rem;
    cursor: pointer;
    list-style: none;
    display: flex;
    align-items: center;
    gap: 0.6rem;
    border-radius: var(--prism-radius);
}
.disclosure__summary::-webkit-details-marker { display: none; }
/* Explicit affordance: a rotating chevron, so "there is more here" does not
   depend on the browser's default triangle. */
.disclosure__summary::before {
    content: "";
    width: 0.4rem;
    height: 0.4rem;
    border-right: 1.5px solid var(--color-ink-muted);
    border-bottom: 1.5px solid var(--color-ink-muted);
    transform: rotate(-45deg);
    transition: transform 0.18s ease;
    flex-shrink: 0;
}
.disclosure[open] > .disclosure__summary::before { transform: rotate(45deg); }
.disclosure__summary:hover { background: var(--color-surface-alt); }
.disclosure__summary:focus-visible {
    outline: 2px solid var(--color-ink);
    outline-offset: 2px;
}
/* The wrapped card already carries its own border and padding; drop the outer
   duplication so an opened disclosure does not read as a box inside a box. */
.disclosure__body > .card {
    border: 0;
    margin-bottom: 0;
    background: transparent;
}
@media (prefers-reduced-motion: reduce) {
    .disclosure__summary::before { transition: none; }
}

/* ---- WAF matrix --------------------------------------------------------
   Pillars on rows, platforms on columns. Cell shading is a neutral sequential
   ramp (a score is ordered data); platform identity lives in the column header
   hue. Neither encoding does the other's job. */
.waf-matrix th,
.waf-matrix td { white-space: nowrap; }
.waf-cell {
    display: inline-block;
    min-width: 2.4rem;
    padding: 0.15rem 0.4rem;
    border-radius: 0.35rem;
    color: var(--color-ink);
    font-weight: 600;
    font-size: 0.8125rem;
}

/* ---- Hero exchange -----------------------------------------------------
   The landing thesis, shown with the product's own output shape: two platforms
   contesting one question, each claim carrying the critic's grade.

   Groundedness is encoded TYPOGRAPHICALLY, not chromatically — a solid vs
   dashed underline plus a mono tag. Red would collide with Databricks and green
   would invent a sixth hue, and inside a platform-tinted card either would
   compete with the identity the card already carries. */
.hero-exchange {
    width: 100%;
    max-width: 56rem;
    margin: 2.5rem auto 0;
    text-align: left;
}
.hero-exchange__eyebrow {
    font-size: 0.6875rem;
    text-transform: uppercase;
    letter-spacing: 0.09em;
    color: var(--color-ink-subtle);
    margin-bottom: 0.6rem;
}
.hero-exchange__grid {
    display: grid;
    gap: 1rem;
    grid-template-columns: 1fr;
}
@media (min-width: 48rem) {
    .hero-exchange__grid { grid-template-columns: 1fr 1fr; }
}
.hero-exchange__claim {
    border: 1px solid var(--color-hairline);
    border-top: 2px solid var(--p-color, var(--color-hairline-strong));
    border-radius: var(--prism-radius);
    background: var(--color-surface);
    padding: 1rem 1.15rem 1.1rem;
}
.hero-exchange__who {
    display: flex;
    align-items: center;
    font-family: var(--font-display);
    font-size: 0.8125rem;
    font-weight: 600;
    letter-spacing: 0.02em;
    text-transform: uppercase;
    color: var(--p-ink, var(--color-ink));
    margin-bottom: 0.5rem;
}
.hero-exchange__text {
    font-size: 1rem;
    color: var(--color-ink);
    margin-bottom: 0.7rem;
}
.hero-exchange__grade {
    font-size: 0.6875rem;
    letter-spacing: 0.02em;
    color: var(--color-ink-muted);
    padding-bottom: 1px;
    display: inline-block;
}
.hero-exchange__grade--grounded {
    border-bottom: 2px solid color-mix(in oklab, var(--color-ink) 55%, transparent);
}
.hero-exchange__grade--unverified {
    border-bottom: 2px dashed color-mix(in oklab, var(--color-ink) 35%, transparent);
}
.hero-exchange__verdict {
    margin-top: 1rem;
    font-size: 0.9375rem;
    color: var(--color-ink-muted);
}
.hero-exchange__verdict .data {
    color: var(--color-ink);
    font-weight: 600;
    text-transform: uppercase;
    font-size: 0.6875rem;
    letter-spacing: 0.09em;
}

/* ---- Debate seats -------------------------------------------------------
   The score ticker becomes a persistent seat per advocate. A seat carries three
   facts, not one: its running score, how many platforms are contesting it, and
   what share of its claims the critic could verify. A platform leading on score
   while its evidence goes unverified is exactly what a reader should notice, and
   that was computed and discarded. */
.seat__meta {
    display: flex;
    flex-direction: column;
    gap: 0.1rem;
    margin-top: 0.35rem;
    font-size: 0.625rem;
    letter-spacing: 0.02em;
    color: var(--color-ink-muted);
}
.seat__ground { color: var(--color-ink-soft); }
/* The advocate currently speaking. Luminance, not an extra hue. */
.score-tile.is-speaking {
    box-shadow: inset 0 0 0 1px var(--p-color, var(--color-ink)),
                0 0 0 3px color-mix(in oklab, var(--color-ink) 6%, transparent);
}
