/* tokens.css */
/*
 * Design tokens.
 *
 * Every colour gets its LIGHT value here on bare :root, so a token always has a
 * definition even when no media query and no [data-theme] block matches. The two
 * blocks below only ever REDEFINE tokens, never introduce them.
 *
 * The dark palette is written twice on purpose: once for the system preference and once
 * for an explicit choice. CSS cannot share one declaration block between a media query
 * and a selector, and light-dark() would still need these blocks as its fallback.
 * tests/unit/contrast.test.js fails if the two copies drift apart.
 *
 * Precedence, lowest to highest:
 *   1. :root                                        light, the baseline
 *   2. prefers-color-scheme: dark, unless data-theme="light"   the system default
 *   3. [data-theme="dark"] / [data-theme="light"]   an explicit choice by the reader
 */

:root {
    color-scheme: light;

    /* brand -- unchanged from the Materialize build: red 700 and orange 400 */
    --brand: #d32f2f;
    --brand-hover: #b71c1c;
    --accent: #ffa726;
    --accent-strong: #fb8c00;
    /* white on #ffa726 is about 2:1, so accent buttons take dark ink instead */
    --on-accent: #3b2200;
    /* the accent as a mark on a card (the fired-indicator tick): #fb8c00 is 2.4:1 on white */
    --accent-ink: #a85300;

    /* The header and footer band is the darker red, not --brand: #f6cccc and white need
       it to reach 4.5:1 (on #d32f2f the muted line was 3.42:1). */
    --header-bg: var(--brand-hover);
    /* the header's top bar (brand, nav, account), a step darker than the band under it */
    --header-bar-bg: #9f1717;
    --header-text: #fff;
    --header-muted: #f6cccc;
    --footer-bg: var(--brand-hover);
    --footer-text: #fff;
    --footer-muted: #f6cccc;

    /* White washes for the controls on that band, the same in both themes. The field
       border is 3:1 against the band; the placeholder is 4.5:1 against the field. */
    --header-field-bg: rgba(255, 255, 255, .12);
    --header-field-bg-focus: rgba(255, 255, 255, .2);
    --header-field-border: rgba(255, 255, 255, .6);
    --header-placeholder: rgba(255, 255, 255, .9);
    --header-control-bg: rgba(255, 255, 255, .14);
    --header-control-bg-hover: rgba(255, 255, 255, .2);
    /* the home band's search field: solid, so typed text reads as on a page */
    --search-bg: #fff;

    /* surfaces */
    --bg: #f6f9fc;
    --surface: #fff;
    --surface-2: #f2f4f7;
    --surface-hover: #f7f9fb;
    --border: #e4e7ec;
    --border-strong: #cfd4dc;

    /* ink */
    --text: #212121;
    /* Both greys clear 4.5:1 on --bg, --surface and --surface-2 (tests/unit/contrast.test.js).
       They sit close together, so the quieter one also differs by size or case where it is used. */
    --text-muted: #666;
    --text-subtle: #6b6b6b;

    /* Links and /stats figures are ink: red is kept for signals and orange for the one
       action a screen asks for. Links in running text are underlined (base.css). */
    --link: var(--text);
    --stat: var(--text);

    /* toasts and tooltips: a dark chip in both themes */
    --toast-bg: #323232;
    --toast-text: #fff;

    /* "Sign in with heart.id" (owner_videos.css, sign-in only): heart.id's black pill and
       red mark, the same in both themes */
    --heartid-bg: #0f0f0f;
    --heartid-bg-hover: #2a2a2a;
    --heartid-text: #fff;
    --heartid-mark: #f00;

    /* The owner's line to Vantage (vantage.css, only while HKI_VANTAGE_SELFCHECK is on):
       Vantage's own teal and a tint of its primary container, so it matches the site it opens */
    --vantage: #006a66;
    --vantage-soft: #e3fbf9;

    /* the matched part of a video title the owner is shown (owner_videos.css) */
    --mark-bg: rgba(255, 167, 38, .35);

    /* the name plate over a /stats channel banner, which can be any colour */
    --plate-bg: rgba(0, 0, 0, .6);
    --on-plate: #fff;

    /* the lightness every indicator stripe is normalised to, per theme */
    --stripe-lightness: 0.55;

    /* The score ramp: how many signals a channel shows, in one family of red. Few signals
       carry no red at all, so a clean channel never looks flagged; some is a red tint, many
       a solid red that reads the same in both themes. Orange stays for the one action a
       screen asks for, so a score never looks like a button. */
    --score-few: var(--text-muted);
    --score-some-bg: #fdecea;
    --score-some-text: #b71c1c;
    --score-many-bg: #b71c1c;
    --score-many-text: #fff;

    /* The check grid, one cell per check: a minor signal is a green tint, a medium one
       amber, a heavy one red; a clear check is an empty outline and one that could not
       run is hatched in the same grey. Every mark clears 3:1 against the card (WCAG 1.4.11). */
    --cell-line: #8b919c;
    --cell-minor-bg: #e3f1ea;
    --cell-minor-line: #2f6f57;
    --cell-medium: #9a5b0b;
    --cell-heavy: #b8281e;

    /* Cards are told apart by their border; a shadow is kept for what floats over the
       page (menus, toasts, the cookie card). */
    --shadow-1: none;
    --shadow-2: 0 4px 14px rgba(16, 24, 40, .10);

    --focus: #1b6ef3;

    --radius: 14px;
    --radius-sm: 8px;

    /* Type scale. Sizes outside it are one-off headings tuned to a fold or a column
       width; moving them onto a step would move the layout. */
    --fs-3xs: .7rem;
    --fs-2xs: .75rem;
    --fs-xs: .8rem;
    --fs-sm: .85rem;
    --fs-md: .9rem;
    --fs-md-plus: .95rem;
    --fs-base: 1rem;
    --fs-lg: 1.25rem;
    /* the /stats headline figures, in the display face */
    --fs-num: clamp(2.5rem, 8vw, 3.75rem);
    --fs-section: clamp(1.1rem, 2.5vw, 1.5rem);
    --fs-page-title: clamp(1.6rem, 5vw, 2.2rem);

    /* spacing scale, for padding and gaps */
    --space-1: .25rem;
    --space-2: .5rem;
    --space-3: .75rem;
    --space-4: 1rem;
    --space-5: 1.25rem;
    --space-6: 1.5rem;

    /* a comfortable line length for reading */
    --measure: 46rem;

    /* the gap between a link and its underline */
    --underline-offset: .15em;

    /* The faces are self-hosted (@font-face at the end of this file). Each "Fallback" face is
       a local font resized to the web font's measurements, so the page barely moves when the
       web font arrives. A character none of them has, such as Korean or Hindi in a channel
       name, falls through to the system font. */
    --font: "IBM Plex Sans", "IBM Plex Sans Fallback", system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
    --font-display: "Bricolage Grotesque", "Bricolage Grotesque Fallback", system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
    --font-mono: "IBM Plex Mono", "IBM Plex Mono Fallback", ui-monospace, SFMono-Regular, Consolas, "Liberation Mono", monospace;

    /* One knob for every transition, so prefers-reduced-motion can zero it out. The site
       moves only while it works: the scan beam and the grid's pulse while a scan runs
       (score_card.css), the skeletons' pulse and the spinner (components.css), and the
       phone's score bar sliding out of the way (results_side.css). Hovers, toasts, the
       Help & feedback dialog and in-page jumps change at once. */
    --speed: 180ms;
}

@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) {
        color-scheme: dark;

        --brand: #ef5350;
        --brand-hover: #ff7a75;
        --accent: #ffb74d;
        --accent-strong: #ffa726;
        --on-accent: #241400;
        --accent-ink: #ffa726;
        --vantage: #3fdfd7;
        --vantage-soft: #00302e;

        --header-bg: #8c2020;
        --header-bar-bg: #761a1a;
        --header-text: #fff;
        --header-muted: #f0c4c4;
        --footer-bg: #8c2020;
        --footer-text: #fff;
        --footer-muted: #f0c4c4;

        --bg: #14161a;
        --surface: #1c1f24;
        --surface-2: #23272e;
        --search-bg: #23272e;
        --surface-hover: #262a31;
        --border: #333941;
        --border-strong: #454c56;

        --text: #e8eaed;
        --text-muted: #a8b0ba;
        --text-subtle: #8b939d;

        --mark-bg: rgba(255, 183, 77, .3);

        --score-some-bg: #3d1a1a;
        --score-some-text: #ff7a75;

        --cell-line: #6b7380;
        --cell-minor-bg: #16302a;
        --cell-minor-line: #6fc6a0;
        --cell-medium: #f2b25c;
        --cell-heavy: #ff7f72;

        --stripe-lightness: 0.74;

        --shadow-1: none;
        --shadow-2: 0 6px 18px rgba(0, 0, 0, .45);

        --focus: #7aa7ff;
    }
}

:root[data-theme="dark"] {
    color-scheme: dark;

    --brand: #ef5350;
    --brand-hover: #ff7a75;
    --accent: #ffb74d;
    --accent-strong: #ffa726;
    --on-accent: #241400;
    --accent-ink: #ffa726;
    --vantage: #3fdfd7;
    --vantage-soft: #00302e;

    --header-bg: #8c2020;
    --header-bar-bg: #761a1a;
    --header-text: #fff;
    --header-muted: #f0c4c4;
    --footer-bg: #8c2020;
    --footer-text: #fff;
    --footer-muted: #f0c4c4;

    --bg: #14161a;
    --surface: #1c1f24;
    --surface-2: #23272e;
    --search-bg: #23272e;
    --surface-hover: #262a31;
    --border: #333941;
    --border-strong: #454c56;

    --text: #e8eaed;
    --text-muted: #a8b0ba;
    --text-subtle: #8b939d;

    --mark-bg: rgba(255, 183, 77, .3);

    --score-some-bg: #3d1a1a;
    --score-some-text: #ff7a75;

    --cell-line: #6b7380;
    --cell-minor-bg: #16302a;
    --cell-minor-line: #6fc6a0;
    --cell-medium: #f2b25c;
    --cell-heavy: #ff7f72;

    --stripe-lightness: 0.74;

    --shadow-1: none;
    --shadow-2: 0 6px 18px rgba(0, 0, 0, .45);

    --focus: #7aa7ff;
}

