﻿/* ============================================================================
   Truth Meter — editorial theme layer over Bootstrap 5.3
   ----------------------------------------------------------------------------
   Strategy: define a small set of design tokens (--tm-*) for an editorial /
   newsroom look, then map Bootstrap's own --bs-* variables onto them so the
   existing card/grid/badge markup is re-skinned globally with no page changes.
   Dark mode just re-points the --tm-* tokens; every --bs-* mapping follows.
   Loaded AFTER bootstrap.min.css, so these declarations win.
   ============================================================================ */

/* ---- Self-hosted fonts (CSP forbids CDNs; variable woff2 keeps it to a few files) ---- */
@font-face {
    font-family: 'Inter';
    font-style: normal;
    font-weight: 100 900;
    font-display: swap;
    src: url('/fonts/inter-latin-wght-normal.woff2') format('woff2-variations');
}
@font-face {
    font-family: 'Inter';
    font-style: italic;
    font-weight: 100 900;
    font-display: swap;
    src: url('/fonts/inter-latin-wght-italic.woff2') format('woff2-variations');
}
@font-face {
    font-family: 'Source Serif 4';
    font-style: normal;
    font-weight: 200 900;
    font-display: swap;
    src: url('/fonts/source-serif-4-latin-wght-normal.woff2') format('woff2-variations');
}

/* ---- Design tokens (light) ---- */
:root {
    --tm-font-sans: 'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
    --tm-font-serif: 'Source Serif 4', Georgia, 'Times New Roman', serif;

    --tm-ink: #1c1a17;          /* primary text — warm near-black */
    --tm-ink-soft: #57534e;     /* secondary text */
    --tm-ink-faint: #8a857d;    /* faint / meta text */
    --tm-paper: #faf8f4;        /* page background — warm paper */
    --tm-surface: #ffffff;      /* cards, raised surfaces */
    --tm-surface-2: #f3f1ec;    /* subtle alt surface / chips */
    --tm-border: #e6e2d9;       /* hairline borders */
    --tm-border-strong: #d6d0c4;

    --tm-accent: #3a5a80;       /* restrained slate-blue for links/active/primary */

    /* The SECOND axis's hue, used by every context-integrity component. A token rather than the
       literal it used to be in each of them: at #6f42c1 on the dark surface it fell to roughly
       1.9:1, so the badges and markers that carry the whole context axis were effectively
       unreadable in dark mode. Re-pointed below, like every other colour here. */
    --tm-context: #6f42c1;
    --tm-context-soft: #f2ecfb;
    --tm-accent-rgb: 58, 90, 128;
    --tm-accent-hover: #2c4763;
    --tm-accent-contrast: #ffffff;

    /* Dark "ink" sidebar rail — identical in both modes so the white nav icons stay visible. */
    --tm-rail: #201d18;
    --tm-rail-text: #cac4b9;
    --tm-rail-hover: rgba(255, 255, 255, 0.06);
    --tm-rail-active: rgba(255, 255, 255, 0.11);
    --tm-rail-border: rgba(255, 255, 255, 0.08);

    --tm-radius-sm: 0.375rem;
    --tm-radius: 0.5rem;
    --tm-radius-lg: 0.85rem;
    --tm-shadow-sm: 0 1px 2px rgba(28, 26, 23, 0.05), 0 1px 3px rgba(28, 26, 23, 0.05);
    --tm-shadow-md: 0 2px 8px rgba(28, 26, 23, 0.07), 0 8px 24px rgba(28, 26, 23, 0.06);

    /* Map Bootstrap onto the tokens (resolved per-element, so dark mode follows automatically). */
    --bs-body-bg: var(--tm-paper);
    --bs-body-color: var(--tm-ink);
    --bs-emphasis-color: var(--tm-ink);
    --bs-secondary-color: var(--tm-ink-soft);
    --bs-secondary-bg: var(--tm-surface-2);
    --bs-tertiary-bg: var(--tm-surface-2);
    --bs-border-color: var(--tm-border);
    --bs-heading-color: var(--tm-ink);

    --bs-primary: var(--tm-accent);
    --bs-primary-rgb: var(--tm-accent-rgb);
    --bs-link-color: var(--tm-accent);
    --bs-link-color-rgb: var(--tm-accent-rgb);
    --bs-link-hover-color: var(--tm-accent-hover);
    --bs-link-hover-color-rgb: var(--tm-accent-rgb);

    --bs-font-sans-serif: var(--tm-font-sans);
    --bs-border-radius-sm: var(--tm-radius-sm);
    --bs-border-radius: var(--tm-radius);
    --bs-border-radius-lg: var(--tm-radius-lg);
}

/* ---- Design tokens (dark) — just re-point the palette; --bs-* mappings cascade ---- */
[data-bs-theme="dark"] {
    --tm-ink: #ece8e0;
    --tm-ink-soft: #b1aaa0;
    --tm-ink-faint: #837d73;
    --tm-paper: #1a1916;
    --tm-surface: #232220;
    --tm-surface-2: #2c2a27;
    --tm-border: #3a3631;
    --tm-border-strong: #4a453e;

    --tm-context: #b79ae8;
    --tm-context-soft: #2b2338;

    --tm-accent: #88abd6;
    --tm-accent-rgb: 136, 171, 214;
    --tm-accent-hover: #a3c0e2;
    --tm-accent-contrast: #16181c;

    --tm-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
    --tm-shadow-md: 0 6px 22px rgba(0, 0, 0, 0.5);
}

/* ---- Base elements ---- */
html, body {
    font-family: var(--bs-font-sans-serif);
}

body {
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    text-rendering: optimizeLegibility;
}

h1, h2, h3, h4, h5, h6,
.h1, .h2, .h3, .h4, .h5, .h6 {
    font-family: var(--tm-font-serif);
    font-weight: 600;
    letter-spacing: -0.012em;
    color: var(--tm-ink);
}

h1, .h1 { letter-spacing: -0.022em; }

a, .btn-link { color: var(--tm-accent); }

code {
    color: var(--tm-accent);
    background: var(--tm-surface-2);
    padding: 0.12em 0.36em;
    border-radius: 0.25rem;
    font-size: 0.875em;
}

details > summary {
    cursor: pointer;
    color: var(--tm-accent);
    font-weight: 600;
}

