/* FishComp design-system components for the web (rollout PR 1, 2026-09-27).
 * Tokens only (css/fc-tokens.css): no literal colours, sizes from the scale.
 * Anatomy follows the FishComp Design System (AppBar, PageHeader, Tabs);
 * the page header is style C, approved 2026-09-27. Dark mode comes from the
 * same tokens under html.dark-mode, so there are no dark rules here. */

/* ------------------------------------------------------------------ */
/* AppBar: the mobile top bar (<992px), includes/header.php            */
/* ------------------------------------------------------------------ */
.fc-appbar {
    display: flex;
    flex-wrap: nowrap;
    align-items: center;
    justify-content: flex-start;
    gap: var(--space-xs);
    color: var(--text-on-header);
}
.fc-appbar__logo {
    display: inline-flex;
    align-items: center;
    min-height: var(--size-touch);
}
.fc-appbar__logo img {
    height: var(--size-logo);
    width: auto;
    display: block;
}
.fc-appbar__spacer {
    flex: 1;
}

/* Header action: a 44 px round target in text-on-header */
.fc-hbtn,
.fc-appbar .navbar-toggler.fc-hbtn {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: var(--size-touch);
    height: var(--size-touch);
    padding: 0;
    border: 0;
    border-radius: var(--radius-full);
    background: transparent;
    color: var(--text-on-header);
    font-size: var(--icon-lg);
    line-height: 1;
    text-decoration: none;
    cursor: pointer;
    box-shadow: none;
    transition: background-color var(--motion-fast);
}
.fc-appbar .navbar-toggler.fc-hbtn {
    font-size: var(--icon-xl);
}
.fc-hbtn:hover,
.fc-hbtn:active {
    background: var(--on-header-pill);
    color: var(--text-on-header);
}
.fc-hbtn:focus {
    box-shadow: none;
}
.fc-hbtn:focus-visible {
    outline: 2px solid var(--text-on-header);
    outline-offset: -2px;
}
.fc-hbtn .fc-count {
    position: absolute;
    top: 4px;
    right: 2px;
}

/* CountBubble */
.fc-count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: var(--size-count-bubble);
    height: var(--size-count-bubble);
    padding: 0 5px;
    border-radius: var(--radius-full);
    background: var(--error);
    color: var(--text-inverse);
    font: 700 10px/1 var(--font-system);
}

/* ------------------------------------------------------------------ */
/* PageHeader (style C): fc_page_header() in includes/page_header.php   */
/* A navy title band and an optional tab row, full bleed across the     */
/* content column (it cancels .main-content-inner's padding).           */
/* ------------------------------------------------------------------ */
.fc-page-header {
    margin: -2rem -2rem var(--space-xl);
    color: var(--text-on-header);
    font-family: var(--font-system);
}
@media (max-width: 991.98px) {
    .fc-page-header {
        margin: -1rem -1rem var(--space-base);
        border-top: var(--border-hairline) solid var(--on-header-pill);
    }
}
.fc-page-header__bar {
    background: var(--navy);
}
/* Cover variant: the page's own photo behind the band, under a navy scrim
   strong enough that the text stays AA on any photo: at 85 % over a pure
   white photo, white text is 7.3:1 and the 72 % muted text (breadcrumb,
   subtitle) 4.65:1 (80 % gave the muted text only 4.1:1). */
.fc-page-header.has-cover .fc-page-header__bar {
    position: relative;
    background-image: var(--fc-cover);
    background-size: cover;
    background-position: center;
}
.fc-page-header.has-cover .fc-page-header__bar::before {
    content: "";
    position: absolute;
    inset: 0;
    background: var(--navy);
    opacity: 0.85;
}
.fc-page-header.has-cover .fc-page-header__inner {
    position: relative;
    padding-top: var(--space-4xl);
}
.fc-page-header__inner {
    padding: var(--space-2xl) var(--space-4xl) var(--space-xl);
}
@media (max-width: 991.98px) {
    .fc-page-header__inner {
        padding: var(--space-base) var(--space-base) var(--space-md);
    }
}
.fc-page-header__row {
    display: flex;
    align-items: center;
    gap: var(--space-base) var(--space-xl);
    flex-wrap: wrap;
}
.fc-page-header__titles {
    flex: 1 1 16rem;
    min-width: 0;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-sm);
}
/* Brand (owner GO 2026-09-28): the cover's huge bold light-blue title */
.fc-page-header .fc-page-header__title {
    margin: 0;
    font: var(--type-display-xl);
    letter-spacing: var(--type-display-xl-tracking);
    color: var(--display-on-navy);
    overflow-wrap: anywhere;
}
.fc-page-header__subtitle {
    margin: 0;
    font: var(--type-body);
    color: var(--text-on-header-muted);
}
@media (max-width: 575.98px) {
    .fc-page-header__titles {
        flex-direction: column;
        align-items: flex-start;
    }
}
.fc-page-header__media {
    width: var(--size-header-touch);
    height: var(--size-header-touch);
    flex: none;
    border-radius: var(--radius-md);
    object-fit: cover;
    background: var(--on-header-pill);
}
.fc-page-header__media-slot {
    position: relative;
    flex: none;
}
.fc-page-header__media-slot img {
    width: var(--size-header-touch);
    height: var(--size-header-touch);
    border-radius: var(--radius-full);
    object-fit: cover;
    background: var(--on-header-pill);
}
.fc-page-header__actions {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--space-sm);
}

/* Header stats (in actions_html): a big value over an overline label */
.fc-header-stats {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-base) var(--space-xl);
    margin: 0;
}
.fc-header-stat {
    display: flex;
    flex-direction: column;
}
/* Phones: the figures stay on one row, smaller */
@media (max-width: 575.98px) {
    .fc-header-stats {
        flex-wrap: nowrap;
        gap: var(--space-base);
    }
    .fc-header-stat {
        flex: 0 1 auto;
        min-width: 0;
    }
    .fc-header-stat-label {
        overflow-wrap: anywhere;
    }
}
.fc-header-stat-value {
    font: var(--type-display-m);
    letter-spacing: var(--type-display-m-tracking);
    color: var(--display-on-navy);
    font-variant-numeric: tabular-nums;
}
@media (max-width: 575.98px) {
    .fc-header-stat-value {
        font: var(--type-title-detail);
        font-weight: 800;
    }
}
.fc-header-badges {
    display: inline-flex;
    flex-wrap: wrap;
    gap: var(--space-xs);
    vertical-align: middle;
}
.fc-header-stat-label {
    font: var(--type-label);
    font-weight: 400;
    color: var(--text-on-header-muted);
}

/* Breadcrumb: small, above the title, in the muted header text */
.fc-page-header__crumbs ol {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-xs);
    margin: 0 0 var(--space-sm);
    padding: 0;
    list-style: none;
    font: var(--type-caption);
    color: var(--text-on-header-muted);
}
.fc-page-header__crumbs li + li::before {
    content: "/";
    margin-right: var(--space-xs);
}
.fc-page-header__crumbs a {
    color: var(--text-on-header-muted);
    text-decoration: none;
}
.fc-page-header__crumbs a:hover {
    color: var(--text-on-header);
    text-decoration: underline;
}
.fc-page-header__crumbs [aria-current] {
    color: var(--text-on-header);
}

/* Header action buttons: white on navy (primary) or a pill outline (ghost).
   Pages put these in actions_html. */