@media (prefers-reduced-motion: reduce) {
    :root {
        --speed: 1ms;
    }
}

/*
 * The three self-hosted faces (views/fonts, SIL Open Font License, licence texts beside them),
 * from the @fontsource packages: Latin and Vietnamese only, and only the weights the pages use.
 * A browser fetches a file only when the page shows a character in its range at that weight,
 * so a page that uses no Vietnamese downloads none of it. The two faces seen above the fold,
 * Bricolage Grotesque 700 and IBM Plex Sans 400, are preloaded from each page's <head>.
 * They live here rather than in a stylesheet of their own because every page already loads
 * this file first, and another render-blocking stylesheet would delay the first paint.
 */

@font-face {
    font-family: "Bricolage Grotesque";
    font-style: normal;
    font-weight: 500;
    font-display: swap;
    src: url("/fonts/bricolage-grotesque-latin-500-normal.woff2") format("woff2");
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
    font-family: "Bricolage Grotesque";
    font-style: normal;
    font-weight: 500;
    font-display: swap;
    src: url("/fonts/bricolage-grotesque-vietnamese-500-normal.woff2") format("woff2");
    unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}

@font-face {
    font-family: "Bricolage Grotesque";
    font-style: normal;
    font-weight: 700;
    font-display: swap;
    src: url("/fonts/bricolage-grotesque-latin-700-normal.woff2") format("woff2");
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
    font-family: "Bricolage Grotesque";
    font-style: normal;
    font-weight: 700;
    font-display: swap;
    src: url("/fonts/bricolage-grotesque-vietnamese-700-normal.woff2") format("woff2");
    unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}

@font-face {
    font-family: "IBM Plex Sans";
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url("/fonts/ibm-plex-sans-latin-400-normal.woff2") format("woff2");
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
    font-family: "IBM Plex Sans";
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url("/fonts/ibm-plex-sans-vietnamese-400-normal.woff2") format("woff2");
    unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}

@font-face {
    font-family: "IBM Plex Sans";
    font-style: normal;
    font-weight: 500;
    font-display: swap;
    src: url("/fonts/ibm-plex-sans-latin-500-normal.woff2") format("woff2");
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
    font-family: "IBM Plex Sans";
    font-style: normal;
    font-weight: 500;
    font-display: swap;
    src: url("/fonts/ibm-plex-sans-vietnamese-500-normal.woff2") format("woff2");
    unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}

@font-face {
    font-family: "IBM Plex Sans";
    font-style: normal;
    font-weight: 600;
    font-display: swap;
    src: url("/fonts/ibm-plex-sans-latin-600-normal.woff2") format("woff2");
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
    font-family: "IBM Plex Sans";
    font-style: normal;
    font-weight: 600;
    font-display: swap;
    src: url("/fonts/ibm-plex-sans-vietnamese-600-normal.woff2") format("woff2");
    unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}

@font-face {
    font-family: "IBM Plex Mono";
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url("/fonts/ibm-plex-mono-latin-400-normal.woff2") format("woff2");
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
    font-family: "IBM Plex Mono";
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url("/fonts/ibm-plex-mono-vietnamese-400-normal.woff2") format("woff2");
    unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}

@font-face {
    font-family: "IBM Plex Mono";
    font-style: normal;
    font-weight: 500;
    font-display: swap;
    src: url("/fonts/ibm-plex-mono-latin-500-normal.woff2") format("woff2");
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
    font-family: "IBM Plex Mono";
    font-style: normal;
    font-weight: 500;
    font-display: swap;
    src: url("/fonts/ibm-plex-mono-vietnamese-500-normal.woff2") format("woff2");
    unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}

/*
 * Fallback faces: a local font stretched to the web font's average width and line box, measured
 * in Chrome against Arial and Courier New on 28 Oct 2026, so text keeps its size and line breaks
 * while the web font loads (font-display: swap) and the swap causes almost no layout shift.
 * A browser that ignores size-adjust or the overrides still gets the plain local font.
 */

@font-face {
    font-family: "Bricolage Grotesque Fallback";
    src: local("Arial");
    size-adjust: 101.66%;
    ascent-override: 91.48%;
    descent-override: 26.56%;
    line-gap-override: 0%;
}

@font-face {
    font-family: "IBM Plex Sans Fallback";
    src: local("Arial");
    size-adjust: 100.5%;
    ascent-override: 102.49%;
    descent-override: 27.86%;
    line-gap-override: 0%;
}

@font-face {
    font-family: "IBM Plex Mono Fallback";
    src: local("Courier New");
    size-adjust: 99.98%;
    ascent-override: 103.02%;
    descent-override: 28%;
    line-gap-override: 0%;
}

/* base.css */
/*
 * Shared chrome for both pages: reset, layout container, header (the footer is footer.css),
 * form controls, panels, tables and the vanilla replacements for the three
 * Materialize widgets we actually used (toast, tooltip, preloader).
 */

*,
*::before,
*::after {
    box-sizing: border-box;
}

html {
    -webkit-text-size-adjust: 100%;
    /* focused controls scroll clear of the cookie bar (consent_banner.js sets --consent-h) */
    scroll-padding-bottom: var(--consent-h, 0);
}

body {
    margin: 0;
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    font-family: var(--font);
    font-size: var(--fs-base);
    line-height: 1.5;
    color: var(--text);
    background: var(--bg);
    /* room for the cookie bar, so the footer can scroll above it */
    padding-bottom: var(--consent-h, 0);
}

img {
    max-width: 100%;
}

a {
    color: var(--link);
    text-decoration: none;
}

/* Links are ink like the text around them (tokens.css --link), so hovering one underlines
   it rather than changing its colour. Only a plain link: a link with a class (a button, the
   brand, a nav row, a card) styles its own hover and must not pick up an underline. */
a:not([class]):hover {
    color: var(--link);
    text-decoration: underline;
    text-underline-offset: var(--underline-offset);
}

/* A link inside running text is underlined wherever it is styled (main.css, pages.css,
   scs.css, self_check.css, footer.css), so it is told apart from the words around
   it by more than colour (WCAG 1.4.1). Navigation rows, buttons, table cells and cards
   keep this plain default. */

h1, h2, h3, h4, h5 {
    margin: 0;
    font-family: var(--font-display);
    font-weight: 500;
    line-height: 1.25;
}

/* Page and section titles take the display face at its bold weight; smaller headings
   keep 500. */
h1, h2 {
    font-weight: 700;
}

code, kbd, samp {
    font-family: var(--font-mono);
}

/* Materialize utility class, kept by name because page_status() toggles it. */
.hide {
    display: none !important;
}

.visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

.container {
    width: min(100% - 2rem, 1200px);
    margin-inline: auto;
}

:focus-visible {
    outline: 2px solid var(--focus);
    outline-offset: 2px;
    border-radius: 3px;
}

/* header */

/* The red band every page's header sits on; its layout is header.css
   (partials/site_header.ejs). */

.site-header {
    /* the ring has to survive a red background */
    --focus: var(--header-text);
    background: var(--header-bg);
    color: var(--header-text);
}

/* buttons */

.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: .4rem;
    font: inherit;
    font-weight: 500;
    line-height: 1;
    padding: .65rem 1.1rem;
    border: 0;
    border-radius: var(--radius-sm);
    background: var(--accent);
    color: var(--on-accent);
    cursor: pointer;
    text-transform: uppercase;
    font-size: var(--fs-xs);
    letter-spacing: .04em;
}

.btn:hover {
    background: var(--accent-strong);
    color: var(--on-accent);
}

.btn svg {
    width: 1.15rem;
    height: 1.15rem;
    fill: currentColor;
}

.btn-icon {
    padding: .65rem .8rem;
}

/* theme toggle: one icon button cycling System, Light, Dark. Its state is the icon,
   and in words in its aria-label and title (ui.js). */

.theme-toggle {
    display: inline-grid;
    place-items: center;
    flex: none;
    width: 2.125rem;
    height: 2.125rem;
    padding: 0;
    font: inherit;
    color: inherit;
    background: var(--header-control-bg);
    border: 0;
    border-radius: var(--radius-sm);
    cursor: pointer;
}

.theme-toggle:hover {
    background: var(--header-control-bg-hover);
}

/* the header sets --focus to its text colour, so the ring shows on the red band */
.theme-toggle:focus-visible {
    outline: 2px solid var(--focus);
    outline-offset: 2px;
}

/* 34px square; on a touch screen its hit area reaches 5px further each way, to 44px,
   so nothing moves (WCAG 2.5.8) */
@media (pointer: coarse) {
    .theme-toggle {
        position: relative;
    }

    .theme-toggle::before {
        content: "";
        position: absolute;
        inset: -5px;
    }
}

.theme-toggle svg {
    width: 1.125rem;
    height: 1.125rem;
    fill: currentColor;
}

/* The served button holds all three states; the head script sets data-theme before
   first paint, so the right one shows at once and the button never changes size. */