/* A URL shown as its own link text — the source video on a session, and the
   citation under an omission whose provider reported no title. It is one word to
   the line breaker, so with nothing but the default `overflow-wrap: normal` it
   refuses to break, runs past the viewport and widens the DOCUMENT: on a 375px
   phone a 50-character x.com status URL took the page to 443px, and the symptom
   is not the link but the fixed topbar, which is 100% of the viewport and so
   stops short of the page it sits on with a white band beyond it.

   `anywhere` rather than `break-word` because the two differ exactly where it
   matters: both break a long word mid-glyph when it would otherwise overflow,
   but only `anywhere` counts that break when the browser computes the element's
   min-content width. `break-word` would leave this link declaring a 419px
   minimum to the flex and grid boxes it sits in, which is the same overflow one
   layer up. Scoped to a class rather than applied to every anchor: ordinary link
   text is prose and should break at its spaces like prose. */
.tm-url {
    overflow-wrap: anywhere;
}

/* ---- Buttons (Bootstrap's .btn-* set local --bs-btn-* vars; re-point them to tokens) ---- */
.btn-primary {
    --bs-btn-color: var(--tm-accent-contrast);
    --bs-btn-bg: var(--tm-accent);
    --bs-btn-border-color: var(--tm-accent);
    --bs-btn-hover-color: var(--tm-accent-contrast);
    --bs-btn-hover-bg: var(--tm-accent-hover);
    --bs-btn-hover-border-color: var(--tm-accent-hover);
    --bs-btn-active-color: var(--tm-accent-contrast);
    --bs-btn-active-bg: var(--tm-accent-hover);
    --bs-btn-active-border-color: var(--tm-accent-hover);
    --bs-btn-disabled-color: var(--tm-accent-contrast);
    --bs-btn-disabled-bg: var(--tm-accent);
    --bs-btn-disabled-border-color: var(--tm-accent);
}

.btn-outline-secondary {
    --bs-btn-color: var(--tm-ink-soft);
    --bs-btn-border-color: var(--tm-border-strong);
    --bs-btn-hover-color: var(--tm-ink);
    --bs-btn-hover-bg: var(--tm-surface-2);
    --bs-btn-hover-border-color: var(--tm-border-strong);
    --bs-btn-active-color: var(--tm-ink);
    --bs-btn-active-bg: var(--tm-surface-2);
    --bs-btn-active-border-color: var(--tm-border-strong);
}

.btn:focus, .btn:active:focus, .btn-link.nav-link:focus,
.form-control:focus, .form-check-input:focus, .form-select:focus {
    border-color: var(--tm-accent);
    box-shadow: 0 0 0 0.2rem rgba(var(--tm-accent-rgb), 0.25);
}

/* ---- Cards ---- */
.card {
    --bs-card-bg: var(--tm-surface);
    --bs-card-border-color: var(--tm-border);
    --bs-card-cap-bg: transparent;
    --bs-card-border-radius: var(--tm-radius-lg);
    --bs-card-inner-border-radius: calc(var(--tm-radius-lg) - 1px);
    box-shadow: var(--tm-shadow-sm);
}

.card-header {
    font-family: var(--tm-font-serif);
    font-weight: 600;
    letter-spacing: 0.005em;
    color: var(--tm-ink);
    background: transparent;
    border-bottom-color: var(--tm-border);
    padding-top: 0.85rem;
    padding-bottom: 0.85rem;
}

/* ---- List groups ---- */
.list-group {
    --bs-list-group-bg: var(--tm-surface);
    --bs-list-group-color: var(--tm-ink);
    --bs-list-group-border-color: var(--tm-border);
    --bs-list-group-action-hover-bg: var(--tm-surface-2);
}

.list-group-item { padding: 0.85rem 1rem; }

/* Bootstrap only rounds a flush list group's last row when the list is a *direct* child of .card. Every
   list on this site is wrapped in a `.card-body p-0` instead -- the wrapper is what lets an empty list
   render an ordinary padded paragraph in the same slot -- so that rule never matches, and the last row's
   square corners paint over the card's rounded ones. The row and the card share a background, so nothing
   changes colour: the corner simply squares off, and the card's border stops short of meeting itself. */
.card > .card-body:last-child > .list-group-flush > .list-group-item:last-child {
    border-bottom-left-radius: var(--bs-card-inner-border-radius);
    border-bottom-right-radius: var(--bs-card-inner-border-radius);
}

/* ---- Alerts (round + keep them theme-aware) ---- */
.alert { border-radius: var(--tm-radius); }

/* ---- Badges: harden the solid/light variants so they read in BOTH themes.
   Bootstrap shifts --bs-secondary-rgb etc. per theme, which can flip a badge to an
   illegible colour; pin the ones this app actually uses. ---- */