.fc-header-action {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-sm);
    min-height: var(--size-touch);
    padding: var(--space-sm) var(--space-base);
    border: var(--border-strong) solid var(--text-on-header);
    border-radius: var(--radius-md);
    background: var(--text-on-header);
    color: var(--navy);
    font: var(--type-label);
    font-weight: 600;
    text-decoration: none;
    white-space: nowrap;
    cursor: pointer;
    transition: opacity var(--motion-fast);
}
.fc-header-action:hover {
    color: var(--navy);
    text-decoration: none;
}
.fc-header-action:active {
    opacity: var(--press-opacity);
}
.fc-header-action.is-ghost {
    background: transparent;
    border-color: var(--text-on-header-muted);
    color: var(--text-on-header);
}
.fc-header-action.is-ghost:hover {
    background: var(--on-header-pill);
    color: var(--text-on-header);
}
.fc-header-action:focus-visible {
    outline: 2px solid var(--text-on-header);
    outline-offset: 2px;
}
/* A state shown in the action slot (e.g. "Du er påmeldt"), and a disabled one
   ("Påmelding åpner snart"): not interactive, still readable (AA on navy). */
.fc-header-action.is-static {
    cursor: default;
}
.fc-header-action.is-static:active {
    opacity: 1;
}
.fc-header-action:disabled {
    cursor: not-allowed;
    background: transparent;
    border-color: var(--text-on-header-muted);
    color: var(--text-on-header-muted);
    opacity: 1;
}
/* Phones: keep the header short (PR 5a review L5). A secondary action marked
   .is-icon-phone shows only its icon (the label stays for screen readers);
   one marked .is-wide-only is left out (it is also in the menu). */
@media (max-width: 575.98px) {
    .fc-header-action.is-icon-phone {
        width: var(--size-touch);
        padding: 0;
    }
    .fc-header-action.is-icon-phone .fc-header-action__label {
        position: absolute;
        width: 1px;
        height: 1px;
        overflow: hidden;
        clip: rect(0 0 0 0);
        white-space: nowrap;
    }
    .fc-header-action.is-wide-only {
        display: none;
    }
}

/* Tabs: the header tab row (44 px, header-tabrow, 3 px active underline).
   Scrolls sideways, never wraps. */
.fc-page-header__tabs {
    background: var(--header-tabrow);
}
.fc-tabs {
    display: flex;
    gap: var(--space-xl);
    height: var(--size-header-tabs);
    padding: 0 var(--space-4xl);
    overflow-x: auto;
    scrollbar-width: none;
    white-space: nowrap;
}
.fc-tabs::-webkit-scrollbar {
    display: none;
}
@media (max-width: 991.98px) {
    .fc-tabs {
        gap: var(--space-lg);
        padding: 0 var(--space-base);
    }
}
.fc-tab {
    position: relative;
    display: inline-flex;
    align-items: center;
    flex: none;
    font: var(--type-label);
    color: var(--text-on-header-muted);
    text-decoration: none;
    border-bottom: var(--border-tab) solid transparent;
    padding-top: var(--border-tab); /* keeps the label centred over the underline */
}
.fc-tab:hover {
    color: var(--text-on-header);
    text-decoration: none;
}
.fc-tab.is-active {
    color: var(--text-on-header);
    font-weight: 600;
    border-bottom-color: var(--header-tab-active);
}
.fc-tab:focus-visible {
    outline: 2px solid var(--text-on-header);
    outline-offset: -4px;
}
/* A count on a header tab (e.g. pending applications), like the bell's. */
.fc-tab-count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: var(--size-count-bubble);
    height: var(--size-count-bubble);
    margin-left: var(--space-xs);
    padding: 0 5px;
    border-radius: var(--radius-full);
    background: var(--error);
    color: var(--text-inverse);
    font: 700 10px/1 var(--font-system);
}

/* ------------------------------------------------------------------ */
/* Brand phone rules (owner GO 2026-09-28): a compact header - 30/800   */
/* title and a 15 px subtitle, both max 2 lines; breadcrumbs hidden;    */
/* a secondary (ghost) action becomes a text link on the title row, the */
/* primary action stays a compact button on its own line. One 16 px     */
/* side gutter: .main-content-inner pads 16, the page container adds 0. */
/* ------------------------------------------------------------------ */
@media (max-width: 767.98px) {
    .main-content-inner > main.container,
    .main-content-inner > main.container-fluid,
    .main-content-inner > .container,
    .main-content-inner > .container-fluid,
    .main-content-inner > main.admin-competitions-container,
    .main-content-inner > .catch-page > .catch-form-wrap {
        padding-left: 0;
        padding-right: 0;
    }
    .fc-page-header__inner,
    .fc-page-header.has-cover .fc-page-header__inner {
        padding: var(--space-base);
    }
    /* the way up a level: only the parent crumb, as one "‹ Parent" link */
    .fc-page-header__crumbs li {
        display: none;
    }
    .fc-page-header__crumbs li:nth-last-child(2):has(a) {
        display: inline-flex;
    }
    .fc-page-header__crumbs li + li::before {
        content: none;
    }
    .fc-page-header__crumbs li:nth-last-child(2) a::before {
        content: "\2039\00a0"; /* ‹ */
    }
    .fc-page-header__row {
        flex-wrap: wrap;
        align-items: flex-start;
        gap: var(--space-sm) var(--space-md);
    }
    .fc-page-header__titles {
        flex: 1 1 14rem;
        min-width: 0;
        gap: var(--space-xs);
    }
    .fc-page-header .fc-page-header__title {
        font: var(--type-display-s);
        letter-spacing: var(--type-display-s-tracking);
        display: -webkit-box;
        -webkit-line-clamp: 3;
        -webkit-box-orient: vertical;
        overflow: hidden;
    }
    /* data titles (a competition's name) are never cut */
    .fc-page-header.is-title-full .fc-page-header__title {
        display: block;
        -webkit-line-clamp: none;
        overflow: visible;
    }
    .fc-page-header__subtitle {
        font: 400 15px/1.4 var(--font-system);
        display: -webkit-box;
        -webkit-line-clamp: 3;
        -webkit-box-orient: vertical;
        overflow: hidden;
    }
    /* actions, text links and header figures get their own line under the
       title, so the title always keeps the full width (PR #101 review) */
    .fc-page-header__actions {
        flex: 1 1 100%;
        min-width: 0;
        flex-wrap: wrap;
    }
    .fc-header-action.is-ghost:not(.is-icon-phone) {
        min-height: 32px;
        padding: 6px 0;
        border: 0;
        background: none;
        font: 600 15px/1.3 var(--font-system);
        color: var(--display-on-navy);
        text-decoration: underline;
        text-underline-offset: 3px;
        white-space: normal;
        text-align: left;
    }
    .fc-header-action.is-ghost:not(.is-icon-phone):hover {
        background: none;
        color: var(--text-on-header);
    }
    .fc-header-action.is-ghost.is-static {
        text-decoration: none;
    }
    .fc-header-action:not(.is-ghost) {
        min-height: 40px;
        padding: var(--space-sm) var(--space-base);
    }
    .fc-header-stats {
        flex-wrap: wrap;
    }
}

/* ------------------------------------------------------------------ */
/* Tiles (brand): solid colour blocks with a big count over a bold      */
/* label, no icons (the feed's shortcuts). Tones: primary (white text), */
/* sky = fab and cyan = secondary-light (ink text), navy (display-on-   */
/* navy). A tile without a count can carry the one gold accent.         */
/* ------------------------------------------------------------------ */
.fc-tiles {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: var(--space-md);
    container-type: inline-size;
}
.fc-tile {
    position: relative;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    gap: var(--space-xs);
    min-height: 112px;
    padding: var(--space-base);
    border-radius: var(--radius-lg);
    text-decoration: none;
    transition: transform var(--motion-fast), filter var(--motion-fast);
}
.fc-tile:hover {
    filter: brightness(1.06);
}
.fc-tile:active {
    transform: scale(0.98);
}
/* the ring sits on the page ground (2 px offset): heading is >= 3:1 there
   in both themes (light #013f63 on #f0f4f8, dark #a8d4f2 on #0b141d) */