.theme-toggle-mode {
    display: none;
    align-items: center;
}

:root:not([data-theme]) [data-theme-mode="system"],
:root[data-theme="light"] [data-theme-mode="light"],
:root[data-theme="dark"] [data-theme-mode="dark"] {
    display: inline-flex;
}

/* panels and cards */

.panel {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow-1);
    overflow: hidden;
}

.section-head {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    margin-bottom: 1rem;
}

/* Sentence case, as the heading is written in the markup */
.section-head h2 {
    font-size: var(--fs-section);
}

/* tables. A container query drives the stack, so the breakpoint follows the
   table's own box rather than the viewport. */

.table-wrap {
    container-type: inline-size;
    container-name: tablewrap;
}

table {
    width: 100%;
    border-collapse: collapse;
    font-size: var(--fs-md);
}

thead th {
    text-align: left;
    padding: var(--space-3) var(--space-4);
    font-size: var(--fs-2xs);
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: .05em;
    color: var(--text-subtle);
    border-bottom: 1px solid var(--border);
}

tbody td {
    padding: var(--space-3) var(--space-4);
    border-bottom: 1px solid var(--border);
    vertical-align: middle;
}

tbody tr:last-child td {
    border-bottom: 0;
}

tbody tr:hover {
    background: var(--surface-hover);
}

.cell-center {
    text-align: center;
}

.thumb {
    width: 48px;
    height: 48px;
    border-radius: 50%;
    object-fit: cover;
    display: block;
    /* stored thumbnail URLs go stale. A broken one is an empty circle (ui.js swaps in
       a blank image), never its alt text -- the title, shown in the next cell anyway --
       spilling across the row */
    overflow: hidden;
    color: transparent;
    font-size: 0;
    background: var(--surface-2);
}

@container tablewrap (max-width: 46rem) {
    thead {
        display: none;
    }

    tbody tr {
        display: grid;
        grid-template-columns: minmax(0, 1fr);
        gap: var(--space-1);
        padding: .85rem var(--space-4);
        border-bottom: 1px solid var(--border);
    }

    tbody tr:last-child {
        border-bottom: 0;
    }

    tbody td {
        display: flex;
        align-items: center;
        justify-content: space-between;
        gap: var(--space-4);
        padding: .15rem 0;
        border: 0;
    }

    /* 12px, the smallest text on the site; --fs-3xs (11.2px) is for the header's key hints */
    tbody td[data-label]::before {
        content: attr(data-label);
        font-size: var(--fs-2xs);
        text-transform: uppercase;
        letter-spacing: .05em;
        color: var(--text-subtle);
    }
}

/* "Score by Spam Channel Indicator. Not from YouTube." beside every score (the score card,
   the phone's score bar, the latest lists, /stats). Small and quiet: a fact, not a warning. */
.score-source {
    margin: 0;
    font-size: var(--fs-xs);
    line-height: 1.4;
    color: var(--text-subtle);
}

.list-source {
    margin-top: var(--space-2);
}

/* Primary buttons reach 44px on a touch screen, as .consent-btn does; desktop keeps its
   sizes so the score stays above the fold. The phone's score bar sets its own 44px
   minimum in results_side.css. */
@media (pointer: coarse) {
    .btn {
        min-height: 2.75rem;
    }
}

/* "Skip to content": the first Tab stop on every page, off screen until it has focus */
.skip-link {
    position: absolute;
    top: var(--space-2);
    left: var(--space-2);
    z-index: 2000;
    padding: .6rem 1rem;
    border-radius: var(--radius-sm);
    background: var(--surface);
    color: var(--text);
    font-weight: 600;
    box-shadow: var(--shadow-2);
    transform: translateY(-200%);
}

.skip-link:focus {
    transform: none;
}

/* The notice of a scheduled change to the privacy policy or terms (lib/policy_notice): a band above
   the header, sent with the page, so nothing moves when it shows. Theme tokens only, so both themes. */
.policy-notice {
    background: var(--surface);
    border-bottom: 1px solid var(--border);
    box-shadow: inset 0 3px 0 var(--accent-strong);
}

.policy-notice[hidden] {
    display: none;
}

.policy-notice-in {
    width: min(100% - 2rem, 1200px);
    margin-inline: auto;
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding-block: var(--space-2);
}

.policy-notice-body {
    flex: 1;
    min-width: 0;
}

.policy-notice-text {
    margin: var(--space-1) 0;
    font-size: var(--fs-md);
    color: var(--text);
}

.policy-notice-text a {
    text-decoration: underline;
    text-underline-offset: var(--underline-offset);
}

/* 44px on every pointer: the band is short, and the button is its only control besides the links */
.policy-notice-close {
    flex: none;
    display: inline-grid;
    place-items: center;
    width: 2.75rem;
    height: 2.75rem;
    padding: 0;
    border: 0;
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--text-muted);
    cursor: pointer;
}

.policy-notice-close[hidden] {
    display: none;
}

.policy-notice-close:hover {
    background: var(--surface-2);
    color: var(--text);
}

.policy-notice-close svg {
    width: 1.25rem;
    height: 1.25rem;
    fill: currentColor;
}

/* footer.css */
/*
 * The footer of every page (partials/page_footer.ejs; index.html and stats.ejs hold copies
 * of it): two calm columns on the red band, the same band as the header's. The first says
 * whose site this is -- Freedom!, the "developed with YouTube" mark and the copyright line;
 * the second holds the links, two short columns of their own. On a phone the second
 * column follows the first. Loaded right after base.css.
 */

.page-footer {
    --focus: var(--footer-text);
    margin-top: auto;
    background: var(--footer-bg);
    color: var(--footer-text);
}

.footer-cols {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: var(--space-6) clamp(2rem, 6vw, 5rem);
    align-items: start;
    padding-block: clamp(2rem, 4vw, 2.75rem);
}

.footer-freedom {
    display: block;
    height: 33px;
    width: auto;
}

.footer-about p {
    margin: .5rem 0 0;
    font-size: var(--fs-md);
    color: var(--footer-muted);
}

/* The "developed with YouTube" mark (partials/youtube_mark.ejs). Shown as the official file,
   100px tall (the brand site's digital minimum); its transparent margin is the clear space,
   so nothing is laid over it and the lines above and below keep clear of the box. It only
   shrinks on a screen narrower than 280px. The file's left margin (104 of its 700px) is
   shifted into the empty page gutter, so the words line up with the text above and below
   while the clear space stays free. */
.footer-about .yt-mark {
    display: block;
    width: max-content;
    max-width: 100%;
    margin-block: var(--space-2) 0;
    line-height: 0;
}

.footer-about .yt-mark img {
    display: block;
    width: 280px;
    max-width: 100%;
    max-height: none;
    height: auto;
    translate: calc(-104 / 700 * 100%) 0;
}

.footer-about .footer-copyright {
    margin-top: var(--space-2);
    font-size: var(--fs-sm);
}

/* YouTube API Services policy III.A.2: people agree to the privacy policy before they use the
   checker. Every page but the home page says so here, under the copyright line, in its muted
   white (--footer-muted on --footer-bg, 4.5:1 or more) with white links; the home page says it
   under its search (header.css). index.html is both the home page and every result, so its
   copy hides while its header is in home mode, which the server sets for a result before first
   paint and site_header.js follow() keeps as the scanner moves. Without :has() it shows on the
   home page too, which repeats the line but loses nothing. */
.footer-about .footer-agreement {
    margin-top: var(--space-2);
    font-size: var(--fs-sm);
}

.footer-agreement a {
    text-decoration: underline;
    text-underline-offset: var(--underline-offset);
}

@supports selector(:has(*)) {
    body:has(.site-header[data-header-mode="home"]) .footer-agreement {
        display: none;
    }
}

/* The links, white and underlined on the band (--footer-text on --footer-bg, 4.5:1 or more
   in both themes): a list in two columns, read down the first and then the second, in the
   order the page writes them. Help & feedback (lib/feedback) and Cookie settings are buttons
   dressed as the links beside them. */
.footer-links {
    display: block;
    columns: 2;
    column-gap: clamp(1.5rem, 4vw, 3rem);
    font-size: var(--fs-md);
}

.footer-links > * {
    display: block;
    width: fit-content;
    margin-bottom: var(--space-3);
    break-inside: avoid;
}

.page-footer a {
    color: var(--footer-text);
}

.footer-links a {
    text-decoration: underline;
    text-underline-offset: var(--underline-offset);
}

.footer-links a:hover {
    text-decoration-thickness: 2px;
}

/* "Cookie settings": a button, since it opens the banner, dressed as its neighbouring links */
.footer-link-button {
    font: inherit;
    text-align: left;
    padding: 0;
    border: 0;
    background: none;
    color: var(--footer-text);
    cursor: pointer;
    text-decoration: underline;
    text-underline-offset: var(--underline-offset);
}

.footer-link-button:hover {
    text-decoration-thickness: 2px;
}

/* a phone: the links under the first column */
@media (max-width: 40rem) {
    .footer-cols {
        grid-template-columns: minmax(0, 1fr);
    }
}