.badge.bg-primary {
    background-color: var(--tm-accent) !important;
    color: var(--tm-accent-contrast) !important;
}
.badge.bg-secondary {
    background-color: #6c757d !important;
    color: #fff !important;
}
.badge.bg-dark {
    background-color: var(--tm-rail) !important;
    color: #fff !important;
}
[data-bs-theme="dark"] .badge.bg-dark { background-color: #3c3a35 !important; }
.badge.bg-danger { color: #fff !important; }
.badge.bg-light {
    background-color: var(--tm-surface-2) !important;
    color: var(--tm-ink-soft) !important;
    border-color: var(--tm-border) !important;
}

/* ---- Form validation (kept from template, tokenised colours retained) ---- */
.valid.modified:not([type=checkbox]) { outline: 1px solid #26b050; }
.invalid { outline: 1px solid #e50000; }
.validation-message { color: #e50000; }

.darker-border-checkbox.form-check-input { border-color: #929292; }

.form-floating > .form-control-plaintext::placeholder,
.form-floating > .form-control::placeholder {
    color: var(--bs-secondary-color);
    text-align: end;
}

.form-floating > .form-control-plaintext:focus::placeholder,
.form-floating > .form-control:focus::placeholder {
    text-align: start;
}

h1:focus { outline: none; }

/* ---- Topbar brand + theme toggle (global: the toggle icon keys off [data-bs-theme]) ---- */
.tm-topbar-brand {
    font-family: var(--tm-font-serif);
    font-weight: 600;
    font-size: 1.02rem;
    color: var(--tm-ink);
    letter-spacing: -0.01em;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.tm-theme-toggle {
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
    border: 1px solid var(--tm-border-strong);
    background: var(--tm-surface);
    color: var(--tm-ink-soft);
    border-radius: 999px;
    padding: 0.3rem 0.75rem;
    font-size: 0.8rem;
    font-weight: 600;
    line-height: 1;
    cursor: pointer;
    transition: background-color 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}

.tm-theme-toggle:hover {
    background: var(--tm-surface-2);
    color: var(--tm-ink);
    border-color: var(--tm-border-strong);
}

.tm-theme-toggle-icon {
    width: 1.05rem;
    height: 1.05rem;
    background-repeat: no-repeat;
    background-position: center;
    background-size: contain;
    /* Light mode → show a moon (click switches to dark). */
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='%2357534e' viewBox='0 0 16 16'%3E%3Cpath d='M6 .278a.768.768 0 0 1 .08.858 7.2 7.2 0 0 0-.878 3.46c0 4.021 3.278 7.277 7.318 7.277q.792-.001 1.533-.16a.79.79 0 0 1 .81.316.73.73 0 0 1-.031.893A8.35 8.35 0 0 1 8.344 16C3.734 16 0 12.286 0 7.71 0 4.266 2.114 1.312 5.124.06A.75.75 0 0 1 6 .278'/%3E%3C/svg%3E");
}

[data-bs-theme="dark"] .tm-theme-toggle-icon {
    /* Dark mode → show a sun (click switches to light). */
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='%23b1aaa0' viewBox='0 0 16 16'%3E%3Cpath d='M8 11a3 3 0 1 1 0-6 3 3 0 0 1 0 6m0 1a4 4 0 1 0 0-8 4 4 0 0 0 0 8M8 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 0m0 13a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 13m8-5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2a.5.5 0 0 1 .5.5M3 8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2A.5.5 0 0 1 3 8m10.657-5.657a.5.5 0 0 1 0 .707l-1.414 1.415a.5.5 0 1 1-.707-.708l1.414-1.414a.5.5 0 0 1 .707 0m-9.193 9.193a.5.5 0 0 1 0 .707L3.05 13.657a.5.5 0 0 1-.707-.707l1.414-1.414a.5.5 0 0 1 .707 0m9.193 2.121a.5.5 0 0 1-.707 0l-1.414-1.414a.5.5 0 0 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .707M4.464 4.465a.5.5 0 0 1-.707 0L2.343 3.05a.5.5 0 1 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .708'/%3E%3C/svg%3E");
}

/* The back link a detail page hands to the top bar, in the left-hand slot (the SectionOutlet in
   MainLayout). Global rather than scoped to the layout: the anchor is written in the *page's* markup,
   so it carries that page's scope attribute and not the layout's -- a `.top-row .tm-topbar-back` rule
   in MainLayout.razor.css would compile to a selector that can never match it.

   Pushed left with an auto end-margin rather than by putting `space-between` back on the bar, because
   the slot is empty on every other page and space-between with a lone toggle in it puts the toggle
   back on the left -- the thing flex-end is there to prevent. */
.tm-topbar-back {
    margin-inline-end: auto;
}

/* Exactly one of the two copies is visible, switching at the layout's own breakpoint -- the width
   where the sidebar stops sitting beside the content and the heading loses the room to hold a button
   in its corner. display:none rather than visibility, because it also takes the hidden copy out of
   the accessibility tree: the duplicate anchor is never announced and never tabbed to. */
@media (min-width: 641px) {
    .tm-topbar-back {
        display: none;
    }
}

@media (max-width: 640.98px) {
    .tm-page-back {
        display: none;
    }
}


/* ----------------------------------------------------------------------------
   Archetype standing -- the speaker profile's "which pattern is this, and where
   in it" card. Reuses the home page's --tm-archetype-hue (set by the
   .tm-archetype-* class SpeakerCategoryText.CssClass emits, and re-pointed per
   theme there), so a category is the same colour on both pages without either
   restating the value.

   The hue is a rule and a numeral here, never a fill: it is recognition, not a
   verdict. Borrowing the green-to-red verdict ramp would rank the three against
   each other, and only one of them is an indictment -- the same reason the
   technique chips have a palette of their own.

   One card per standing, hued on its own top edge, because a speaker can hold two
   of these at once and one card could only be one colour. The stripe is on the
   card rather than on a header the way the home page puts it, because there is no
   header here: the eyebrow inside names the category, so a word above it would
   only repeat what the card already says.
   ---------------------------------------------------------------------------- */
.tm-standing {
    border-top: 3px solid var(--tm-archetype-hue, var(--tm-border-strong));
}

.tm-standing-body {
    display: flex;
    align-items: center;
    gap: 1rem;
}

/* Reserved width, not measured width: two stacked standings must line their
   headlines up, and the two numerals are ranks in different lists that need
   not have the same number of digits. Wide enough for three, so the
   alignment is a property of the rule rather than of the corpus being
   small -- the same reason the home page's cards reserve their lines. */
.tm-standing-rank {
    flex: 0 0 auto;
    min-width: 4.25rem;
    text-align: center;
    font-family: var(--tm-font-sans);
    font-size: 1.65rem;
    font-weight: 700;
    line-height: 1;
    font-variant-numeric: tabular-nums;
    color: var(--tm-archetype-hue, var(--tm-ink));
}

/* The denominator, never dropped: "#3" alone flatters a category of three and
   undersells one of ninety. */
.tm-standing-of {
    display: block;
    margin-top: 0.25rem;
    font-size: 0.68rem;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--tm-ink-faint);
}

.tm-standing-what {
    min-width: 0;
}

.tm-standing-headline {
    font-size: 0.95rem;
    font-weight: 600;
    line-height: 1.3;
    text-wrap: balance;
    margin-bottom: 0.15rem;
}

/* ============================================================================
   Veracity scale + score components (shared class names — do not rename).
   Used by StatementList, Scoreboard, Scoring, speaker/debate report pages.
   The colour bands are semantic and stay fixed across themes (legible on both).
   ============================================================================ */
.veracity-high { background-color: #198754; color: #fff; }      /* green  */
.veracity-mixed { background-color: #ffc107; color: #212529; }  /* amber  */
.veracity-low { background-color: #fd7e14; color: #fff; }       /* orange */
.veracity-verylow { background-color: #dc3545; color: #fff; }   /* red    */
.veracity-unknown { background-color: #6c757d; color: #fff; }   /* grey   */

.tm-score-badge {
    display: inline-block;
    padding: 0.35em 0.6em;
    font-size: 0.85em;
    font-weight: 600;
    border-radius: var(--tm-radius-sm);
    white-space: nowrap;
}

.tm-speaker-card {
    min-width: 120px;
    padding: 0.75rem 0.9rem;
    background: var(--tm-surface);
    border: 1px solid var(--tm-border);
    border-radius: var(--tm-radius);
    box-shadow: var(--tm-shadow-sm);
    text-align: center;
}

/* The card's name when there is a profile behind it. Deliberately not accent-coloured: a scoreboard is a
   dozen of these side by side, and a dozen blue names would read as a nav bar and pull the eye off the
   figures underneath. The underline on hover/focus is the whole affordance -- and it must stay on :focus-
   visible, since without a colour difference the keyboard user has nothing else to go on. */
.tm-speaker-name-link {
    color: inherit;
    text-decoration: none;
}

.tm-speaker-name-link:hover,
.tm-speaker-name-link:focus-visible {
    color: inherit;
    text-decoration: underline;
    text-underline-offset: 0.15em;
}

.tm-speaker-avg {
    display: inline-block;
    margin: 0.3rem 0;
    padding: 0.2em 0.7em;
    font-size: 1.3rem;
    font-weight: 700;
    border-radius: var(--tm-radius);
    min-width: 4rem;
}

/* ----------------------------------------------------------------------------
   Speaker avatar. Three things here are load-bearing rather than decorative:

   1. The photo is an <img>, never a CSS background. PhotoUrl is admin-editable
      free text that may also arrive from a model, and interpolating a URL into
      an inline style="background-image:url('...')" is a CSS injection point:
      Razor encodes an apostrophe to &#x27;, the browser decodes it, and the CSS
      parser then sees a string terminator. An attribute value has no second
      parse, so an <img src> closes that hole.
   2. The fallback glyph sits BEHIND the image rather than instead of it, which
      is what lets one code path cover all three misses -- no URL stored, a URL
      SafeUrl rejects, and a URL that simply fails to load. The third is the
      common one, since a photo is a remote host we do not control, and it is
      the only one the server cannot detect: an <img alt=""> that 404s renders
      nothing at all, so the glyph shows through by itself.
   3. The glyph is masked rather than painted, so its colour follows a --tm-*
      token and dark mode re-points it along with everything else instead of
      needing a second copy of the data URI.
   4. Size is ONE custom property, not a class per placement. The same face now
      appears beside a name in a page heading, a table row, a scoreboard card
      and a statement badge, and every one of those wants a different diameter
      but the identical circle, border, cover-fit and glyph. A modifier that
      re-declared those would let them drift apart placement by placement.
   ---------------------------------------------------------------------------- */
:root {
    /* Bootstrap Icons person-fill. The fill colour is irrelevant under a mask --
       only the alpha channel is read. */
    --tm-avatar-glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='%23000' viewBox='0 0 16 16'%3E%3Cpath d='M3 14s-1 0-1-1 1-4 6-4 6 3 6 4-1 1-1 1zm5-6a3 3 0 1 0 0-6 3 3 0 0 0 0 6'/%3E%3C/svg%3E");
}

.tm-avatar {
    position: relative;
    width: var(--tm-avatar-size, 6.5rem);
    height: var(--tm-avatar-size, 6.5rem);
    flex: 0 0 auto;
    border-radius: 50%;
    overflow: hidden;
    background: var(--tm-surface-2);
    border: 1px solid var(--tm-border);
}

/* Beside a name rather than above one: an inline face sits on the text baseline
   badly, so these are used inside a flex row and the alignment is the row's job.
   Large is the default and needs no class -- it is the profile header's portrait.

   Card is 4.5rem rather than the 3.25rem it was: at 52px a face was a coloured
   circle rather than a person, and both placements that take this size -- the
   home page's archetype panels and the per-debate scoreboard -- are asking the
   reader to recognise WHO. Still well short of the 6.5rem portrait, which is for
   a page about one speaker. Raising the size here rather than adding a fifth one
   keeps the closed set in SpeakerAvatarSize honest. */
.tm-avatar-md { --tm-avatar-size: 4.5rem; }
.tm-avatar-sm { --tm-avatar-size: 2.25rem; }

/* Small enough to sit inside a line of text, where a border would read as noise
   at one pixel of a 1.5rem circle. */
.tm-avatar-xs {
    --tm-avatar-size: 1.5rem;
    border: 0;
    vertical-align: text-bottom;
    display: inline-block;
}

.tm-avatar::before {
    content: "";
    position: absolute;
    inset: 0;
    background-color: var(--tm-ink-faint);
    -webkit-mask-image: var(--tm-avatar-glyph);
    mask-image: var(--tm-avatar-glyph);
    -webkit-mask-repeat: no-repeat;
    mask-repeat: no-repeat;
    -webkit-mask-position: center;
    mask-position: center;
    -webkit-mask-size: 58%;
    mask-size: 58%;
}

/* Positioned, so it paints over the ::before glyph; object-fit keeps a portrait
   of any aspect ratio circular rather than squashed. */
.tm-avatar img {
    position: relative;
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/* ----------------------------------------------------------------------------
   Profile header -- the portrait and name on one side, "All speakers" on the
   other.

   The session page runs the same pattern and needs none of this, and the
   difference is worth naming: there the left-hand item is a bare h1, which
   wraps to its longest word and so shrinks to almost nothing on demand. Here it
   is a 6.5rem portrait GLUED to a name, and a portrait does not wrap. At 343px
   that left group would not go below 238px, the button would not go below its
   own 115px of nowrap text, and 238 + 115 + the gap is 369 into a row that has
   295 -- so the button ran 50px past the viewport, clipped mid-word, and took
   the document with it. flex-shrink-0 (the session page's answer) would have
   changed nothing: neither item was shrinking in the first place.

   So the row breaks instead. The basis on the left group is what states the
   threshold -- it asks for 15rem, which is roughly the width at which a name
   still has room to be read beside the face, and the button drops to its own
   line as soon as 15rem plus the button no longer fits. Same shape as the
   statement row, and for the same reason: it is the ROW's width that decides,
   not the viewport's.

   `margin-left: auto` rather than the parent's justify-content, again as in the
   statement row: space-between puts a lone item on a wrapped line at the START,
   which would have swung the button to the left margin on exactly the widths
   this exists for. An auto margin keeps it under the right-hand edge it
   occupies everywhere else.

   The last two declarations are a floor, not the fix. Once the button has
   dropped away the name has the whole row, and every name in the corpus fits
   it; a single word longer than the row -- which a surname can be -- would
   otherwise start the overflow again with nothing left to drop. `anywhere`
   never breaks a word that fits, and because the row wraps before it shrinks,
   flex never squeezes this group while there is still room. It engages only
   when the name genuinely exceeds the full width, which is the one case where a
   broken word beats a broken page.
   ---------------------------------------------------------------------------- */
.tm-profile-head {
    flex-wrap: wrap;
}

.tm-profile-who {
    flex: 1 1 15rem;
}

.tm-profile-back {
    margin-left: auto;
}

.tm-profile-name {
    min-width: 0;
}

.tm-profile-name > h1 {
    overflow-wrap: anywhere;
}

/* ----------------------------------------------------------------------------
   Statement ordinal — the left-hand column of a session's statement list.

   Left-aligned, so a single digit sits at the start of the column rather than
   floating against its right edge with a gap in front of it — the eye looks for
   the row to begin at the row's left edge. It is the fixed minimum WIDTH, not the
   alignment, that keeps the claims on one left edge instead of stepping right a
   digit at a time; tabular figures then keep the leading digits under each other
   and stop a two-digit column jittering between 11 and 18.

   That minimum is stated in ch, which under tabular-nums is exactly one digit's
   advance — so 2.25ch is "two digits and a sliver", and it says so. It was 2rem,
   which at this font size reserved room for THREE digits: a typical session has
   well under twenty statements, so the column sat mostly empty and shoved the
   face, name, timestamp and claim right for nothing. min-width rather than a flex
   basis is what makes the narrow value safe — a session that does reach 100 gets
   a wider column, not a clipped one, and only pays for it once it is there.

   Muted to the same weight as the timestamp beside it. It is a landmark for
   talking about a row — "look at number ten" — not a fact about the claim, and
   anything more prominent would compete with the speaker badge for the eye.
   ---------------------------------------------------------------------------- */
.tm-statement-number {
    flex: 0 0 auto;
    min-width: 2.25ch;
    padding-top: 0.3rem;
    text-align: left;
    font-size: 0.8em;
    font-variant-numeric: tabular-nums;
    color: var(--tm-ink-faint);
}

/* ----------------------------------------------------------------------------
   Statement row — number, claim, verdicts. Three columns on a desktop; two
   lines on a phone.

   The verdict column cannot shrink and must not: "Abs 100% . Mostly false" is
   168px of deliberately unwrapped badge, and a score broken over two lines is
   not a score. So the ROW gives way instead of the column. Below the width where
   both fit, the verdicts wrap to a line of their own under the claim, which is
   the only arrangement that leaves either of them readable -- at 375px the two
   side by side left the claim 137px, and the badges still hung 12px past the
   card and took the document 13px past the viewport.

   The wrap is driven by the ROW's own width, not by a viewport breakpoint, and
   that distinction is load-bearing here: this component also renders inside the
   /batch console and the scoring explainer, where the column it sits in is
   narrower than the page. A media query would measure the wrong box in both.
   `flex-basis` on the claim is what states the threshold -- the claim asks for
   15rem, so the verdicts drop as soon as 15rem + their own width no longer fits,
   and above that the basis is irrelevant because the claim grows past it. It is
   a floor for the DECISION, not a floor for the rendered width: once the
   verdicts have dropped away, the claim shrinks below 15rem quite happily and
   fills whatever the phone actually has.

   `margin-left: auto` on the verdicts, not the parent's justify-content, is what
   holds them at the right edge. space-between aligns a lone item on a wrapped
   line to the START, so the badges would have swung to the left margin on
   exactly the widths this rule exists for; an auto margin eats the free space on
   whichever line the item lands on, so the verdicts sit right in both layouts
   and the reader's eye keeps one place to look for them.
   ---------------------------------------------------------------------------- */
.tm-statement-row {
    flex-wrap: wrap;
}

.tm-statement-claim {
    flex: 1 1 15rem;
}

.tm-statement-verdicts {
    margin-left: auto;
}

/* ============================================================================
   Context integrity — the SECOND axis. Styled deliberately unlike the veracity
   badges above: outlined rather than filled, and in a distinct hue, so it can
   never be mistaken for a modifier of the accuracy score. The interesting case
   is a green accuracy badge sitting beside a flagged context badge — a claim
   that is entirely true and still misleading — and that pairing has to be
   legible at a glance.

   There is no colour scale here because there is no integrity score: findings
   are either present or absent.
   ============================================================================ */
.tm-context-badge {
    display: inline-block;
    padding: 0.3em 0.6em;
    font-size: 0.8em;
    font-weight: 600;
    border-radius: var(--tm-radius-sm);
    white-space: nowrap;
    border: 1px solid var(--tm-context);
    color: var(--tm-context);
    background: transparent;
}

.tm-context-title {
    color: var(--tm-context);
}

/* Examined and clean. Muted on purpose: it reports an absence of findings, not a
   positive result, and must not read as a second green tick. */
.tm-context-clear {
    border-color: var(--tm-border);
    color: var(--tm-text-muted, #6c757d);
}

.tm-speaker-context {
    margin-top: 0.35rem;
    color: var(--tm-text-muted, #6c757d);
}
.tm-speaker-context.tm-context-flagged { color: var(--tm-context); font-weight: 600; }

/* The omission block inside "Evidence & sources". */
.tm-omission {
    border-left: 3px solid var(--tm-context);
    padding-left: 0.6rem;
    margin-bottom: 0.5rem;
}

.tm-omission-technique {
    display: inline-block;
    font-size: 0.75em;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--tm-context);
}

/* Verdict distribution bar (speaker profile) */
.tm-verdict-bar {
    display: flex;
    height: 1.5rem;
    border-radius: var(--tm-radius-sm);
    overflow: hidden;
    background: var(--tm-surface-2);
}
.tm-verdict-bar > div { height: 100%; }

.tm-verdict-swatch {
    display: inline-block;
    width: 0.8rem;
    height: 0.8rem;
    border-radius: 2px;
    vertical-align: middle;
    margin-right: 0.25rem;
}

/* Technique mix (speaker profile). Reuses the verdict bar's geometry via .tm-verdict-bar
   and .tm-verdict-swatch, but never its palette: those colours run green-to-red because a
   verdict is ordered, and techniques are not. Cherry-picking is not "worse" than a missing
   baseline, and borrowing a severity ramp would invent a ranking the data does not carry.

   So these are all one hue family — the context purple, varied in tint — which says
   "different kinds of the same thing" and keeps the second axis visually its own. They are
   tokens rather than literals so the dark palette can be re-pointed the same way the rest of
   the theme is; the fills sit on --tm-surface-2 and need lifting there. */
:root {
    --tm-technique-cherry: #6f42c1;
    --tm-technique-baseline: #8a63d2;
    --tm-technique-timeframe: #a58ae0;
    --tm-technique-subgroup: #5b3aa6;
    --tm-technique-rate: #b98fdd;
    --tm-technique-quote: #452c80;
    --tm-technique-other: #9c93ac;
}

[data-bs-theme="dark"] {
    --tm-technique-cherry: #9b7ae0;
    --tm-technique-baseline: #b193e8;
    --tm-technique-timeframe: #c7b1f0;
    --tm-technique-subgroup: #7e5bc9;
    --tm-technique-rate: #d3bcf3;
    --tm-technique-quote: #6547ab;
    --tm-technique-other: #8d8698;
}

.tm-technique-cherrypicking { background-color: var(--tm-technique-cherry); }
.tm-technique-missingbaseline { background-color: var(--tm-technique-baseline); }
.tm-technique-selectivetimeframe { background-color: var(--tm-technique-timeframe); }
.tm-technique-subgroupselection { background-color: var(--tm-technique-subgroup); }
.tm-technique-absolutevsrate { background-color: var(--tm-technique-rate); }
.tm-technique-quotemining { background-color: var(--tm-technique-quote); }
.tm-technique-other { background-color: var(--tm-technique-other); }

/* One omission in the profile's cross-session list. Same left rule as .tm-omission in the
   statement list, with room for the claim and session it came from — on a profile an
   omission has travelled away from the transcript that explains it. */
.tm-omission-entry {
    border-left: 3px solid var(--tm-context);
    padding-left: 0.6rem;
    margin-bottom: 0.9rem;
}

/* A whole card that belongs to the omissions axis, used by the two instruction pages
   (/scoring, /scoring-simple). Global rather than page-scoped precisely because both use it:
   the pages differ in length on purpose, and a card about omissions must not also differ in
   colour between them.

   Same 3px top rule as .tm-archetype and TwoAxesPanel, so the reader who has just been told
   that purple means omissions can tell at a glance which of the cards below are still on that
   subject — which is the whole job here, since several of them read as ordinary prose. The
   accent is the header only: tinting the body would make the copy inside compete with the
   badges and rules that carry the hue meaningfully. */
.tm-context-card > .card-header {
    border-top: 3px solid var(--tm-context);
    color: var(--tm-context);
}

.tm-verdict-true { background-color: #198754; }
.tm-verdict-mostlytrue { background-color: #5cb87a; }
.tm-verdict-mixed { background-color: #ffc107; }
.tm-verdict-mostlyfalse { background-color: #fd7e14; }
.tm-verdict-false { background-color: #dc3545; }
.tm-verdict-unverifiable { background-color: #6c757d; }

/* ---- Blazor framework error UI (kept from template) ---- */
.blazor-error-boundary {
    background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
    padding: 1rem 1rem 1rem 3.7rem;
    color: white;
}

    .blazor-error-boundary::after {
        content: "An error has occurred."
    }

/* ============================================================================
   Home page — the archetype panels.

   The whole layout rests on one rule: EVERY PANEL IS THE SAME SHAPE. Heading,
   person, two axis rows, techniques, link — always in that order, always at the
   same height. The panels used to answer different questions in different orders
   (an omission rate, then a lean instead, then both plus a paragraph), which left
   the reader comparing three layouts rather than three records. With one shape
   the pattern is the only thing that varies, and it reads before any word does.

   The rule holds INSIDE a row as well as between them. The context row once
   printed a rate on one card, a lean on the next and both on the third -- three
   statistics under one heading, each with its own denominator. It now asks every
   card the same pair (how many facts were left out, and how many of those cut
   the speaker's way), so a reader learns the row once. See Home.razor's
   ContextRow: nothing in it may branch on the category again.

   A panel is rendered only for a category some speaker actually satisfies, so
   the grid holds one, two or three of them. That is why the alignment is built
   from reserved space -- .tm-archetype-headline and .tm-axis-note each reserve
   their lines whether or not that speaker needs them -- rather than from the
   three happening to carry the same amount of copy: nothing here may depend on
   all three being present, or on any of them holding a particular speaker.

   Anything added here that can appear on one card and not another has to be
   rendered-but-empty on the others (see .tm-technique-chip-none), or the rows
   stop lining up across the grid and the comparison is gone.
   ============================================================================ */

.tm-home-lede {
    max-width: 62ch;
    margin-bottom: 1rem;
}

/* ---- The two questions, named once above the grid ---- */
.tm-question-strip {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.6rem 1.15rem;
    padding: 0.7rem 0;
    margin-bottom: 1rem;
    border-top: 1px solid var(--tm-border);
    border-bottom: 1px solid var(--tm-border);
}

.tm-question-label {
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.11em;
    text-transform: uppercase;
    color: var(--tm-ink-faint);
}

.tm-question {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    font-size: 0.94rem;
    font-weight: 600;
    color: var(--tm-ink);
}

/* Numbered because the order is real: the context check only ever runs on an
   already-scored claim, so accuracy genuinely is asked first. The numeral is
   tinted to the axis it introduces and matches the marker on that row below. */
.tm-question-num {
    width: 1.2rem;
    height: 1.2rem;
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    border: 1.5px solid currentColor;
    font-size: 0.68rem;
    font-weight: 700;
}

.tm-question-truth .tm-question-num { color: #198754; }
.tm-question-context .tm-question-num { color: var(--tm-context); }

.tm-question-note {
    flex: 1;
    min-width: 15rem;
    font-size: 0.86rem;
    color: var(--tm-ink-soft);
}

/* ---- The panel ---- */
.tm-archetype .card-header {
    display: flex;
    flex-direction: column;
    gap: 0.1rem;
    border-top: 3px solid var(--tm-archetype-hue, var(--tm-border-strong));
}

/* Recognition, not verdict — the marker on the accuracy row is what carries the
   judgement. The one-sided panel takes the context hue rather than an amber,
   because its finding is entirely on that axis and an amber would imply a
   middling accuracy score it does not have. */
.tm-archetype-forthright { --tm-archetype-hue: #198754; }
.tm-archetype-selective { --tm-archetype-hue: var(--tm-context); }
.tm-archetype-unreliable { --tm-archetype-hue: #dc3545; }

/* The hue is a text colour as well as a rule, and the two solid band colours are
   pitched for white fills rather than for reading against a dark surface -- the
   green fell to 3.5:1 on it. Lightened to the same values .tm-axis-value uses in
   this theme, so a heading and the figure it introduces stay the same colour.
   Selective needs no entry: --tm-context is already re-pointed per theme. */
[data-bs-theme="dark"] .tm-archetype-forthright { --tm-archetype-hue: #4ec08a; }
[data-bs-theme="dark"] .tm-archetype-unreliable { --tm-archetype-hue: #f0707c; }

.tm-archetype-pattern {
    font-family: var(--tm-font-sans);
    font-size: 0.68rem;
    font-weight: 700;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--tm-archetype-hue, var(--tm-ink-faint));
}

.tm-archetype-headline {
    font-size: 1.02rem;
    font-weight: 600;
    line-height: 1.25;
    text-wrap: balance;

    /* Two lines, reserved whether this headline needs them or not. The three
       headlines are different lengths, so at most widths one of them fits on a
       single line and its neighbours wrap to two -- which lifted that card's
       whole body by a line and left the person, both axis rows and the figures
       beside them sitting at a different height from the cards either side. The
       tail of the card is anchored to the foot already; this is the other end.
       A floor, not a fixed height: a longer headline, a larger font size or a
       narrower column still wraps to a third line rather than clipping. Below
       the lg breakpoint the cards stack one per row, where nothing has to line
       up with anything, so the floor only has to hold across the three-column
       grid -- and there it holds for every headline in SpeakerCategoryText. */
    min-height: calc(2 * 1.25em);
}

/* The lg band is the exception, and the only one. Three columns start at 992px,
   where the header has ~180px of inner width and the two longer headlines wrap
   to a third line while the first still fits on two -- the same misalignment
   again, a breakpoint lower. Three lines is the floor there and two everywhere
   above it, rather than three throughout: at full width every headline fits on
   two, and a third reserved line would show as a band of dead space under all
   of them. Below 992px the cards stack one per row and nothing has to line up,
   so no floor applies at all. */
@media (min-width: 992px) and (max-width: 1199.98px) {
    .tm-archetype-headline { min-height: calc(3 * 1.25em); }
}

.tm-archetype-body {
    display: flex;
    flex-direction: column;
    gap: 0.85rem;
}

.tm-archetype-link {
    font-size: 0.86rem;
    font-weight: 600;
}

/* ---- Person: a row, not a centred column ----
   The 104px portrait stacked above a centred name was the largest single item
   between the reader and the finding. The face is still first in reading order,
   which is what the centred version was protecting. */
.tm-archetype-person {
    display: flex;
    align-items: center;
    gap: 0.7rem;
}

.tm-archetype-who {
    display: flex;
    flex-direction: column;
    min-width: 0;
    /* Clipped, never wrapped. The face is deliberately large enough to recognise,
       which leaves a long name more text than column -- and a name broken over two
       lines pushes the axis rows of ITS card down while its neighbours stay put,
       which is the one thing this grid cannot afford. So the line runs to the edge
       of the card and stops there; the whole name is on hover, and on the profile
       the name links to. */
    overflow: hidden;
}

.tm-archetype-name {
    font-size: 1rem;
    font-weight: 700;
    line-height: 1.25;
    white-space: nowrap;
}

.tm-archetype-role {
    font-size: 0.78rem;
    line-height: 1.3;
    color: var(--tm-ink-faint);
    white-space: nowrap;
}

/* ---- The two axis rows ---- */
.tm-axes {
    border: 1px solid var(--tm-border);
    border-radius: var(--tm-radius);
    overflow: hidden;
}

.tm-axis {
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
    padding: 0.55rem 0.7rem;
}

.tm-axis + .tm-axis { border-top: 1px solid var(--tm-border); }

/* The question and its figure share a line until they cannot, and then the figure
   takes a line of its own -- never the other way round.

   The two floors below are what make that uniform ACROSS the grid, which is the
   only thing that matters here. The figure's natural width varies with the
   answer ("Nothing" against "12 omissions"), so with a squeezable question and
   no wrapping, one card kept its question on a single line while its neighbour
   wrapped a word per line, and the whole second axis row sat at a different
   height on each. Fixing both widths makes every card wrap at the SAME viewport
   width, whatever its figures say -- no breakpoint to maintain, and nothing to
   re-tune when a speaker with a longer number turns up. */
.tm-axis-top {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex-wrap: wrap;
    row-gap: 0.15rem;
}

.tm-axis-q {
    flex: 1;
    /* How far the question may be squeezed before the figure gives up the line.
       Not `max-content`: at the narrowest three-column width even the mark and an
       unsqueezable question overflow the row, which would strand the mark alone
       on a line of its own. */
    min-width: 7rem;
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    /* Soft rather than faint. This is the question itself -- the thing the reader
       is meant to learn once and then recognise on every card -- and at 11px faint
       measured 3.7:1, under the 4.5:1 that size needs. */
    color: var(--tm-ink-soft);
}

/* Deliberately NOT .tm-score-badge: that is a filled chip on the veracity ramp,
   and using it here would give the context row a scored-looking twin. This is a
   bare figure that takes its colour from the row's own language. */
.tm-axis-value {
    font-size: 1.02rem;
    font-weight: 700;
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
    background: transparent;
    /* The other half of the pair above: one slot, the same width on every card,
       so a card answering "Nothing" and one answering "6 omissions" leave their
       questions exactly as much room. Right-aligned within it, which is where
       both figures already sat when the row had space to spare. */
    min-width: 6rem;
    margin-left: auto;
    text-align: right;
}

/* The veracity ramp is a set of background colours; on a bare figure it has to
   be read as the text colour instead. Mixed and Low are darkened from the badge
   values, which are chip fills chosen to carry dark text and are far too light
   to be text themselves. */
.tm-axis-value.veracity-high { color: #198754; }
.tm-axis-value.veracity-mixed { color: #9a6c00; }
.tm-axis-value.veracity-low { color: #c25e08; }
.tm-axis-value.veracity-verylow { color: #dc3545; }
.tm-axis-value.veracity-unknown { color: var(--tm-ink-faint); }

[data-bs-theme="dark"] .tm-axis-value.veracity-high { color: #4ec08a; }
[data-bs-theme="dark"] .tm-axis-value.veracity-mixed { color: #e0b04a; }
[data-bs-theme="dark"] .tm-axis-value.veracity-low { color: #f59a4d; }
[data-bs-theme="dark"] .tm-axis-value.veracity-verylow { color: #f0707c; }

.tm-axis-value-context { color: var(--tm-context); }

/* Examined and clean. Muted on purpose, like .tm-context-clear: it reports an
   absence of findings and must not read as a second green tick. */
.tm-axis-value-clean {
    font-size: 0.88rem;
    color: var(--tm-ink-faint);
}

.tm-axis-note {
    font-size: 0.79rem;
    line-height: 1.4;
    color: var(--tm-ink-soft);
    /* Two lines' worth, reserved whether or not the copy needs them. The evidence
       sentence is built from live counts, so its length varies per speaker: at
       one line under two cards and two under the third, the SECOND axis row slid
       18px down that card and the three stopped lining up -- which is the one
       thing this layout cannot afford, since the comparison is the whole point.
       Reserving the space costs a little height and makes the rows independent
       of how long a name or a number happens to be. */
    min-height: calc(2 * 0.79rem * 1.4);
}

.tm-axis-strict {
    margin-left: 0.3rem;
    color: var(--tm-ink-faint);
    font-variant-numeric: tabular-nums;
}

/* ---- Axis markers ----
   Accuracy is scored and ordered, so its marker is FILLED on the veracity ramp.
   Context has no score, so its marker stays OUTLINED — the same rule
   .tm-context-badge follows, and the reason is the same: a filled tick on this
   row would be a number in disguise on the app's least quantifiable axis. */
.tm-axis-mark {
    width: 1.2rem;
    height: 1.2rem;
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    font-size: 0.7rem;
    font-weight: 700;
    line-height: 1;
}

.tm-mark-true { background: #198754; color: #fff; }
.tm-mark-false { background: #dc3545; color: #fff; }
.tm-mark-clean { border: 1.5px solid var(--tm-border-strong); }
.tm-mark-found { border: 1.5px solid var(--tm-context); color: var(--tm-context); }
.tm-mark-pending { border: 1.5px solid var(--tm-border-strong); color: var(--tm-ink-faint); }

/* ---- Technique chips ----
   Same palette as the profile's technique bar and for the same reason: one hue
   family, never the green-to-red veracity ramp, because techniques are unordered
   and that ramp would invent a ranking the data does not carry. */
.tm-technique-chips {
    /* NOT anchored to the foot. An auto margin here used to pin the tail -- chips
       then link -- to the bottom of the card, because the lean bar was drawn per
       speaker rather than per panel, so any card could carry one its neighbours
       did not and the chip rows sat at different heights without it.
       That bar is gone, and with it the last thing above the tail whose height
       varied per speaker: the headline and both axis notes reserve their lines
       (.tm-archetype-headline, .tm-axis-note), so the axes block ends at the same
       y on all three cards and the chips line up simply by following it. All an
       auto margin can do now is open a visible hole between the axes and the
       chips on whichever card has the shortest tail -- .h-100 stretches the three
       to a common height, so the slack has to sit somewhere, and below the last
       line it reads as padding where in the middle it reads as something failing
       to load. */
    display: flex;
    flex-wrap: wrap;
    gap: 0.3rem;
}

.tm-technique-chip {
    font-size: 0.68rem;
    font-weight: 600;
    color: #fff;
    padding: 0.2em 0.5em;
    border-radius: var(--tm-radius-sm);
    white-space: nowrap;
}

/* Examined and found clean. Rendered rather than omitted: an explicit "nothing
   on record" says more than blank space, and a row that vanished on one card
   would pull the three out of alignment.
   Soft rather than faint, for the same reason .tm-axis-q is: this is 11px text,
   and faint measures 3.9:1 on the card in dark and 3.7:1 in light -- both under
   the 4.5:1 that size needs. Soft clears it in both (6.9 and 7.6) and is still
   visibly quieter than a filled chip, which is all the muting has to do. */
.tm-technique-chip-none {
    background: transparent;
    color: var(--tm-ink-soft);
    border: 1px dashed var(--tm-border-strong);
    font-weight: 500;
}

/* The pale tints in the technique family cannot carry white text. Listed per
   class rather than guessed at, in both themes, because the dark palette
   re-points every one of them lighter. */
.tm-technique-chip.tm-technique-selectivetimeframe,
.tm-technique-chip.tm-technique-absolutevsrate { color: #241540; }

/* Quote mining is the dark end of the family in both themes, so it keeps white
   text where its paler siblings take ink. A blanket rule here put dark text on it
   at 2.7:1.
   The "none" chip is excluded for a different reason: it has no fill at all, so
   this rule painted near-black text straight onto the dark page surface and the
   one card with a clean record said so illegibly. It keeps its faint ink from the
   rule above, which is theme-aware already. */
[data-bs-theme="dark"] .tm-technique-chip:not(.tm-technique-quotemining):not(.tm-technique-chip-none) { color: #17131f; }

/* ---- Nothing to feature yet ----
   A panel exists only where a speaker satisfies its category, so before any of
   them do there is no grid at all and this line stands in its place. One line
   rather than three outlined cards stating their thresholds: the panels are the
   first thing on the site, and cards naming what the page *cannot* show would
   crowd out the one or two it can as soon as they arrive. The bar each pattern
   sets is on /speakers in full, which this links to. */
.tm-home-pending {
    max-width: 62ch;
    margin-bottom: 0;
    color: var(--tm-ink-soft);
}

/* ---- The standing caveats, demoted from a banner ---- */
.tm-home-footnote {
    max-width: 88ch;
    margin: 0.85rem 0 0;
    padding-top: 0.7rem;
    border-top: 1px solid var(--tm-border);
    font-size: 0.8rem;
    line-height: 1.5;
    color: var(--tm-ink-faint);
}