.fc-tile:focus-visible {
    outline: 3px solid var(--heading);
    outline-offset: 2px;
}
.fc-tile__count {
    font: var(--type-display-l);
    letter-spacing: var(--type-display-l-tracking);
}
.fc-tile__label {
    font: 700 18px/1.2 var(--font-system);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.fc-tile__short {
    display: none;
}
.fc-tile__accent {
    position: absolute;
    top: var(--space-base);
    right: var(--space-base);
    width: 28px;
    height: 28px;
    border-radius: var(--radius-full);
    background: var(--gold);
}
/* fixed brand fills in both themes */
.fc-tile.tone-primary { background: var(--brand-blue); color: var(--text-on-header); }
.fc-tile.tone-sky     { background: var(--fab); color: var(--ink); }
.fc-tile.tone-cyan    { background: var(--brand-cyan); color: var(--ink); }
.fc-tile.tone-navy    { background: var(--brand-navy); color: var(--display-on-navy); }
.fc-tile.tone-navy .fc-tile__label {
    font: var(--type-display-m);
    letter-spacing: var(--type-display-m-tracking);
}
.fc-tile--text {
    justify-content: flex-start;
    min-height: 0;
    padding: var(--space-xl);
}
.fc-tile__title {
    margin: 0 0 var(--space-sm);
    font: var(--type-display-m);
    letter-spacing: var(--type-display-m-tracking);
    color: inherit;
}
/* An image placeholder without the image: the name's initials on navy
   (brand B2; display-on-navy on brand-navy 7.1:1). */
.fc-initials-block {
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--brand-navy) !important;
    color: var(--display-on-navy) !important;
    font: var(--type-display-m);
    letter-spacing: var(--type-display-m-tracking);
}
/* the row is narrower than four long labels need: short labels */
@container (max-width: 999.98px) {
    .fc-tile__long { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
    .fc-tile__short { display: inline; }
}
@media (max-width: 767.98px) {
    .fc-tiles {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
    .fc-tile {
        min-height: 88px;
        padding: var(--space-md);
    }
    .fc-tile__count {
        font: 800 28px/1 var(--font-system);
        letter-spacing: var(--type-display-m-tracking);
    }
    .fc-tile__label { font-size: 16px; }
    .fc-tile.tone-navy .fc-tile__label { font: 800 22px/1.1 var(--font-system); }
    .fc-tile__accent { width: 20px; height: 20px; top: var(--space-md); right: var(--space-md); }
}

/* ================================================================== */
/* Controls (design-system PR 2, 2026-09-28): Button, Badge, Chip,      */
/* Segmented and in-content Tabs. Bootstrap's components are mapped     */
/* onto the tokens through their own CSS variables, so every existing   */
/* .btn/.badge/.nav-tabs on the site follows without markup changes.    */
/* ================================================================== */

/* Button: sentence case, 600, radius 8, heights 44 / 48 / 56, press =
   opacity .7, disabled .5. No ripple, lift, shadow or gradient. */
.btn {
    --bs-btn-padding-x: var(--space-lg);
    --bs-btn-padding-y: var(--space-md);
    --bs-btn-font-family: var(--font-system);
    --bs-btn-font-size: 16px;
    --bs-btn-font-weight: 600;
    --bs-btn-line-height: 20px;
    --bs-btn-border-width: var(--border-strong);
    --bs-btn-border-radius: var(--radius-md);
    --bs-btn-box-shadow: none;
    --bs-btn-focus-box-shadow: var(--focus-ring);
    --bs-btn-disabled-opacity: 0.5;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-sm);
    min-height: var(--size-input);
    text-transform: none;
    letter-spacing: 0;
    box-shadow: none;
    transition: opacity var(--motion-fast), background-color var(--motion-fast), border-color var(--motion-fast), color var(--motion-fast);
}
.btn:active {
    opacity: var(--press-opacity);
    transform: none;
}
.btn:focus-visible {
    outline: none;
    box-shadow: var(--focus-ring);
}
.btn-sm,
.btn-fc-sm,
.btn-group-sm > .btn {
    --bs-btn-padding-y: var(--space-sm);
    --bs-btn-padding-x: var(--space-md);
    --bs-btn-font-size: 12px;
    --bs-btn-line-height: 16px;
    --bs-btn-border-radius: var(--radius-md);
    min-height: var(--size-touch);
}
/* Dense admin tables keep a 36 px small button (design-system audit §5). */
table .btn-sm {
    min-height: var(--size-chip);
}
.btn-lg,
.btn-group-lg > .btn {
    --bs-btn-padding-y: var(--space-base);
    --bs-btn-padding-x: var(--space-xl);
    --bs-btn-font-size: 18px;
    --bs-btn-line-height: 22px;
    --bs-btn-border-radius: var(--radius-md);
    min-height: 56px;
}

/* primary: the one action colour. .btn-fc-primary / .btn-fc-outline /
   .btn-fc-sm are admin-manage-competitions-v2.css button names that other
   pages (judge_invitations) use without that stylesheet; they alias here. */
.btn-primary,
.btn-fc-primary,
.btn-cta-gradient {
    --bs-btn-color: var(--text-on-primary);
    --bs-btn-bg: var(--primary);
    --bs-btn-border-color: var(--primary);
    --bs-btn-hover-color: var(--text-on-primary);
    --bs-btn-hover-bg: color-mix(in srgb, var(--primary) 85%, var(--navy));
    --bs-btn-hover-border-color: color-mix(in srgb, var(--primary) 85%, var(--navy));
    --bs-btn-active-color: var(--text-on-primary);
    --bs-btn-active-bg: color-mix(in srgb, var(--primary) 85%, var(--navy));
    --bs-btn-active-border-color: color-mix(in srgb, var(--primary) 85%, var(--navy));
    --bs-btn-disabled-color: var(--text-on-primary);
    --bs-btn-disabled-bg: var(--primary);
    --bs-btn-disabled-border-color: var(--primary);
}
/* .btn-cta-gradient is retired: it renders as a plain primary button. */
.btn-cta-gradient {
    background-image: none;
}

/* secondary (the design system's outline button) */
.btn-outline-primary {
    --bs-btn-color: var(--primary);
    --bs-btn-bg: transparent;
    --bs-btn-border-color: var(--primary);
    --bs-btn-hover-color: var(--primary);
    --bs-btn-hover-bg: var(--surface-variant);
    --bs-btn-hover-border-color: var(--primary);
    --bs-btn-active-color: var(--primary);
    --bs-btn-active-bg: var(--surface-variant);
    --bs-btn-active-border-color: var(--primary);
    --bs-btn-disabled-color: var(--primary);
    --bs-btn-disabled-bg: transparent;
    --bs-btn-disabled-border-color: var(--primary);
}

/* neutral outline (cancel, close, "not available"): Bootstrap's grey
   .btn-secondary maps here too, as it is used almost only to dismiss.
   The border is text-tertiary so the control edge reaches 3:1. */
.btn-secondary,
.btn-outline-secondary,
.btn-outline-dark,
.btn-fc-outline {
    --bs-btn-color: var(--text);
    --bs-btn-bg: transparent;
    --bs-btn-border-color: var(--text-tertiary);
    --bs-btn-hover-color: var(--text);
    --bs-btn-hover-bg: var(--surface-variant);
    --bs-btn-hover-border-color: var(--text-tertiary);
    --bs-btn-active-color: var(--text);
    --bs-btn-active-bg: var(--surface-variant);
    --bs-btn-active-border-color: var(--text-tertiary);
    --bs-btn-disabled-color: var(--text-secondary);
    --bs-btn-disabled-bg: transparent;
    --bs-btn-disabled-border-color: var(--input-border);
}

/* neutral fill: Bootstrap's .btn-light and .btn-dark (the theme toggle) */
.btn-light,
.btn-dark {
    --bs-btn-color: var(--text);
    --bs-btn-bg: var(--surface-variant);
    --bs-btn-border-color: var(--surface-variant);
    --bs-btn-hover-color: var(--text);
    --bs-btn-hover-bg: color-mix(in srgb, var(--surface-variant) 85%, var(--text));
    --bs-btn-hover-border-color: var(--border);
    --bs-btn-active-color: var(--text);
    --bs-btn-active-bg: color-mix(in srgb, var(--surface-variant) 85%, var(--text));
    --bs-btn-active-border-color: var(--border);
    --bs-btn-disabled-color: var(--text-secondary);
    --bs-btn-disabled-bg: var(--surface-variant);
    --bs-btn-disabled-border-color: var(--surface-variant);
}

/* ghost: text-only primary action */
.btn-link {
    --bs-btn-color: var(--primary);
    --bs-btn-hover-color: var(--primary);
    --bs-btn-active-color: var(--primary);
    --bs-btn-disabled-color: var(--text-tertiary);
    --bs-btn-hover-bg: var(--surface-variant);
    --bs-btn-border-color: transparent;
    --bs-btn-hover-border-color: transparent;
    text-decoration: none;
}
.btn-link:hover,
.btn-link:focus {
    background-color: var(--surface-variant);
    text-decoration: none;
}

/* status fills carry text-inverse (white in light, navy in dark) */
.btn-danger  { --fc-btn-tone: var(--error); }
.btn-success { --fc-btn-tone: var(--success); }
.btn-warning { --fc-btn-tone: var(--warning); }
.btn-info    { --fc-btn-tone: var(--info); }
.btn-accent  { --fc-btn-tone: var(--secondary); }
.btn-danger, .btn-success, .btn-warning, .btn-info, .btn-accent {
    --bs-btn-color: var(--text-inverse);
    --bs-btn-bg: var(--fc-btn-tone);
    --bs-btn-border-color: var(--fc-btn-tone);
    --bs-btn-hover-color: var(--text-inverse);
    --bs-btn-hover-bg: color-mix(in srgb, var(--fc-btn-tone) 85%, var(--navy));
    --bs-btn-hover-border-color: color-mix(in srgb, var(--fc-btn-tone) 85%, var(--navy));
    --bs-btn-active-color: var(--text-inverse);
    --bs-btn-active-bg: color-mix(in srgb, var(--fc-btn-tone) 85%, var(--navy));
    --bs-btn-active-border-color: color-mix(in srgb, var(--fc-btn-tone) 85%, var(--navy));
    --bs-btn-disabled-color: var(--text-inverse);
    --bs-btn-disabled-bg: var(--fc-btn-tone);
    --bs-btn-disabled-border-color: var(--fc-btn-tone);
}
.btn-outline-danger  { --fc-btn-tone: var(--error);   --fc-btn-tint: var(--danger-bg); }
.btn-outline-success { --fc-btn-tone: var(--success); --fc-btn-tint: var(--success-bg); }
.btn-outline-warning { --fc-btn-tone: var(--warning); --fc-btn-tint: var(--warning-bg); }
.btn-outline-info    { --fc-btn-tone: var(--info);    --fc-btn-tint: var(--info-bg); }
.btn-outline-danger, .btn-outline-success, .btn-outline-warning, .btn-outline-info {
    --bs-btn-color: var(--fc-btn-tone);
    --bs-btn-bg: transparent;
    --bs-btn-border-color: var(--fc-btn-tone);
    --bs-btn-hover-color: var(--fc-btn-tone);
    --bs-btn-hover-bg: var(--fc-btn-tint);
    --bs-btn-hover-border-color: var(--fc-btn-tone);
    --bs-btn-active-color: var(--fc-btn-tone);
    --bs-btn-active-bg: var(--fc-btn-tint);
    --bs-btn-active-border-color: var(--fc-btn-tone);
    --bs-btn-disabled-color: var(--fc-btn-tone);
    --bs-btn-disabled-bg: transparent;
    --bs-btn-disabled-border-color: var(--fc-btn-tone);
}

/* Selected state of toggle groups (.btn-check + label, or .btn.active) built
   from outline buttons: a solid fill, so selected never depends on a faint
   tint (WCAG 1.4.1 / 1.4.11). Neutral and primary outlines select to primary;
   status outlines select to their own tone. */
.btn-check:checked + :is(.btn-outline-primary, .btn-secondary, .btn-outline-secondary, .btn-outline-dark),
:is(.btn-outline-primary, .btn-secondary, .btn-outline-secondary, .btn-outline-dark).active {
    color: var(--text-on-primary);
    background-color: var(--primary);
    border-color: var(--primary);
}
.btn-check:checked + :is(.btn-outline-success, .btn-outline-danger, .btn-outline-warning, .btn-outline-info),
:is(.btn-outline-success, .btn-outline-danger, .btn-outline-warning, .btn-outline-info).active {
    color: var(--text-inverse);
    background-color: var(--fc-btn-tone);
    border-color: var(--fc-btn-tone);
}
.btn.active {
    opacity: 1;
}

/* .btn has a gap for icons; icons that also carry a margin utility would
   double it. */
.btn > :is(i, svg, .fa, .fas, .far, .fab, .bi)[class*="me-"],
.btn > :is(i, svg, .fa, .fas, .far, .fab, .bi)[class*="ms-"] {
    margin-inline: 0 !important;
}

/* Small groups stay dense: small input groups and small button groups in
   tables use the 36 px height, like table .btn-sm. */
.input-group-sm > .btn,
table .btn-group-sm > .btn {
    min-height: var(--size-chip);
}

/* Badge: 20 px pill, 10/600. Bootstrap's .badge + .bg-* map to the solid
   status pill (tone fill + text-inverse); new code can use .fc-badge tones. */
.badge {
    --bs-badge-padding-x: var(--space-sm);
    --bs-badge-padding-y: 2px;
    --bs-badge-font-size: 10px;
    --bs-badge-font-weight: 600;
    --bs-badge-border-radius: var(--radius-full);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-xs);
    min-height: 20px;
    line-height: 12px;
    font-family: var(--font-system);
    letter-spacing: 0;
    vertical-align: middle;
}
.badge.bg-primary,   .badge.text-bg-primary   { background-color: var(--primary) !important; color: var(--text-on-primary) !important; }
.badge.bg-success,   .badge.text-bg-success   { background-color: var(--success) !important; color: var(--text-inverse) !important; }
.badge.bg-warning,   .badge.text-bg-warning   { background-color: var(--warning) !important; color: var(--text-inverse) !important; }
.badge.bg-danger,    .badge.text-bg-danger    { background-color: var(--error) !important;   color: var(--text-inverse) !important; }
.badge.bg-info,      .badge.text-bg-info      { background-color: var(--info) !important;    color: var(--text-inverse) !important; }
.badge.bg-secondary, .badge.text-bg-secondary { background-color: var(--surface-variant) !important; color: var(--text-secondary) !important; }
.badge.bg-light,     .badge.text-bg-light     { background-color: var(--surface-variant) !important; color: var(--text) !important; }
.badge.bg-dark,      .badge.text-bg-dark      { background-color: var(--fc-badge-dark-bg, var(--navy)) !important; color: var(--fc-badge-dark-color, var(--text-on-header)) !important; }
/* navy disappears on the dark card, so dark badges use the neutral step there
   (dark sets the variables; the fill rule above stays the one !important rule) */