/* header.css */
/*
 * The site header (views/partials/site_header.ejs, the redesign accepted on 30 Sep 2026).
 *
 * A 56px bar -- brand, nav, theme button, account -- and, on the home page, a band
 * under it with the task heading and the search. Below 72rem the nav and the theme
 * button move into a sheet the menu button opens (views/js/site_header.js), so the
 * bar keeps to one line: brand, account, menu. Below 60rem (a phone) the bar's
 * pieces shrink further.
 *
 * No track of the bar is narrower than what it holds, so its pieces never sit on
 * top of each other: the nav's track is at least as wide as the nav, the account's
 * as wide as the account (which signin.css caps at 17rem, whatever its label says),
 * and in compact mode the search is the piece that gives up room. 72rem leaves the
 * search at least 10rem beside the widest account and the nav with "Help & feedback".
 *
 * In compact mode (result pages and the pages beside the checker) the band folds
 * into the bar: the search sits between the brand and the nav, and the III.A.2
 * agreement line moves to the footer (footer.css). The markup is the same in
 * both modes (partials/site_header.ejs), so the scanner switches without a reload.
 * Compact mode's rules are header_compact.css, loaded right after this file.
 *
 * Every page has it now; the rules still name [data-header-mode], so a page that
 * brings its own header markup keeps base.css's plain band.
 */

.site-header[data-header-mode] {
    --bar-height: 3.5rem;
    position: relative;
}

/* the bar's colour across the full width, behind the first row */
.site-header[data-header-mode]::before {
    content: "";
    position: absolute;
    inset: 0 0 auto;
    height: var(--bar-height);
    background: var(--header-bar-bg);
}

.site-header-in {
    position: relative;
    width: min(100% - 2rem, 1200px);
    margin-inline: auto;
    display: grid;
    grid-template-columns: max-content minmax(max-content, 1fr) max-content auto;
    grid-template-rows: var(--bar-height);
    align-items: center;
    column-gap: var(--space-2);
}


/* brand */

.site-brand {
    grid-column: 1;
    grid-row: 1;
    display: flex;
    align-items: center;
    gap: .625rem;
    min-width: 0;
    margin-right: var(--space-4);
    color: inherit;
    /* the display face: Plex Sans ships no 700, Bricolage does */
    font-family: var(--font-display);
    font-weight: 700;
    font-size: var(--fs-md-plus);
    line-height: 1.2;
    white-space: nowrap;
}

.site-brand:hover {
    color: inherit;
}

.site-brand picture {
    flex: none;
}

.site-brand .sci-logo {
    display: block;
    width: 2rem;
    /* the width and height attributes only reserve the space; keep it square */
    height: auto;
}

.site-brand-text {
    display: grid;
}

.site-brand-by {
    font-weight: 500;
    font-size: var(--fs-3xs);
    letter-spacing: .02em;
    color: var(--header-muted);
}


/* nav and theme button: a row in the bar, a sheet on a phone */

.site-menu {
    grid-column: 2;
    grid-row: 1;
    display: flex;
    align-items: center;
    gap: var(--space-2);
    min-width: 0;
}

.site-menu .theme-toggle {
    margin-left: auto;
}

.site-nav {
    display: flex;
    gap: 2px;
}

.site-nav a {
    padding: .45rem .7rem;
    border-radius: var(--radius-sm);
    color: var(--header-muted);
    font-size: var(--fs-sm);
    white-space: nowrap;
}

.site-nav a:hover,
.site-nav a[aria-current] {
    color: var(--header-text);
    background: var(--header-control-bg);
}

.site-nav a[aria-current] {
    font-weight: 600;
}

.site-account {
    grid-column: 3;
    grid-row: 1;
    display: flex;
    align-items: center;
    min-width: 0;
}

.site-account:empty {
    display: none;
}

.site-menu-button {
    grid-column: 4;
    grid-row: 1;
    display: none;
    place-items: center;
    width: 2.125rem;
    height: 2.125rem;
    padding: 0;
    color: inherit;
    background: var(--header-control-bg);
    border: 0;
    border-radius: var(--radius-sm);
    cursor: pointer;
}

.site-menu-button:hover,
.site-menu-button[aria-expanded="true"] {
    background: var(--header-control-bg-hover);
}

.site-menu-button:focus-visible {
    outline: 2px solid var(--focus);
    outline-offset: 2px;
}

.site-menu-button svg {
    width: 1.25rem;
    height: 1.25rem;
    fill: currentColor;
}

/* 34px square; on a touch screen its hit area reaches 44px (WCAG 2.5.8) */
@media (pointer: coarse) {
    .site-menu-button {
        position: relative;
    }

    .site-menu-button::before {
        content: "";
        position: absolute;
        inset: -5px;
    }
}


/* The home band (the redesign in the original colours, 28 Oct 2026): the task as a large
   headline and a line under it, one white search field whose Check button is the page's
   one orange thing, the example chips that fill it, and the agreement. Centred on a
   wide screen, on a faint grid of dots. */

.site-header[data-header-mode="home"] {
    background-image: radial-gradient(var(--header-control-bg) 1.2px, transparent 1.3px);
    background-size: 22px 22px;
}

.site-band {
    grid-column: 1 / -1;
    grid-row: 2;
    padding-block: 3.5rem 2.75rem;
}

.site-band-in {
    display: grid;
    gap: var(--space-4);
    max-width: 45rem;
    margin-inline: auto;
}

/* home mode only: the compact bar keeps the band's pieces left-aligned and in line */
.site-header[data-header-mode="home"] .site-band-in {
    justify-items: center;
    text-align: center;
}

.site-header[data-header-mode="home"] .site-search {
    margin-top: var(--space-2);
}

.site-band h1 {
    max-width: 22ch;
    font-family: var(--font-display);
    font-size: clamp(2rem, 1.1rem + 3.4vw, 3.375rem);
    font-weight: 700;
    line-height: 1.04;
    letter-spacing: -.025em;
    text-wrap: balance;
}

.site-band-sub {
    max-width: 35rem;
    margin: 0;
    font-size: 1.125rem;
    line-height: 1.5;
    color: var(--header-muted);
}

.site-search {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2);
    justify-self: stretch;
    padding: .5rem .5rem .5rem .6rem;
    text-align: start;
    background: var(--search-bg);
    border-radius: var(--radius);
    box-shadow: var(--shadow-2);
}

/* the field draws no ring of its own; the whole search does, white on the red band */
.site-search:focus-within {
    outline: 2px solid var(--focus);
    outline-offset: 2px;
}

.site-search-icon {
    flex: none;
    width: 1.15rem;
    height: 1.15rem;
    margin-left: .45rem;
    fill: var(--text-muted);
}

.site-search input {
    flex: 1 1 0;
    min-width: 0;
    padding: .55rem .25rem;
    font: inherit;
    font-size: var(--fs-base);
    color: var(--text);
    background: transparent;
    border: 0;
    outline: none;
}

.site-search input::placeholder {
    color: var(--text-muted);
    opacity: 1;
}

.site-search-go {
    flex: none;
    padding: .8rem 1.25rem;
    font: inherit;
    font-size: var(--fs-md-plus);
    /* Plex Sans ships 600 at most; a 700 would be synthesised */
    font-weight: 600;
    line-height: 1;
    color: var(--on-accent);
    background: var(--accent);
    border: 0;
    border-radius: var(--radius-sm);
    cursor: pointer;
}

.site-search-go:hover {
    background: var(--accent-strong);
}

.site-search-go:focus-visible {
    outline: 2px solid var(--text);
    outline-offset: 2px;
}

/* the "/" hint and the button's second word are for the compact bar and the band */
.site-search-key {
    display: none;
}

/* "Try" and the example chips: buttons that fill the field (site_header.js) */
.site-search-formats {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);
    margin: 0;
    font-size: var(--fs-sm);
    color: var(--header-muted);
}

.site-search-chip {
    padding: .45rem .7rem;
    font-family: var(--font-mono);
    font-size: var(--fs-xs);
    line-height: 1;
    color: var(--header-text);
    background: var(--header-control-bg);
    border: 1px solid var(--header-field-border);
    border-radius: 999px;
    cursor: pointer;
}

.site-search-chip:hover {
    background: var(--header-control-bg-hover);
}

.site-search-chip:focus-visible {
    outline: 2px solid var(--header-text);
    outline-offset: 2px;
}

/* YouTube API Services policy III.A.2: agreement comes before use, directly under the home search */
.site-header .search-agreement {
    margin: 0;
    font-size: var(--fs-xs);
    color: var(--header-muted);
}

/* "Paste a channel's @handle, URL or ID" under the field after an empty submit (search_hint.js) */
.site-header .search-error {
    margin: 0;
    font-size: var(--fs-sm);
    font-weight: 600;
    color: var(--header-text);
}

.site-header .search-error[hidden] {
    display: none;
}

.site-header .search-agreement a,
.site-header .noscript-note a {
    color: inherit;
    text-decoration: underline;
    text-underline-offset: var(--underline-offset);
}

.site-header .noscript-note {
    margin: 0;
    font-size: var(--fs-sm);
}


/* tablet and phone: brand, account, menu; the nav and theme button in a sheet */

@media (width < 72rem) {
    .site-header-in {
        grid-template-columns: minmax(0, 1fr) auto auto;
    }

    .site-account {
        grid-column: 2;
    }

    .site-menu-button {
        grid-column: 3;
        display: inline-grid;
    }

    /* Placed across the bar's row, from its top edge: just under the bar. The -1rem
       reach the screen's edges past the container's gutter. */
    .site-menu {
        position: absolute;
        z-index: 20;
        grid-column: 1 / -1;
        top: var(--bar-height);
        left: -1rem;
        right: -1rem;
        display: none;
        flex-direction: column;
        align-items: stretch;
        gap: var(--space-1);
        padding: var(--space-2) 1rem var(--space-4);
        background: var(--header-bar-bg);
        box-shadow: var(--shadow-2);
    }

    .site-menu[data-open] {
        display: flex;
    }

    .site-nav {
        flex-direction: column;
    }

    .site-nav a {
        padding: .7rem .75rem;
        font-size: var(--fs-base);
    }

    /* in the sheet the theme button says its state in words as well */
    .site-menu .theme-toggle {
        display: flex;
        justify-content: flex-start;
        width: auto;
        height: auto;
        margin-left: 0;
        padding: .7rem .75rem;
        background: transparent;
    }

    .site-menu .theme-toggle:hover {
        background: var(--header-control-bg);
    }

    .site-menu .theme-toggle-mode {
        gap: .6rem;
    }

    .site-menu .theme-toggle .visually-hidden {
        position: static;
        width: auto;
        height: auto;
        margin: 0;
        overflow: visible;
        clip-path: none;
    }
}


/* phone: a smaller brand without "by Freedom!", a tighter band */

@media (width < 60rem) {
    .site-brand {
        margin-right: 0;
        white-space: normal;
        font-size: var(--fs-md);
    }

    .site-brand-by {
        display: none;
    }

    .site-band {
        padding-block: 2rem 1.6rem;
    }

    /* the headline, the field and the chips, left-aligned; the line under the headline
       gives way to the field */
    .site-header[data-header-mode="home"] .site-band-in {
        justify-items: stretch;
        gap: var(--space-3);
        text-align: start;
    }

    .site-band-sub {
        display: none;
    }

    .site-search-formats {
        justify-content: flex-start;
    }

    /* the button takes its own row, full width: a bigger target than the field's end */
    .site-search-go {
        flex: 1 0 100%;
        padding-block: .8rem;
    }
}

/* header_compact.css */
/*
 * The site header in compact mode (header.css has the bar and the home band; this loads
 * right after it): result pages and the pages beside the checker. The band folds into
 * the bar, with the search between the brand and the nav.
 */

.site-header[data-header-mode="compact"] {
    background: var(--header-bar-bg);
}

/* The search takes what the brand, the nav and the account leave, up to 26rem;
   the nav's track never gets narrower than the nav, so nothing overlaps it. */
.site-header[data-header-mode="compact"] .site-header-in {
    grid-template-columns: max-content minmax(10rem, 26rem) minmax(max-content, 1fr) max-content auto;
}

/* the band's pieces become the bar's */
.site-header[data-header-mode="compact"] .site-band,
.site-header[data-header-mode="compact"] .site-band-in {
    display: contents;
}

.site-header[data-header-mode="compact"] .site-brand-by,
.site-header[data-header-mode="compact"] .site-band-sub,
.site-header[data-header-mode="compact"] .site-search-formats,
.site-header[data-header-mode="compact"] .noscript-note {
    display: none;
}

/* a result page keeps the scanner's heading for screen readers */
.site-header[data-header-mode="compact"] .site-band h1 {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

.site-header[data-header-mode="compact"] .site-menu {
    grid-column: 3;
}

.site-header[data-header-mode="compact"] .site-account {
    grid-column: 4;
}

.site-header[data-header-mode="compact"] .site-menu-button {
    grid-column: 5;
}

.site-header[data-header-mode="compact"] .site-search {
    grid-column: 2;
    grid-row: 1;
    flex-wrap: nowrap;
    gap: var(--space-1);
    min-width: 0;
    height: 2.25rem;
    padding: 0 .2rem 0 .6rem;
    color: var(--header-text);
    background: var(--header-field-bg);
    border: 1px solid var(--header-field-border);
    border-radius: var(--radius-sm);
    box-shadow: none;
}

.site-header[data-header-mode="compact"] .site-search:focus-within {
    background: var(--header-field-bg-focus);
    border-color: var(--header-text);
}

.site-header[data-header-mode="compact"] .site-search-icon {
    width: 1rem;
    height: 1rem;
    margin-left: 0;
    fill: currentColor;
}

.site-header[data-header-mode="compact"] .site-search input {
    padding: .3rem .25rem;
    /* 16px, or a phone's browser zooms the page in on focus */
    font-size: var(--fs-base);
    color: var(--header-text);
}

.site-header[data-header-mode="compact"] .site-search input::placeholder {
    color: var(--header-placeholder);
}

.site-header[data-header-mode="compact"] .site-search-key {
    display: inline-block;
    flex: none;
    padding: 0 .35rem;
    border: 1px solid var(--header-field-border);
    border-radius: 4px;
    font-family: var(--font-mono);
    font-size: var(--fs-3xs);
    line-height: 1.5;
    color: var(--header-placeholder);
}

/* while typing the hint has done its job */
.site-header[data-header-mode="compact"] .site-search:focus-within .site-search-key {
    display: none;
}

.site-header[data-header-mode="compact"] .site-search-go {
    padding: .4rem .65rem;
    font-size: var(--fs-xs);
}

.site-header[data-header-mode="compact"] .site-search-go-more {
    display: none;
}

.site-header[data-header-mode="compact"] .site-search-go:focus-visible {
    outline-color: var(--header-text);
}

/* The agreement line is the home band's. Off the home page it is in the footer (footer.css),
   so the bar keeps to one row; the compact partial leaves it out, and this hides the copy
   index.html carries when a result is sent compact or the scanner moves to one. */
.site-header[data-header-mode="compact"] .search-agreement {
    display: none;
}

/* In the bar, the empty-search hint drops down under the field, over the top of the page,
   until the reader types, which hides it (search_hint.js). */
.site-header[data-header-mode="compact"] .search-error {
    position: absolute;
    z-index: 25;
    grid-column: 2;
    grid-row: 1;
    top: calc(var(--bar-height) - .25rem);
    left: 0;
    width: max-content;
    max-width: min(34rem, 100vw - 2rem);
    padding: var(--space-2) var(--space-3);
    border-radius: 0 0 var(--radius-sm) var(--radius-sm);
    background: var(--header-bar-bg);
    box-shadow: var(--shadow-2);
}

/* Just above the sheet's breakpoint the bar is at its fullest: four nav links, "Help &
   feedback" and the sign-in button leave the search under 19rem, which clips its
   placeholder. Drop the "/" hint and tighten the links there, which gives the search
   back about 2.5rem. */
@media (72rem <= width < 76rem) {
    .site-header[data-header-mode="compact"] .site-search-key {
        display: none;
    }

    .site-header[data-header-mode="compact"] .site-nav a,
    .site-header[data-header-mode="compact"] .site-help {
        padding-inline: .5rem;
    }

    .site-header[data-header-mode="compact"] .site-brand {
        margin-right: var(--space-2);
    }
}

/* tablet and phone: brand, search, account, menu; the nav in the sheet */
@media (width < 72rem) {
    .site-header[data-header-mode="compact"] .site-header-in {
        grid-template-columns: auto minmax(0, 1fr) auto auto;
    }

    .site-header[data-header-mode="compact"] .site-account {
        grid-column: 3;
    }

    .site-header[data-header-mode="compact"] .site-menu-button {
        grid-column: 4;
    }

    .site-header[data-header-mode="compact"] .site-menu {
        grid-column: 1 / -1;
    }
}

@media (width < 60rem) {
    /* the logo alone; the name stays the link's text for screen readers */
    .site-header[data-header-mode="compact"] .site-brand-text {
        position: absolute;
        width: 1px;
        height: 1px;
        overflow: hidden;
        clip-path: inset(50%);
        white-space: nowrap;
    }

    /* the phone keyboard's own Search key submits; the field keeps the room, and a
       shorter placeholder (site_header.js) */
    .site-header[data-header-mode="compact"] .site-search-icon,
    .site-header[data-header-mode="compact"] .site-search-key,
    .site-header[data-header-mode="compact"] .site-search-go {
        display: none;
    }
}

/* components.css */
/*
 * The three Materialize widgets this site actually used, rewritten by hand:
 * the preloader, the indeterminate progress bar, the toast and the tooltip.
 * Behaviour for the last two lives in js/ui.js.
 */

/* preloader, replacing Materialize .preloader-wrapper */

.spinner {
    display: inline-block;
    width: 1.5rem;
    height: 1.5rem;
    border: 3px solid var(--border-strong);
    border-top-color: var(--accent-strong);
    border-radius: 50%;
    animation: spin 700ms linear infinite;
}

.spinner-sm {
    width: 1.1rem;
    height: 1.1rem;
    border-width: 2px;
}

@keyframes spin {
    to {
        transform: rotate(360deg);
    }
}

/* indeterminate progress bar, replacing Materialize .progress */

.progress {
    position: relative;
    height: 6px;
    margin: 1.5rem 0;
    overflow: hidden;
    border-radius: 999px;
    background: color-mix(in srgb, var(--accent) 25%, var(--surface));
}

.progress .bar {
    position: absolute;
    inset-block: 0;
    width: 40%;
    border-radius: inherit;
    background: var(--accent-strong);
    animation: slide 1.6s ease-in-out infinite;
}

@keyframes slide {
    0% {
        left: -40%;
    }

    100% {
        left: 100%;
    }
}

/* toast, replacing Materialize.toast */

.toast-stack {
    position: fixed;
    left: 50%;
    bottom: 1.5rem;
    translate: -50% 0;
    z-index: 1000;
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    align-items: center;
    pointer-events: none;
}

.toast {
    background: var(--toast-bg);
    color: var(--toast-text);
    padding: .7rem 1.1rem;
    border-radius: var(--radius-sm);
    box-shadow: var(--shadow-2);
    font-size: var(--fs-md);
    max-width: min(90vw, 30rem);
}

/* it goes as it came, at once (ui.js removes it a moment later) */
.toast.is-leaving {
    opacity: 0;
}

/* tooltip, replacing $.fn.tooltip and driven by the same data-tooltip attributes */

.tooltip {
    position: absolute;
    z-index: 1100;
    max-width: 18rem;
    padding: .4rem .6rem;
    border-radius: var(--radius-sm);
    background: var(--toast-bg);
    color: var(--toast-text);
    font-size: var(--fs-xs);
    line-height: 1.35;
    /* the pointer can rest on it to read it (WCAG 1.4.13); ui.js keeps it open meanwhile */
    box-shadow: var(--shadow-2);
}

/* Reduced motion: nothing loops. The spinner holds still as a ring with its coloured
   quarter, and the progress bar becomes a full, static track fill, which still reads as
   "working" next to the page's loading state. The one transition left (the phone's score
   bar, results_side.css) runs on --speed, which tokens.css drops to 1ms under the same query. */
@media (prefers-reduced-motion: reduce) {
    .spinner {
        animation: none;
    }

    .progress .bar {
        left: 0;
        width: 100%;
        animation: none;
    }
}

/* The analytics cookie banner (consent_banner.js): a compact card fixed to the bottom
   right corner, so the page under it never moves; a phone's is the width of the screen
   less a margin. Reject and Accept share one style, so neither is the easy one. */
.consent-banner {
    position: fixed;
    inset: auto max(var(--space-4), env(safe-area-inset-right)) max(var(--space-4), env(safe-area-inset-bottom)) auto;
    z-index: 1050;
    box-sizing: border-box;
    display: grid;
    gap: var(--space-3);
    width: min(23rem, 100vw - 2 * var(--space-4));
    padding: var(--space-4);
    background: var(--surface);
    color: var(--text);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius);
    box-shadow: var(--shadow-2);
}