html.dark-mode { --fc-badge-dark-bg: var(--surface-variant); --fc-badge-dark-color: var(--text); }
/* Markup that pairs a fill with .text-dark/.text-white (e.g. "badge bg-warning
   text-dark") keeps the token text colour, so the pair stays AA on the new fills. */
.badge.bg-primary.text-dark, .badge.bg-primary.text-white { color: var(--text-on-primary) !important; }
.badge:is(.bg-success, .bg-warning, .bg-danger, .bg-info):is(.text-dark, .text-white) { color: var(--text-inverse) !important; }

.fc-badge {
    display: inline-flex;
    align-items: center;
    gap: var(--space-xs);
    height: 20px;
    padding: 2px var(--space-sm);
    border-radius: var(--radius-full);
    font: 600 10px/12px var(--font-system);
    white-space: nowrap;
    background: var(--surface-variant);
    color: var(--text-secondary);
}
.fc-badge.info    { background: var(--info-bg);    color: var(--info); }
.fc-badge.success { background: var(--success-bg); color: var(--success); }
.fc-badge.warning,
.fc-badge.record  { background: var(--warning-bg); color: var(--warning); }
.fc-badge.danger  { background: var(--danger-bg);  color: var(--error); }
.fc-badge.solid   { font-weight: 700; letter-spacing: 0.5px; color: var(--text-inverse); }
.fc-badge.solid.success { background: var(--success); }
.fc-badge.solid.warning { background: var(--warning); }
.fc-badge.solid.danger  { background: var(--error); }
.fc-badge.solid.info    { background: var(--info); }
.fc-badge.you     { background: var(--primary); color: var(--text-on-primary); font-weight: 700; }