.consent-text {
    margin: 0;
    font-size: var(--fs-sm);
    line-height: 1.5;
}

.consent-text a {
    color: var(--link);
    text-decoration: underline;
    text-underline-offset: var(--underline-offset);
}

.consent-actions {
    display: flex;
    gap: var(--space-3);
}

.consent-btn {
    flex: 1 1 0;
    min-height: 2.75rem;
}

.consent-btn:focus-visible {
    outline: 2px solid var(--focus);
    outline-offset: 2px;
}

/* "See more" under a list of channels (the home list and /stats' latest channels), and "Back to
   the newest channels" on /recent: a quiet link, underlined as a link in running text is
   (base.css), with a 44px target on a touch screen, as .btn has */
.list-more {
    display: inline-flex;
    align-items: center;
    font-weight: 500;
    text-decoration: underline;
    text-underline-offset: var(--underline-offset);
}

.list-more-line {
    margin: var(--space-2) 0 0;
}

.list-more:hover {
    text-decoration-thickness: 2px;
}

@media (pointer: coarse) {
    .list-more {
        min-height: 2.75rem;
    }
}

/*
 * The score pill (tokens.css score ramp), on the latest lists' rows and the result's
 * score card; the check grid's cells are score_card.css's. A band is told by shape as
 * well as colour, so it reads in greyscale and for colour-blind visitors: few is an
 * outline, some a tint, many solid.
 */
.score-pill {
    display: inline-flex;
    align-items: center;
    padding: .2rem .55rem;
    border: 1.5px solid transparent;
    border-radius: 999px;
    font-size: var(--fs-xs);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}
.score-pill-few {
    border-color: var(--score-few);
    color: var(--score-few);
}
.score-pill-some {
    background: var(--score-some-bg);
    color: var(--score-some-text);
}
.score-pill-many {
    background: var(--score-many-bg);
    color: var(--score-many-text);
}

/* skeletons: neutral blocks while a scan runs, in both columns; a slow pulse only where
   motion is welcome (results_main.css sizes the main column's, score_card.css the grid's) */
.skeleton-bar {
    display: block;
    border-radius: var(--radius-sm);
    background: var(--surface-2);
}
@media (prefers-reduced-motion: no-preference) {
    .skeleton-bar {
        animation: skeleton-pulse 1.4s ease-in-out infinite alternate;
    }
}
@keyframes skeleton-pulse {
    to {
        opacity: .45;
    }
}

/*
 * Every button but a screen's one main action: outlined in ink, so orange stays the one
 * thing on a screen that asks to be pressed (Run again, Try heart.id, the cookie card's
 * Reject and Accept). The outline is --text-subtle, 3:1 or more on a card (WCAG 1.4.11).
 */
.btn-outline {
    background: var(--surface);
    color: var(--text);
    box-shadow: inset 0 0 0 1px var(--text-subtle);
    font-size: var(--fs-sm);
    font-weight: 600;
    letter-spacing: 0;
    text-transform: none;
}

.btn-outline:hover {
    background: var(--surface-2);
    color: var(--text);
}

.btn-outline:focus-visible {
    outline: 2px solid var(--focus);
    outline-offset: 2px;
}

/*
 * The latest channels (index.html's home list and /recent; render.recent_channel_row and
 * partials/recent_row.ejs): every row is one link. The channel's name is the row's only
 * <a>, and its ::after covers the row, so the whole row opens the result with no second
 * link in it. Each row is a grid, with the header row on the same tracks so the columns
 * line up; a grid box, unlike a table row in some browsers, holds the link's ::after.
 */
.channel-rows tr {
    display: grid;
    grid-template-columns: 2.5rem minmax(0, 1fr) 7rem 9rem 12rem;
    align-items: center;
    column-gap: var(--space-4);
    padding: var(--space-3) var(--space-4);
}

.channel-rows thead tr {
    padding-block: var(--space-2);
    background: var(--surface-2);
    border-bottom: 1px solid var(--border);
}

.channel-rows tbody tr {
    position: relative;
    border-bottom: 1px solid var(--border);
}

.channel-rows tbody tr:last-child {
    border-bottom: 0;
}

.channel-rows th,
.channel-rows td {
    padding: 0;
    border: 0;
}

.channel-rows .thumb {
    width: 2.5rem;
    height: 2.5rem;
}

.channel-rows td[data-label="Channel"] a {
    font-weight: 600;
    color: var(--link);
    overflow-wrap: anywhere;
}

.channel-rows td[data-label="Channel"] a::after {
    content: "";
    position: absolute;
    inset: 0;
}

/* the name underlines while the pointer is anywhere on its row */
.channel-rows tbody tr:hover a,
.channel-rows a:focus-visible {
    text-decoration: underline;
    text-underline-offset: var(--underline-offset);
}

/* the ring goes round the row, which is what the link covers */
.channel-rows a:focus-visible {
    outline: none;
}

.channel-rows a:focus-visible::after {
    outline: 2px solid var(--focus);
    outline-offset: -2px;
}

.channel-rows td[data-label="Subscribers"],
.channel-rows td[data-label="Checked"] {
    font-variant-numeric: tabular-nums;
    color: var(--text-muted);
}

/* the pill, then a chevron at the row's end, as a list of links to pages has. The
   chevron's alternative text is empty, so a screen reader passes over it; a browser
   without that syntax keeps the first declaration. */
.channel-rows td[data-label="Spam score"] {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-2);
}

.channel-rows td[data-label="Spam score"]::after {
    content: "\203A";
    content: "\203A" / "";
    font-size: var(--fs-lg);
    line-height: 1;
    color: var(--text-subtle);
}

/* the spinner or a message in place of rows */
.channel-rows td.cell-center {
    grid-column: 1 / -1;
}

/* " · few signals": a flex item would drop its leading space */
.score-pill-band {
    white-space: pre;
}

/* On a phone: the thumbnail, the name with "49 subs · 3 minutes ago" under it, and the
   pill with the percent alone in sight (its band word stays for screen readers). */
@container tablewrap (max-width: 46rem) {
    .channel-rows tbody tr {
        grid-template-columns: 2.5rem auto minmax(0, 1fr) auto;
        grid-template-areas:
            "thumb name name score"
            "thumb subs checked score";
        gap: .1rem var(--space-3);
        padding: var(--space-3) var(--space-4);
    }

    .channel-rows tbody td {
        display: block;
    }

    .channel-rows tbody td:first-child {
        grid-area: thumb;
    }

    .channel-rows td[data-label="Channel"] {
        grid-area: name;
    }

    .channel-rows td[data-label="Subscribers"] {
        grid-area: subs;
    }

    /* the row's column gap would sit before the "·"; pull it back to a word space */
    .channel-rows td[data-label="Checked"] {
        grid-area: checked;
        margin-inline-start: calc(var(--space-1) - var(--space-3));
        overflow: hidden;
        text-overflow: ellipsis;
    }

    .channel-rows td[data-label="Subscribers"],
    .channel-rows td[data-label="Checked"] {
        font-size: var(--fs-xs);
        white-space: nowrap;
    }

    .channel-rows td[data-label="Spam score"] {
        grid-area: score;
    }

    .channel-rows td[data-label]::before,
    .channel-rows td[data-label="Spam score"]::after {
        content: none;
    }

    .channel-rows td[data-label="Subscribers"] span::after {
        content: " subs";
    }

    .channel-rows td[data-label="Checked"]::before {
        content: "· ";
        content: "· " / "";
        font-size: inherit;
        letter-spacing: 0;
        color: inherit;
    }

    .channel-rows .score-pill-band {
        position: absolute;
        width: 1px;
        height: 1px;
        overflow: hidden;
        clip-path: inset(50%);
        white-space: nowrap;
    }

    /* a spinner or message row is one block across the width: in the row grid its one
       cell is also :first-child, so the thumb track's rule above would squeeze it */
    .channel-rows tbody tr.message-row {
        display: block;
    }

    .channel-rows td.cell-center {
        grid-area: auto;
        grid-column: 1 / -1;
        text-align: center;
    }
}