/* Chip: pill filter toggle, 36 px visual. .fc-chip is the component; the
   existing filter rows (.fc-filter-btn, .filter-btn, .class-filter-btn) take
   the same look in their own stylesheets. */
.fc-chips {
    display: flex;
    gap: var(--space-sm);
    overflow-x: auto;
    scrollbar-width: none;
}
.fc-chips::-webkit-scrollbar {
    display: none;
}
.fc-chip {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-xs);
    min-height: var(--size-chip);
    padding: 0 var(--space-base);
    border: 0;
    border-radius: var(--radius-full);
    background: var(--surface-variant);
    color: var(--text-secondary);
    font: var(--type-label);
    white-space: nowrap;
    text-decoration: none;
    cursor: pointer;
    transition: opacity var(--motion-fast);
}
.fc-chip:hover {
    color: var(--text);
    text-decoration: none;
}
.fc-chip[aria-pressed="true"],
.fc-chip.active {
    background: var(--primary);
    color: var(--text-on-primary);
    font-weight: 600;
}
.fc-chip:active {
    opacity: var(--press-opacity);
}
.fc-chip:disabled,
.fc-chip[aria-disabled="true"] {
    opacity: 0.5;
    cursor: default;
}
.fc-chip:focus-visible {
    outline: none;
    box-shadow: var(--focus-ring);
}

/* Segmented: 2-4 mutually exclusive views */
.fc-seg {
    display: inline-flex;
    padding: var(--space-xs);
    gap: var(--space-xs);
    background: var(--bg-secondary);
    border-radius: var(--radius-lg);
}
.fc-seg > a,
.fc-seg > button {
    flex: 1;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-xs);
    min-height: var(--size-chip);
    padding: var(--space-sm) var(--space-md);
    border: 0;
    border-radius: var(--radius-md);
    background: transparent;
    color: var(--text-secondary);
    font: var(--type-label);
    text-decoration: none;
    white-space: nowrap;
    cursor: pointer;
}
.fc-seg > [aria-pressed="true"],
.fc-seg > [aria-current="page"],
.fc-seg > .active {
    background: var(--bg);
    color: var(--primary);
    font-weight: 600;
    box-shadow: var(--shadow-sm);
}
html.dark-mode .fc-seg > [aria-pressed="true"],
html.dark-mode .fc-seg > [aria-current="page"],
html.dark-mode .fc-seg > .active {
    background: var(--surface-variant);
}

/* Tabs inside content (Bootstrap .nav-tabs): underline tabs on the page,
   the in-content sibling of the header tab row. Sentence case, 14/500,
   active primary with a 3 px underline. */
.nav-tabs {
    --bs-nav-tabs-border-width: 0;
    flex-wrap: nowrap;
    gap: var(--space-xl);
    overflow-x: auto;
    scrollbar-width: none;
    border-bottom: 0;
    /* hairline drawn inside, so the scroller does not clip the underline */
    box-shadow: inset 0 calc(-1 * var(--border-hairline)) var(--border);
}
.nav-tabs::-webkit-scrollbar {
    display: none;
}
.nav-tabs .nav-link {
    padding: var(--space-md) 0;
    margin-bottom: 0;
    border: 0;
    border-bottom: var(--border-tab) solid transparent;
    border-radius: 0;
    background: none;
    color: var(--text-secondary);
    font: var(--type-label);
    text-transform: none;
    letter-spacing: 0;
    white-space: nowrap;
}
.nav-tabs .nav-link:hover,
.nav-tabs .nav-link:focus {
    color: var(--text);
    border-bottom-color: var(--border);
    background: none;
}
.nav-tabs .nav-link.active,
.nav-tabs .nav-item.show .nav-link {
    color: var(--primary);
    font-weight: 600;
    background: none;
    border-bottom-color: var(--primary);
}
.nav-tabs .nav-link:focus-visible,
.fc-chips > .fc-chip:focus-visible {
    /* inside a scroller a box-shadow ring is clipped; an inset outline is not */
    outline: 2px solid var(--primary);
    outline-offset: -2px;
    box-shadow: none;
}

/* ================================================================== */
/* Surfaces (design-system PR 3, 2026-09-28): Card, ListRow, rank disc, */
/* Avatar and EmptyState. Bootstrap's .card is mapped through its own   */
/* --bs-card-* variables, like the buttons.                              */
/* ================================================================== */

/* Card: radius 12, card fill, 1 px card-border, shadow-sm in light only.
   Header and footer sit on the card with a border-light separator. No
   hover lift anywhere; a link card shows its pointer cue as a primary
   border. */
.card {
    --bs-card-bg: var(--card);
    --bs-card-border-width: var(--border-default);
    --bs-card-border-color: var(--card-border);
    --bs-card-border-radius: var(--radius-lg);
    --bs-card-inner-border-radius: calc(var(--radius-lg) - var(--border-default));
    --bs-card-cap-bg: var(--card);
    --bs-card-cap-color: var(--text);
    --bs-card-cap-padding-y: var(--space-md);
    --bs-card-cap-padding-x: var(--space-base);
    --bs-card-spacer-y: var(--space-base);
    --bs-card-spacer-x: var(--space-base);
    --bs-card-subtitle-color: var(--text-secondary);
    --bs-card-box-shadow: var(--shadow-sm);
    /* colour on the card itself (not --bs-card-color / --bs-card-title-color,
       which Bootstrap pins onto .card-body/.card-title), so .text-white and
       filled cards flow through to the body and title */
    color: var(--text);
    margin-bottom: var(--space-xl);
    box-shadow: var(--shadow-sm);
}
html.dark-mode .card {
    box-shadow: none;
}
.card-header {
    font: var(--type-heading);
    border-bottom-color: var(--border-light);
}
.card-footer {
    border-top-color: var(--border-light);
}
.card-title {
    font-weight: 600;
}
a.card:hover,
.card.card-link:hover {
    border-color: var(--primary);
    transform: none;
}
/* A card, header or footer painted with a Bootstrap .bg-* fill keeps the
   token text colour for that fill (as the status badges do). */
:is(.card, .card-header, .card-footer).bg-primary { background-color: var(--primary) !important; color: var(--text-on-primary) !important; }
:is(.card, .card-header, .card-footer):is(.bg-success, .bg-info, .bg-danger, .bg-warning) { color: var(--text-inverse) !important; }
/* ...also when the markup adds .text-dark/.text-white/.text-body (the dark
   critical block paints .text-dark with the light text token) */
:is(.card, .card-header, .card-footer).bg-primary:is(.text-dark, .text-white, .text-body) { color: var(--text-on-primary) !important; }
:is(.card, .card-header, .card-footer):is(.bg-success, .bg-info, .bg-danger, .bg-warning):is(.text-dark, .text-white, .text-body) { color: var(--text-inverse) !important; }
:is(.card, .card-header, .card-footer).bg-success { background-color: var(--success) !important; }
:is(.card, .card-header, .card-footer).bg-info    { background-color: var(--info) !important; }
:is(.card, .card-header, .card-footer).bg-danger  { background-color: var(--error) !important; }
:is(.card, .card-header, .card-footer).bg-warning { background-color: var(--warning) !important; }
:is(.card, .card-header, .card-footer):is(.bg-primary, .bg-success, .bg-info, .bg-danger, .bg-warning) :is(h1, h2, h3, h4, h5, h6, p, .card-body, .card-title, .card-text, .text-white) { color: inherit !important; }
/* muted white text on a filled header follows the fill's text colour at 75 % */
:is(.card, .card-header, .card-footer):is(.bg-primary, .bg-success, .bg-info, .bg-danger, .bg-warning) .text-white-50 { color: color-mix(in srgb, currentColor 75%, transparent) !important; }

.fc-card {
    background: var(--card);
    border: var(--border-default) solid var(--card-border);
    border-radius: var(--radius-lg);
    overflow: hidden;
    box-shadow: var(--shadow-sm);
}
html.dark-mode .fc-card {
    box-shadow: none;
}
.fc-card.filled {
    background: var(--surface-variant);
    border-color: transparent;
    box-shadow: none;
}
.fc-card-h,
.fc-card-b,
.fc-card-f {
    padding: var(--space-base);
}
.fc-card-h {
    border-bottom: var(--border-hairline) solid var(--border-light);
    font: var(--type-heading);
}
.fc-card-f {
    border-top: var(--border-hairline) solid var(--border-light);
    display: flex;
    gap: var(--space-sm);
    justify-content: flex-end;
}
a.fc-card {
    display: block;
    color: inherit;
    text-decoration: none;
}
a.fc-card:hover {
    border-color: var(--primary);
}

/* ListRow: min 56, padding 8 16, gap 12; leading 32 rank + 40 avatar;
   title 16/600, meta 12 text-secondary, value 16/700 heading; hairline
   divider inset to the text column. */
.fc-list {
    background: var(--card);
}
.fc-section {
    margin: 0;
    padding: var(--space-base) var(--space-base) var(--space-sm);
    font: var(--type-overline);
    letter-spacing: var(--type-overline-tracking);
    text-transform: uppercase;
    color: var(--text-secondary);
}
.fc-row {
    position: relative;
    display: flex;
    align-items: center;
    gap: var(--space-md);
    min-height: var(--size-row);
    padding: var(--space-sm) var(--space-base);
    color: inherit;
    text-decoration: none;
}
.fc-row + .fc-row::before {
    content: "";
    position: absolute;
    top: 0;
    right: 0;
    left: calc(var(--space-base) + var(--size-avatar-md) + var(--space-md));
    border-top: var(--border-hairline) solid var(--divider);
}
.fc-row.has-rank + .fc-row.has-rank::before {
    left: calc(var(--space-base) + var(--size-avatar-sm) + var(--size-avatar-md) + 2 * var(--space-md));
}
.fc-row.is-me {
    background: color-mix(in srgb, var(--primary) 10%, var(--card));
}
.fc-row.is-first {
    background: color-mix(in srgb, var(--gold) 15%, var(--card));
}
.fc-row-main {
    flex: 1;
    min-width: 0;
}
.fc-row-title {
    font: 600 16px/22px var(--font-system);
    color: var(--text);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.fc-row-meta {
    font: var(--type-caption);
    color: var(--text-secondary);
}
.fc-row-value {
    font: var(--type-value);
    color: var(--heading);
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}
.fc-row .fc-chev {
    color: var(--text-secondary);
}
.fc-thumb {
    width: var(--size-avatar-md);
    height: var(--size-avatar-md);
    flex: none;
    border-radius: var(--radius-md);
    background: var(--surface-variant);
    object-fit: cover;
}

/* Rank disc: 32 px; gold/silver/bronze for 1-3 with text-on-medal. */
.fc-rank {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: none;
    width: var(--size-avatar-sm);
    height: var(--size-avatar-sm);
    border-radius: var(--radius-full);
    background: var(--surface-variant);
    color: var(--text);
    font: 700 14px/1 var(--font-system);
    font-variant-numeric: tabular-nums;
}
.fc-rank.gold   { background: var(--gold);   color: var(--text-on-medal); }
.fc-rank.silver { background: var(--silver); color: var(--text-on-medal); }
.fc-rank.bronze { background: var(--bronze); color: var(--text-on-medal); }

/* Avatar: a photo, or the initials on primary (no third-party avatar
   service). A photo that fails to load is removed and the initials show. */
.fc-avatar {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: none;
    width: var(--size-avatar-md);
    height: var(--size-avatar-md);
    border-radius: var(--radius-full);
    background: var(--primary);
    color: var(--text-on-primary);
    font: 700 16px/1 var(--font-system);
    overflow: hidden;
    text-transform: uppercase;
}
.fc-avatar > img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
}
.fc-avatar.sm {
    width: var(--size-avatar-sm);
    height: var(--size-avatar-sm);
    font-size: 12px;
}
.fc-avatar.lg {
    width: var(--size-avatar-lg);
    height: var(--size-avatar-lg);
    font-size: 30px;
}

/* EmptyState */
.fc-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-sm);
    padding: var(--space-2xl);
    text-align: center;
}
.fc-empty > i,
.fc-empty > .fc-icon {
    font-size: var(--icon-empty);
    color: var(--text-secondary);
}
.fc-empty h2 {
    margin: var(--space-sm) 0 0;
    font: var(--type-title-detail);
    color: var(--text);
}
.fc-empty p {
    max-width: 320px;
    margin: 0 0 var(--space-sm);
    font: var(--type-body);
    color: var(--text-secondary);
}

/* ================================================================== */
/* Forms (design-system PR 4, 2026-09-28): Field, SearchField, select,  */
/* checks and switches, input groups, validation. Bootstrap's controls  */
/* are styled on the tokens, so every .form-* on the site follows.      */
/* ================================================================== */

:root {
    accent-color: var(--primary);
    --bs-form-valid-color: var(--success);
    --bs-form-valid-border-color: var(--success);
    --bs-form-invalid-color: var(--error);
    --bs-form-invalid-border-color: var(--error);
    /* select chevron in text-secondary; the dark one is set below */
    --bs-form-select-bg-img: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23646b72' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");
}
html.dark-mode {
    --bs-form-select-bg-img: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23a9b6c3' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");
}

/* Label 14/500 text, 4 px above the control; help text and messages 14 (owner ruling 2026-09-28) */
.form-label,
.col-form-label {
    margin-bottom: var(--space-xs);
    font: var(--type-field-label);
    color: var(--text);
}
.form-text {
    margin-top: var(--space-xs);
    font: var(--type-field-help);
    color: var(--text-secondary);
}

/* Field: 48 px, radius 8, input fill, 1 px input-border, 16 px text (no iOS
   zoom). Focus = primary border + bg fill. Disabled .5. */