/* recent.css */
/*
 * "Recently checked channels" (views/recent.ejs). The table and its rows are
 * components.css's .channel-rows, as on the home list, phone layout included; this
 * file carries the page's layout, the status line and the end of the list.
 */

.recent-page {
    flex: 1 0 auto;
    padding-block: clamp(1.5rem, 4vw, 2.5rem) clamp(2rem, 6vw, 4rem);
}

/* the page's title, under the compact header (partials/site_header.ejs), as on /stats: the
   display face at 700 from base.css */
.recent-title {
    margin-bottom: var(--space-3);
    font-size: var(--fs-page-title);
    letter-spacing: -.02em;
}

.recent-intro {
    max-width: 46rem;
    margin: 0 0 var(--space-4);
    color: var(--text-muted);
}

/* loading, how many rows came in, the end of the list or an error (views/js/recent.js) */
.recent-status {
    min-height: 1.4em;
    margin: var(--space-3) 0 0;
    font-size: var(--fs-sm);
    color: var(--text-muted);
}

.recent-more {
    display: flex;
    justify-content: center;
    margin-top: var(--space-3);
}

.recent-end {
    margin: 0;
    color: var(--text-muted);
}

/* the button while its page loads (aria-disabled keeps it focusable) */
.recent-more .btn[aria-disabled="true"] {
    cursor: progress;
    opacity: .75;
}

/* help.css */
/*
 * "Help & feedback" (lib/feedback): the header and footer links and the form's dialog.
 * Only pages lib/feedback/page_entry adds them to load this, so with HKI_FEEDBACK off
 * nothing here applies anywhere.
 *
 * The dialog is a native <dialog> in the top layer, so opening it moves nothing on the
 * page. Colours come from tokens.css, so both themes follow the reader's choice.
 */

/* the header link: a plain link after the nav, styled as the nav's links (header.css),
   pushed to the bar's end; a row of the sheet below 72rem */
.site-help {
    margin-left: auto;
    padding: .45rem .7rem;
    border-radius: var(--radius-sm);
    color: var(--header-muted);
    font-size: var(--fs-sm);
    white-space: nowrap;
}

.site-help:hover {
    color: var(--header-text);
    background: var(--header-control-bg);
}

.site-help:focus-visible {
    outline: 2px solid var(--header-text);
    outline-offset: 2px;
}

.site-menu .site-help + .theme-toggle {
    margin-left: 0;
}

/* in the sheet the menu button opens (header.css) */
@media (width < 72rem) {
    .site-help {
        margin-left: 0;
        padding: .7rem .75rem;
        font-size: var(--fs-base);
    }
}

/* the footer link is an <a> styled as the footer's other link buttons (footer.css) */
a.footer-link-button {
    color: var(--footer-text);
}


/* the dialog */

.help-dialog {
    box-sizing: border-box;
    width: min(30rem, calc(100% - 2rem));
    max-height: calc(100% - 2rem);
    padding: 0;
    border: 1px solid var(--border);
    border-radius: 12px;
    background: var(--surface);
    color: var(--text);
    box-shadow: 0 18px 50px rgba(0, 0, 0, .28);
    overflow: auto;
    overscroll-behavior: contain;
}

.help-dialog::backdrop {
    background: rgba(15, 18, 22, .5);
}

.help-form,
.help-done {
    display: grid;
    /* minmax(0, 1fr): a long attached item shortens with an ellipsis instead of widening the form */
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-3);
    padding: var(--space-5);
}

.help-done[hidden],
.help-form[hidden] {
    display: none;
}

.help-title {
    margin: 0;
    font-size: var(--fs-lg);
    line-height: 1.3;
}

.help-title:focus {
    outline: none;
}

.help-types {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: .4rem;
    min-width: 0;
    margin: 0;
    padding: 0;
    border: 0;
}

.help-type {
    display: flex;
    align-items: center;
    gap: .5rem;
    min-height: 2.75rem;
    padding: .45rem .65rem;
    border: 1px solid var(--border-strong);
    border-radius: 8px;
    font-size: var(--fs-sm);
    line-height: 1.25;
    cursor: pointer;
}

.help-type input {
    flex: none;
    margin: 0;
    accent-color: var(--brand);
}

.help-type:hover {
    background: var(--surface-hover);
}

/* the chosen type outlined in the brand red; without :has() the radio alone shows it */
@supports selector(:has(*)) {
    .help-type:has(input:checked) {
        border-color: var(--brand);
        box-shadow: inset 0 0 0 1px var(--brand);
        font-weight: 600;
    }

    .help-type:has(input:focus-visible) {
        outline: 2px solid var(--focus);
        outline-offset: 2px;
    }
}

.help-field {
    display: grid;
    gap: .3rem;
}

.help-field label,
.help-question legend {
    font-size: var(--fs-sm);
    font-weight: 600;
}

.help-field textarea,
.help-field input {
    width: 100%;
    padding: .55rem .7rem;
    border: 1px solid var(--border-strong);
    border-radius: 8px;
    background: var(--bg);
    color: var(--text);
    font: inherit;
    font-size: var(--fs-md);
}

.help-field textarea {
    min-height: 6.5rem;
    resize: vertical;
}

.help-field textarea:focus-visible,
.help-field input:focus-visible {
    outline: 2px solid var(--focus);
    outline-offset: 1px;
}

.help-field [aria-invalid="true"] {
    border-color: var(--brand);
}

.help-hint {
    margin: 0;
    color: var(--text-muted);
    font-size: var(--fs-xs);
}

.help-count:empty {
    display: none;
}

.help-attached {
    display: grid;
    gap: .35rem;
    padding: .55rem .7rem;
    border: 1px dashed var(--border-strong);
    border-radius: 8px;
    background: var(--bg);
    font-size: var(--fs-xs);
    color: var(--text-muted);
}

.help-attached-label {
    margin: 0;
}

.help-attached-list {
    display: flex;
    flex-wrap: wrap;
    gap: .3rem;
    margin: 0;
    padding: 0;
    list-style: none;
}

.help-attached-list:empty {
    display: none;
}

.help-chip {
    display: inline-flex;
    align-items: center;
    max-width: 100%;
    border: 1px solid var(--border);
    border-radius: 999px;
    background: var(--surface);
    color: var(--text);
}

.help-chip-text {
    min-width: 0;
    padding: .2rem 0 .2rem .6rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.help-chip button {
    display: inline-grid;
    place-items: center;
    flex: none;
    width: 2rem;
    height: 1.75rem;
    padding: 0;
    border: 0;
    border-radius: 999px;
    background: none;
    color: var(--text-muted);
    cursor: pointer;
}

.help-chip button:hover {
    color: var(--text);
}

.help-chip button:focus-visible {
    outline: 2px solid var(--focus);
    outline-offset: -2px;
}

.help-chip svg {
    width: .9rem;
    height: .9rem;
    fill: currentColor;
}

/* Turnstile's widget is 65px tall; the room is kept so it appears without moving the form */
.help-turnstile {
    min-height: 65px;
}

.help-turnstile:empty::before {
    content: "Checking that you're a person…";
    display: block;
    padding-top: 1.4rem;
    color: var(--text-muted);
    font-size: var(--fs-xs);
}

.help-status {
    margin: 0;
    padding: .55rem .7rem;
    border-left: 3px solid var(--brand);
    border-radius: 0 6px 6px 0;
    background: var(--surface-2);
    font-size: var(--fs-sm);
}

.help-status a {
    color: inherit;
    text-decoration: underline;
}

.help-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-2);
}

.help-privacy {
    color: var(--text-muted);
    font-size: var(--fs-xs);
    text-decoration: underline;
    text-underline-offset: var(--underline-offset);
}

.help-buttons {
    display: flex;
    gap: var(--space-2);
    margin-left: auto;
}

.help-button {
    min-height: 2.75rem;
    padding: .55rem 1.1rem;
    border: 0;
    border-radius: 8px;
    background: var(--accent);
    color: var(--on-accent);
    font: inherit;
    font-size: var(--fs-md);
    font-weight: 700;
    cursor: pointer;
}

.help-button:hover {
    background: var(--accent-strong);
}

.help-button:focus-visible {
    outline: 2px solid var(--focus);
    outline-offset: 2px;
}

.help-button[disabled] {
    opacity: .6;
    cursor: progress;
}

.help-button-quiet,
.help-button-quiet:hover {
    background: transparent;
    color: var(--text-muted);
    font-weight: 500;
}

.help-done p {
    margin: 0;
}

.help-ref {
    display: inline-block;
    margin-left: .3rem;
    padding: .15rem .5rem;
    border: 1px solid var(--border);
    border-radius: 6px;
    background: var(--bg);
    font-family: var(--font-mono);
    font-size: var(--fs-md);
    /* Plex Mono ships 400 and 500 only (tokens.css); 600 would be a faked bold */
    font-weight: 500;
}

/* a problem report's screenshot (Phase 3) */
.help-shot[hidden],
.help-preview[hidden] {
    display: none;
}

/* a guest's invitation to sign in, in place of the screenshot field */
.help-shot-hint[hidden] {
    display: none;
}

.help-shot-hint a {
    color: inherit;
    text-decoration: underline;
}

.help-shot input[type="file"] {
    padding: .4rem;
    font-size: var(--fs-sm);
}

.help-preview {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2);
}

.help-preview img {
    max-width: 8rem;
    max-height: 5rem;
    border: 1px solid var(--border);
    border-radius: 6px;
    object-fit: contain;
    background: var(--bg);
}

/* a result dispute: "Is this your channel?" and the owners' hint */
.help-question {
    display: grid;
    gap: .3rem;
    min-width: 0;
    margin: 0;
    padding: 0;
    border: 0;
}