.form-control,
.form-select {
    min-height: var(--size-input);
    padding: var(--space-md) var(--space-base);
    font: var(--type-body);
    color: var(--text);
    background-color: var(--input);
    border: var(--border-default) solid var(--input-border);
    border-radius: var(--radius-md);
    box-shadow: none;
    transition: border-color var(--motion-fast), background-color var(--motion-fast);
}
.form-select {
    padding-right: calc(var(--space-base) * 2 + var(--icon-sm));
    background-position: right var(--space-base) center;
    background-size: var(--icon-sm) var(--icon-sm);
}
.form-select[multiple],
.form-select[size]:not([size="1"]) {
    padding-right: var(--space-base);
    background-image: none;
}
textarea.form-control {
    min-height: calc(var(--size-input) * 2);
}
.form-control::placeholder {
    color: var(--placeholder);
    opacity: 1;
}
.form-control:focus,
.form-select:focus {
    color: var(--text);
    background-color: var(--bg);
    border-color: var(--primary);
    outline: none;
    /* a 2 px primary edge (border + 1 px shadow), no layout shift */
    box-shadow: 0 0 0 1px var(--primary);
}
.form-control:disabled,
.form-select:disabled,
.form-control[readonly]:not(.form-control-plaintext) {
    background-color: var(--input);
    color: var(--text);
    opacity: 0.5;
}
.form-control[readonly]:not(:disabled) {
    opacity: 1;
    color: var(--text-secondary);
}
.form-control-plaintext {
    color: var(--text);
    font: var(--type-body);
}
.form-control-sm,
.form-select-sm {
    /* dense admin tables and toolbars */
    min-height: var(--size-chip);
    padding: var(--space-xs) var(--space-md);
    font-size: 14px;
    line-height: 20px;
    border-radius: var(--radius-md);
}
.form-select-sm {
    padding-right: calc(var(--space-md) * 2 + var(--icon-sm));
    background-position: right var(--space-md) center;
}
/* Below 768 px every control keeps 16 px text so iOS never zooms on focus;
   the small controls keep their 36 px height. */
@media (max-width: 767.98px) {
    .form-control-sm,
    .form-select-sm,
    .input-group-sm > .input-group-text,
    .input-group-sm > .form-control,
    .input-group-sm > .form-select {
        font-size: 16px;
    }
}
.form-control-lg,
.form-select-lg {
    min-height: 56px;
    padding: var(--space-base) var(--space-lg);
    font-size: 18px;
    border-radius: var(--radius-md);
}
.form-control[type="color"] {
    min-height: var(--size-input);
    padding: var(--space-xs);
}
.form-control::file-selector-button {
    margin: calc(-1 * var(--space-md)) var(--space-base) calc(-1 * var(--space-md)) calc(-1 * var(--space-base));
    padding: var(--space-md) var(--space-base);
    background: var(--surface-variant);
    color: var(--text);
    border: 0;
    border-right: var(--border-default) solid var(--input-border);
}
.form-control:hover:not(:disabled):not([readonly])::file-selector-button {
    background: color-mix(in srgb, var(--surface-variant) 85%, var(--text));
}

/* Autofill keeps the token colours (Chrome/Safari paint their own) */
.form-control:-webkit-autofill,
.form-control:-webkit-autofill:hover,
.form-control:-webkit-autofill:focus,
.form-select:-webkit-autofill {
    -webkit-text-fill-color: var(--text);
    -webkit-box-shadow: 0 0 0 1000px var(--input) inset;
    box-shadow: 0 0 0 1000px var(--input) inset;
    caret-color: var(--text);
}

/* SearchField: the same control at radius-lg */
.fc-search.form-control,
input[type="search"].form-control {
    border-radius: var(--radius-lg);
}

/* Validation: error border + 14 px message in error; success in success */
.form-control.is-invalid,
.form-select.is-invalid,
.was-validated .form-control:invalid,
.was-validated .form-select:invalid {
    border-color: var(--error);
}
.form-control.is-invalid:focus,
.form-select.is-invalid:focus,
.was-validated .form-control:invalid:focus,
.was-validated .form-select:invalid:focus {
    /* keeps the error edge and adds the focus ring, so focus stays visible */
    border-color: var(--error);
    box-shadow: 0 0 0 1px var(--error), var(--focus-ring);
}
.form-control.is-valid:focus,
.form-select.is-valid:focus,
.was-validated .form-control:valid:focus,
.was-validated .form-select:valid:focus {
    border-color: var(--success);
    box-shadow: 0 0 0 1px var(--success), var(--focus-ring);
}
.form-control.is-valid,
.form-select.is-valid,
.was-validated .form-control:valid,
.was-validated .form-select:valid {
    border-color: var(--success);
}
.invalid-feedback,
.invalid-tooltip,
.valid-feedback,
.valid-tooltip {
    margin-top: var(--space-xs);
    font: var(--type-field-help);
}
.invalid-feedback { color: var(--error); }
.valid-feedback { color: var(--success); }
.invalid-tooltip { background: var(--error); color: var(--text-inverse); border-radius: var(--radius-md); }
.valid-tooltip { background: var(--success); color: var(--text-inverse); border-radius: var(--radius-md); }

/* Checks, radios and switches: 20 px, 1.5 px text-tertiary edge (>= 3:1),
   checked = primary with a text-on-primary mark (navy in dark). */
.form-check {
    min-height: var(--size-touch);
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    padding-left: 0;
}
.form-check .form-check-input {
    float: none;
    margin: 0;
    flex: none;
}
.form-check {
    flex-wrap: wrap;
}
.form-check > :is(.form-text, .invalid-feedback, .valid-feedback, small, div) {
    flex-basis: 100%;
    margin-left: calc(20px + var(--space-sm));
}
.form-check-reverse {
    flex-direction: row-reverse;
    justify-content: flex-end;
}
.form-check-inline {
    display: inline-flex;
    margin-right: var(--space-base);
}
.form-check-label {
    font: var(--type-body);
    color: var(--text);
}
.form-check-input {
    width: 20px;
    height: 20px;
    background-color: var(--input);
    border: var(--border-strong) solid var(--text-tertiary);
    --bs-form-check-bg: var(--input);
}
.form-check-input:focus {
    border-color: var(--primary);
    outline: none;
    box-shadow: var(--focus-ring);
}
.form-check-input:checked {
    background-color: var(--primary);
    border-color: var(--primary);
}
.form-check-input[type="checkbox"] {
    border-radius: var(--radius-sm);
}
.form-check-input:checked[type="checkbox"] {
    --bs-form-check-bg-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23ffffff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='m6 10 3 3 6-6'/%3e%3c/svg%3e");
}
.form-check-input:checked[type="radio"] {
    --bs-form-check-bg-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23ffffff'/%3e%3c/svg%3e");
}
.form-check-input:indeterminate {
    background-color: var(--primary);
    border-color: var(--primary);
}
html.dark-mode .form-check-input:checked[type="checkbox"] {
    --bs-form-check-bg-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%2307182a' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='m6 10 3 3 6-6'/%3e%3c/svg%3e");
}
html.dark-mode .form-check-input:checked[type="radio"] {
    --bs-form-check-bg-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%2307182a'/%3e%3c/svg%3e");
}
.form-check-input:disabled,
.form-check-input:disabled ~ .form-check-label {
    opacity: 0.5;
}
.form-check-input.is-invalid,
.was-validated .form-check-input:invalid {
    border-color: var(--error);
}
.form-check-input.is-invalid ~ .form-check-label,
.was-validated .form-check-input:invalid ~ .form-check-label {
    color: var(--error);
}
.form-switch {
    padding-left: 0;
}
.form-switch .form-check-input {
    width: 40px;
    height: 24px;
    margin: 0;
    border-radius: var(--radius-full);
    --bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23697077'/%3e%3c/svg%3e");
}
html.dark-mode .form-switch .form-check-input {
    --bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%238494a3'/%3e%3c/svg%3e");
}
.form-switch .form-check-input:focus {
    --bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%230267a1'/%3e%3c/svg%3e");
}
html.dark-mode .form-switch .form-check-input:focus {
    --bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%235aaee8'/%3e%3c/svg%3e");
}
.form-switch .form-check-input:checked {
    --bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23ffffff'/%3e%3c/svg%3e");
}
html.dark-mode .form-switch .form-check-input:checked {
    --bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%2307182a'/%3e%3c/svg%3e");
}
.form-range {
    accent-color: var(--primary);
}
.form-range::-webkit-slider-thumb { background: var(--primary); }
.form-range::-moz-range-thumb { background: var(--primary); }
.form-range::-webkit-slider-runnable-track { background: var(--surface-variant); }
.form-range::-moz-range-track { background: var(--surface-variant); }
.form-range:focus::-webkit-slider-thumb { box-shadow: var(--focus-ring); }