.help-question[hidden],
.help-owner-hint[hidden] {
    display: none;
}

.help-link-button {
    padding: 0;
    border: 0;
    background: none;
    color: inherit;
    font: inherit;
    text-decoration: underline;
    text-underline-offset: var(--underline-offset);
    cursor: pointer;
}

.help-owner-hint a {
    color: inherit;
    text-decoration: underline;
}

/* a phone: the dialog rises from the bottom edge, full width */
@media (width < 34rem) {
    .help-dialog {
        width: 100%;
        max-width: 100%;
        margin: auto 0 0;
        border-radius: 14px 14px 0 0;
        border-bottom: 0;
    }

    .help-form,
    .help-done {
        padding: var(--space-4);
    }
}

@supports (height: 100dvh) {
    .help-dialog {
        max-height: calc(100dvh - 2rem);
    }

    @media (width < 34rem) {
        .help-dialog {
            max-height: calc(100dvh - 1rem);
        }
    }
}

/* help_result.css */
/*
 * "Help & feedback" outside its dialog (help_result.js): "This result looks wrong?" under a
 * score, and an error's reference. Added with help.css by lib/feedback/page_entry, right
 * after it, so with HKI_FEEDBACK off nothing here applies anywhere.
 */

/* under the score on a result (help_result.js) */
.help-dispute-line {
    margin: var(--space-2) 0 0;
    font-size: var(--fs-sm);
}

.help-dispute-line a,
.help-error-report {
    color: var(--text-muted);
    text-decoration: underline;
    text-underline-offset: var(--underline-offset);
}

.help-dispute-line a:hover,
.help-error-report:hover {
    color: var(--text);
}

/* an error's reference, in the page's error message, the slow scan's panel and a toast */
.help-error-ref {
    margin: var(--space-2) 0 0;
    font-size: var(--fs-sm);
    color: var(--text-muted);
}

.help-error-code {
    font-family: var(--font-mono);
    font-size: .95em;
}

/* signin.css */
/*
 * The header's account (lib/signin/chip.js, views/js/signin_chip.js). Loaded
 * only while HKI_SIGNIN is on. Signed out it is heart.id's black sign-in
 * button; signed in, a menu button (initial, name, paid plan) opening a menu of
 * the owner pages, heart.id's billing page and "Sign out". Both are as tall as
 * the theme button, so the bar keeps its height.
 */

.signin-chip:not([hidden]) {
    display: flex;
    align-items: center;
    min-width: 0;
}

/* The displays here would otherwise beat the hidden attribute. */
.signin-chip [hidden] {
    display: none;
}


/* Signed out: heart.id's own button, black with the red heart; "Sign in with
   Google" over a smaller "via heart.id". Capped at the account button's 17rem,
   each line cut with an ellipsis past it, so a longer label never widens the bar
   (header.css counts on that). */

.signin-chip-link {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    min-width: 0;
    max-width: 17rem;
    min-height: 2.125rem;
    padding: .2rem .8rem .2rem .65rem;
    border-radius: var(--radius-sm);
    font-size: var(--fs-sm);
    font-weight: 600;
    white-space: nowrap;
    color: var(--heartid-text);
    background: var(--heartid-bg);
}

.signin-chip-text {
    display: grid;
    min-width: 0;
    line-height: 1.15;
}

.signin-chip-main,
.signin-chip-via {
    overflow: hidden;
    text-overflow: ellipsis;
}

.signin-chip-via {
    font-size: var(--fs-3xs);
    font-weight: 500;
}

.signin-chip-link:hover {
    color: var(--heartid-text);
    background: var(--heartid-bg-hover);
}

/* heart.id's mark: a red dot, and its heart where masks work */
.signin-chip-link::before {
    content: "";
    flex: none;
    width: 1rem;
    height: 1rem;
    border-radius: 50%;
    background: var(--heartid-mark);
}

@supports (mask: url("")) {
    .signin-chip-link::before {
        border-radius: 0;
        mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 21.3 10.6 20C5.4 15.4 2 12.3 2 8.5A5.4 5.4 0 0 1 7.5 3 6 6 0 0 1 12 5.1 6 6 0 0 1 16.5 3 5.4 5.4 0 0 1 22 8.5c0 3.8-3.4 6.9-8.6 11.5L12 21.3z'/%3E%3C/svg%3E") center / contain no-repeat;
    }
}


/* signed in: the account menu button */

.account {
    position: relative;
    min-width: 0;
}

.account-button {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    max-width: 17rem;
    min-height: 2.125rem;
    padding: .2rem .75rem .2rem .25rem;
    font: inherit;
    font-size: var(--fs-sm);
    color: inherit;
    background: var(--header-control-bg);
    border: 0;
    border-radius: 999px;
    cursor: pointer;
}

.account-button:hover,
.account-button[aria-expanded="true"] {
    background: var(--header-control-bg-hover);
}

.signin-chip-link:focus-visible,
.account-button:focus-visible {
    outline: 2px solid var(--focus);
    outline-offset: 2px;
}

.account-avatar {
    flex: none;
    display: grid;
    place-items: center;
    width: 1.625rem;
    height: 1.625rem;
    border-radius: 50%;
    font-size: var(--fs-2xs);
    font-weight: 700;
    color: var(--on-accent);
    background: var(--accent);
}

/* without a name there is no letter to show; the words start at the edge instead */
.account-avatar[hidden] + .account-name {
    margin-left: var(--space-2);
}

/* The name stays on one line and never pushes the bar's other controls off a phone. */
.account-name {
    min-width: 0;
    max-width: 10rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* the plan, shown for paid plans only (signin_chip.js) */
.account-plan {
    flex: none;
    padding: 1px 6px;
    border-radius: 4px;
    font-size: var(--fs-3xs);
    font-weight: 700;
    letter-spacing: .05em;
    text-transform: uppercase;
    color: var(--on-accent);
    background: var(--accent);
}


/* the menu */

.account-menu {
    /* a card in the page's colours, so the ring is the page text's, not the red band's */
    --focus: var(--text);
    position: absolute;
    z-index: 30;
    top: calc(100% + var(--space-2));
    right: 0;
    width: 16.25rem;
    padding: .375rem;
    color: var(--text);
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow-2);
}

.account-who {
    display: grid;
    gap: 2px;
    margin: 0 0 var(--space-1);
    padding: .6rem .6rem .55rem;
    border-bottom: 1px solid var(--border);
}

.account-who strong {
    font-size: var(--fs-md);
    font-weight: 600;
    overflow-wrap: anywhere;
}

.account-who span {
    font-size: var(--fs-2xs);
    color: var(--text-muted);
}

.account-links,
.account-channel-links {
    margin: 0;
    padding: 0;
    list-style: none;
}

/* Each held channel: its title (text from YouTube, cut to one line; the whole
   title is its tooltip), then its links. Not a link, so not a menu item. */
.account-channel + .account-channel {
    margin-top: var(--space-1);
    border-top: 1px solid var(--border);
}

.account-channel-name {
    display: block;
    padding: .5rem .6rem .15rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: var(--fs-2xs);
    font-weight: 600;
    color: var(--text-muted);
}

.account-link {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    width: 100%;
    padding: .55rem .6rem;
    border: 0;
    border-radius: var(--radius-sm);
    font: inherit;
    font-size: var(--fs-sm);
    text-align: left;
    color: var(--text);
    background: transparent;
    cursor: pointer;
}

.account-link:hover {
    color: var(--text);
    background: var(--surface-2);
}

.account-link:focus-visible {
    outline: 2px solid var(--focus);
    outline-offset: -2px;
}

.account-note {
    flex: none;
    font-size: var(--fs-2xs);
    color: var(--text-muted);
}

/* Sign out, set apart from the links above it */
.account-out {
    margin-top: var(--space-1);
    border-top: 1px solid var(--border);
    border-radius: 0 0 var(--radius-sm) var(--radius-sm);
}

@media (pointer: coarse) {
    .signin-chip-link,
    .account-button {
        position: relative;
    }

    /* 34px tall; on a touch screen the hit area reaches 44px (WCAG 2.5.8) */
    .signin-chip-link::after,
    .account-button::after {
        content: "";
        position: absolute;
        inset: -5px 0;
    }

    .account-link {
        min-height: 2.75rem;
    }
}

/* On a phone: "Sign in", and the account as its initial and plan; the link's
   aria-label and the name are still the accessible names. Without :has() the
   compact bar's account button keeps its padding, a little wider. */
@media (width < 60rem) {
    .signin-chip-long,
    .signin-chip-via {
        display: none;
    }

    .account-avatar:not([hidden]) + .account-name,
    .site-header[data-header-mode="compact"] .signin-chip-text {
        position: absolute;
        width: 1px;
        height: 1px;
        overflow: hidden;
        clip-path: inset(50%);
        white-space: nowrap;
    }

    /* The compact bar shares its row with the search: the heart alone, and the
       avatar alone, whose menu names the plan. */
    .site-header[data-header-mode="compact"] .signin-chip-link {
        padding-inline: .6rem;
    }

    .site-header[data-header-mode="compact"] .account-button:has(.account-avatar:not([hidden])) {
        padding-right: .25rem;
    }

    .site-header[data-header-mode="compact"] .account-avatar:not([hidden]) ~ .account-plan {
        display: none;
    }
}

/* The narrowest phones: the avatar alone beside the brand too, whatever the plan is called. */
@media (width < 25rem) {
    .account-button:has(.account-avatar:not([hidden])) {
        padding-right: .25rem;
    }

    .account-avatar:not([hidden]) ~ .account-plan {
        display: none;
    }
}