/* Input groups: addons on surface-variant in the same 1 px input-border */
.input-group-text {
    min-height: var(--size-input);
    padding: 0 var(--space-md);
    font: var(--type-body);
    color: var(--text-secondary);
    background-color: var(--surface-variant);
    border: var(--border-default) solid var(--input-border);
    border-radius: var(--radius-md);
}
.input-group-sm > .input-group-text {
    min-height: var(--size-chip);
    font-size: 14px;
}
.input-group > .form-control:focus,
.input-group > .form-select:focus {
    z-index: 5;
}
.input-group > .btn {
    position: relative;
    z-index: 2;
}
.form-floating > label {
    color: var(--text-secondary);
}
.form-floating > .form-control:focus ~ label,
.form-floating > .form-control:not(:placeholder-shown) ~ label {
    color: var(--text-secondary);
}
.form-floating > .form-control:focus ~ label::after,
.form-floating > .form-control:not(:placeholder-shown) ~ label::after {
    background-color: transparent;
}

/* ================================================================== */
/* Surfaces on tokens (PR 6, dark mode on tokens, 2026-09-28): tables,  */
/* modals, the offcanvas menu, list groups and dropdowns read the       */
/* tokens through Bootstrap's own component variables, so they follow   */
/* html.dark-mode with no dark rules and no !important. The light values */
/* equal Bootstrap's (#fff surfaces), so light mode does not change.    */
/* ================================================================== */
.table {
    --bs-table-color: var(--text);
    --bs-table-bg: var(--card);
    --bs-table-border-color: var(--border);
    --bs-table-striped-color: var(--text);
    --bs-table-active-color: var(--text);
    --bs-table-hover-color: var(--text);
}
.modal {
    --bs-modal-color: var(--text);
    --bs-modal-bg: var(--card);
    --bs-modal-border-color: var(--card-border);
    --bs-modal-header-border-color: var(--border);
    --bs-modal-footer-border-color: var(--border);
}
.offcanvas,
.offcanvas-lg,
.offcanvas-md,
.offcanvas-sm {
    --bs-offcanvas-color: var(--text);
    --bs-offcanvas-bg: var(--drawer);
    --bs-offcanvas-border-color: var(--border);
}
.list-group {
    --bs-list-group-color: var(--text);
    --bs-list-group-bg: var(--card);
    --bs-list-group-border-color: var(--border);
    --bs-list-group-action-color: var(--text);
    --bs-list-group-action-hover-color: var(--text);
    --bs-list-group-action-hover-bg: var(--surface-variant);
    --bs-list-group-action-active-color: var(--text);
    --bs-list-group-action-active-bg: var(--surface-variant);
    --bs-list-group-active-color: var(--text-on-header);
    --bs-list-group-active-bg: var(--brand-blue);
    --bs-list-group-active-border-color: var(--brand-blue);
    --bs-list-group-disabled-color: var(--text-tertiary);
    --bs-list-group-disabled-bg: var(--card);
}
.dropdown-menu {
    --bs-dropdown-color: var(--text);
    --bs-dropdown-bg: var(--card);
    --bs-dropdown-border-color: var(--border);
    --bs-dropdown-divider-bg: var(--divider);
    --bs-dropdown-link-color: var(--text);
    --bs-dropdown-link-hover-color: var(--text);
    --bs-dropdown-link-hover-bg: var(--surface-variant);
    --bs-dropdown-header-color: var(--text-secondary);
}
.popover {
    --bs-popover-bg: var(--card);
    --bs-popover-border-color: var(--border);
    --bs-popover-header-bg: var(--surface-variant);
    --bs-popover-body-color: var(--text);
}
.accordion {
    --bs-accordion-color: var(--text);
    --bs-accordion-bg: var(--card);
    --bs-accordion-border-color: var(--border);
    --bs-accordion-btn-color: var(--text);
    --bs-accordion-btn-bg: var(--card);
    --bs-accordion-active-color: var(--heading);
    --bs-accordion-active-bg: var(--info-bg);
}
.toast {
    --bs-toast-color: var(--text);
    --bs-toast-bg: var(--card);
    --bs-toast-header-color: var(--text-secondary);
    --bs-toast-header-bg: var(--surface-variant);
}
.page-link {
    color: var(--primary);
}
.pagination {
    --bs-pagination-color: var(--primary);
    --bs-pagination-bg: var(--card);
    --bs-pagination-border-color: var(--border);
    --bs-pagination-hover-color: var(--primary);
    --bs-pagination-hover-bg: var(--surface-variant);
    --bs-pagination-hover-border-color: var(--border);
    --bs-pagination-disabled-color: var(--text-tertiary);
    --bs-pagination-disabled-bg: var(--surface-variant);
    --bs-pagination-disabled-border-color: var(--border);
}
/* Bootstrap's light utility tables and fills would stay light in dark */
html.dark-mode .table-light {
    --bs-table-color: var(--text);
    --bs-table-bg: var(--surface-variant);
    --bs-table-border-color: var(--border);
    --bs-table-striped-bg: var(--surface-variant);
    --bs-table-hover-bg: var(--surface-variant);
    color: var(--text);
    border-color: var(--border);
}
html.dark-mode .table-striped > tbody > tr:nth-of-type(odd) > * {
    --bs-table-bg-type: var(--surface-variant);
}
html.dark-mode .table-hover > tbody > tr:hover > * {
    --bs-table-bg-state: var(--surface-variant);
}
/* Bootstrap's colour utilities on the tokens, in both themes (PR 6). They
   are !important because Bootstrap's utilities are; there is no dark copy:
   the tokens switch under html.dark-mode, so .text-muted, .bg-light and the
   rest follow without per-theme rules. */
.text-primary   { color: var(--primary) !important; }
.text-secondary { color: var(--text-secondary) !important; }
.text-success   { color: var(--success) !important; }
.text-warning   { color: var(--warning) !important; }
/* a gold glyph is an icon, not text: icon-gold (3:1 icon floor in light, the
   brand gold in dark); words in warning stay --warning (PR 6 review M2) */
:is(i, svg, .fa, .fas, .far, .bi).text-warning { color: var(--icon-gold) !important; }
.text-danger    { color: var(--error) !important; }
.text-info      { color: var(--info) !important; }
.text-dark, .text-body, .text-black, .text-body-emphasis { color: var(--text) !important; }
.text-muted, .text-body-secondary { color: var(--text-secondary) !important; }
.text-body-tertiary { color: var(--text-tertiary) !important; }
.link-dark { color: var(--text) !important; }
.bg-light, .bg-body-tertiary, .bg-body-secondary { background-color: var(--surface-variant) !important; }
.bg-white, .bg-body { background-color: var(--surface) !important; }
.bg-dark { background-color: var(--navy) !important; }

html.dark-mode .btn-close:not(.btn-close-white) {
    filter: invert(1) grayscale(100%) brightness(200%);
}
/* .text-dark on a light fill (warning/info cards and badges) keeps dark ink
   in both themes; everywhere else it is the text token above. */
:is(.bg-warning, .bg-info, .text-bg-warning, .text-bg-info, .alert-warning).text-dark,
:is(.bg-warning, .bg-info, .text-bg-warning, .text-bg-info) .text-dark { color: var(--ink) !important; }
/* alert links take the alert's own text colour (bold), so they hold AA on
   every alert fill in both themes */
.alert { --bs-alert-link-color: currentColor; }
