/*
 * The palette is NOT here any more - it is in theme-tokens.css, which the
 * site loads too. This file held its own copy that claimed to match the site
 * and had not for months: five of its colours were the ones the site replaced
 * for failing contrast. See theme-tokens.css for the values and the reasons.
 */

/*
 * Theme-aware overrides for the plain Bootstrap primitives used on the
 * admin panel's own pages (card, table, form controls, badges, dropdowns).
 * The bundled bootstrap.css hardcodes light-mode colors for these, so
 * without this file they stay white/black regardless of the .dark-theme
 * toggle. Everything here maps to the same CSS custom properties the
 * baseline template already flips in .dark-theme (see style.css ~L5453).
 */

.card {
    background: var(--White);
    color: var(--Body-Text);
    border-color: var(--Stroke);
}

.card-header {
    background: transparent;
    border-bottom-color: var(--Stroke);
    color: var(--Heading);
}

/*
 * Bootstrap's .progress/.progress-bar hardcode light-mode colors (near-
 * white track, plain blue fill) same as .table/.card did – never themed
 * before. Also gives the track a real border so it's visibly a bounded
 * container even at very low fill percentages (a long-running job like the
 * year backfill spends most of its time under 5% – an unbordered near-
 * white track blended into a light card made the whole thing look like it
 * wasn't there at all, not just "a thin bar").
 */
.progress {
    background-color: var(--table-head-bg, rgba(125, 125, 125, 0.12));
    border: 1px solid var(--Stroke);
}
/* The brand blue the buttons use, not a hardcoded indigo of its own. */
.progress-bar {
    background-color: var(--Main);
    min-width: 2px;
}

/*
 * Readability pass: the bootstrap defaults these were riding on give every
 * table barely-visible row borders, no zebra striping or hover feedback,
 * and cramped default padding – hard to scan at a glance, especially once
 * a table runs to 20-100 rows. This is a single shared file, so every
 * admin table picks these up automatically.
 */
/*
 * Text size/weight here is deliberately matched to the public tennis site's
 * own real table (.yoy-table in tennis/public/css/style.css – td 14px, th
 * 12px uppercase, 8-10px padding) rather than picked independently, so the
 * two apps' tables actually read the same rather than each having their own
 * guess at "readable". Both already share the same font stack (Inter).
 */
.table {
    color: var(--Body-Text);
    --bs-table-color: var(--Body-Text);
    --bs-table-striped-color: var(--Body-Text);
    --bs-table-striped-bg: var(--table-stripe-bg, rgba(125, 125, 125, 0.06));
    --bs-table-hover-color: var(--Body-Text);
    --bs-table-hover-bg: var(--table-hover-bg, rgba(61, 90, 254, 0.08));
    font-size: 14px;
}

.table > :not(caption) > * > * {
    border-bottom-color: var(--Stroke);
    color: var(--Body-Text);
    padding: 10px;
    vertical-align: middle;
}

.table thead th {
    color: var(--Note);
    font-size: 12px;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    font-weight: 700;
    background: var(--table-head-bg, rgba(125, 125, 125, 0.08));
    border-bottom: 2px solid var(--Stroke);
    white-space: nowrap;
}

/* Sort affordance: a faint always-visible glyph on every sortable header
   (set by admin-tables.js), swapping to a solid arrow once that column is
   the active sort – at 0.5 opacity and blank-until-clicked, the previous
   version was too subtle to register as "this is clickable". */
.admin-table-sort-indicator {
    margin-left: 6px;
    font-size: 10px;
    opacity: 0.35;
}
.admin-table-sort-indicator.active {
    opacity: 1;
}

.table thead th.admin-table-sortable:hover {
    background: var(--table-head-hover-bg, rgba(61, 90, 254, 0.12));
}

/* Per-column filter row admin-tables.js inserts under the header labels. */
.admin-table-filter-row th {
    background: var(--table-head-bg, rgba(125, 125, 125, 0.08));
    padding: 6px 8px !important;
    border-bottom: 2px solid var(--Stroke);
}
.admin-table-filter-row input {
    width: 100%;
    box-sizing: border-box;
    font-size: 12px;
    font-weight: 400;
    text-transform: none;
    letter-spacing: normal;
    padding: 4px 8px;
    border: 1px solid var(--Stroke);
    border-radius: 4px;
    background: var(--White);
    color: var(--Body-Text);
}

.table tbody tr {
    background: var(--bs-table-striped-bg, transparent);
}

.table tbody tr:nth-of-type(odd) {
    background: transparent;
}

.table tbody tr:hover {
    background: var(--bs-table-hover-bg);
}

.text-muted {
    color: var(--Note) !important;
}

.form-control,
.form-select {
    background-color: var(--White);
    color: var(--Body-Text);
    border-color: var(--Stroke);
}

.form-control::placeholder {
    color: var(--Note);
    opacity: 1;
}

.form-control:focus,
.form-select:focus {
    background-color: var(--White);
    color: var(--Body-Text);
}

.dropdown-menu {
    background-color: var(--White);
    color: var(--Body-Text);
}

.dropdown-menu li,
.dropdown-menu .body-text {
    color: var(--Body-Text);
}

/*
 * Collapsed-sidebar logo: the sidebar's own logo slides off-screen when the
 * sidebar is collapsed (.layout-wrap.full-width), so show it (full size, same
 * as the sidebar's own logo – the collapse toggle sits to its right, having
 * swapped sides with it) in the header instead. Kept permanently `display:
 * flex` (with !important, since style.css's `.header-dashboard .wrap
 * .header-left > a { display: none }` outguns a plain class selector on
 * specificity) and animated via max-width/opacity instead, so the reveal can
 * transition smoothly rather than snapping with `display`, which can't be
 * animated.
 */
.header-collapsed-logo {
    display: -webkit-box !important;
    display: -moz-box !important;
    display: -ms-flexbox !important;
    display: -webkit-flex !important;
    display: flex !important;
    align-items: center;
    overflow: hidden;
    max-width: 0;
    opacity: 0;
    margin-right: 0;
    transition:
        max-width 0.35s cubic-bezier(0.4, 0, 0.2, 1),
        margin-right 0.35s cubic-bezier(0.4, 0, 0.2, 1),
        opacity 0.2s ease;
}

.layout-wrap.full-width .header-collapsed-logo {
    max-width: 160px;
    opacity: 1;
    margin-right: 15px;
    transition:
        max-width 0.35s cubic-bezier(0.4, 0, 0.2, 1),
        margin-right 0.35s cubic-bezier(0.4, 0, 0.2, 1),
        opacity 0.3s ease 0.1s;
}

.header-collapsed-logo img {
    height: 52px;
    width: 154px;
}

/* Open system alerts, badged on the sidebar's Cron / Pipeline link. Amber for
   warnings only; red as soon as any open alert is an error. */
.sidebar-alert-badge {
    display: inline-block;
    min-width: 18px;
    margin-left: 6px;
    padding: 1px 6px;
    border-radius: 999px;
    font-size: 11px;
    font-weight: 800;
    line-height: 16px;
    text-align: center;
    color: #fff;
    /* The solid fills, not the readable inks: white sits on top of these and
       the dark theme's amber (#F0AE2F) measured 1.9:1 under it. */
    background: #8F5A1D;
}
.sidebar-alert-badge.is-error {
    background: var(--siq-bad-solid);
}
:root { --compare-strong: var(--siq-accent); }

/* ---- Shared with the site: bell, notifications page, message centre -----
 * Copied from tennis/public/css/site.css so the panel's header and its
 * Notifications / Messages pages match the site's. Keep the two in step. */
.panel-comms { display: grid; gap: 16px; }
.panel-comms .stadium-style-title { font-size: 18px; font-weight: 800; color: var(--Heading); }
.panel-comms .stadium-style-title span { color: var(--Heading); }
.mf-note { font-size: 13px; padding: 8px 12px; border-radius: 8px; }
.mf-note.is-ok { color: var(--siq-good); background: rgba(47, 163, 107, .1); }
.mf-note.is-error { color: var(--siq-bad); background: rgba(229, 72, 77, .1); }

/* ---- Header: notifications + messages ----------------------------------- */
.hdr-badge[hidden] { display: none !important; }
.hdr-dropdown { width: min(380px, 92vw); max-height: 70vh; overflow-y: auto; }
.hdr-items { display: block; }
.hdr-empty { font-size: 12px; color: var(--Note); padding: 8px 4px; }
.hdr-noti { display: flex; gap: 10px; align-items: flex-start; padding: 8px 6px; border-radius: 8px; text-decoration: none; color: var(--Body-Text); }
.hdr-noti:hover { background: rgba(78, 168, 255, .07); }
.hdr-noti.is-unread { background: rgba(78, 168, 255, .06); }
.hdr-noti.is-unread .hdr-noti-title::before { content: ''; display: inline-block; width: 7px; height: 7px; margin-right: 6px; border-radius: 50%; background: var(--siq-accent); vertical-align: middle; }
.hdr-noti-icon { flex: 0 0 28px; height: 28px; border-radius: 50%; display: flex; align-items: center; justify-content: center; background: var(--Stroke); color: var(--Heading); font-size: 13px; }
.hdr-noti.is-warning .hdr-noti-icon { background: rgba(209, 133, 43, .18); color: var(--siq-warn); }
.hdr-noti.is-live .hdr-noti-icon { background: rgba(229, 72, 77, .15); color: var(--siq-bad); }
.hdr-noti.is-upcoming .hdr-noti-icon { background: rgba(78, 168, 255, .15); color: var(--siq-accent); }
.hdr-noti.is-filter .hdr-noti-icon { background: rgba(155, 107, 255, .15); color: var(--siq-accent-alt); }
.hdr-noti.is-good .hdr-noti-icon { background: rgba(47, 163, 107, .15); color: var(--siq-good); }
.hdr-noti-main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.hdr-noti-title { font-size: 13px; font-weight: 700; color: var(--Heading); }
.hdr-noti-body { font-size: 12px; color: var(--Body-Text); margin-top: 2px; }
.hdr-noti-time { flex-shrink: 0; font-size: 11px; color: var(--Note); }
/* A short ring on the bell when a new notification lands. */
@keyframes hdr-ring { 0%, 100% { transform: rotate(0); } 20% { transform: rotate(-14deg); } 40% { transform: rotate(12deg); } 60% { transform: rotate(-8deg); } 80% { transform: rotate(5deg); } }
.hdr-ring .icon-bell { display: inline-block; animation: hdr-ring .9s ease-in-out 2; }


/* ---- Notifications page -------------------------------------------------- */
.notif-list { display: flex; flex-direction: column; gap: 8px; }
.notif-row { display: flex; gap: 12px; align-items: flex-start; padding: 12px; border: 1px solid var(--Stroke); border-radius: 10px; text-decoration: none; color: var(--Body-Text); }
.notif-row:hover { border-color: var(--compare-strong); }
.notif-row.is-unread { border-left: 3px solid var(--siq-accent); background: rgba(78, 168, 255, .04); }
.notif-icon { flex: 0 0 34px; height: 34px; border-radius: 50%; display: flex; align-items: center; justify-content: center; background: var(--Stroke); color: var(--Heading); }
.notif-row.is-warning .notif-icon { background: rgba(209, 133, 43, .18); color: var(--siq-warn); }
.notif-row.is-live .notif-icon { background: rgba(229, 72, 77, .15); color: var(--siq-bad); }
.notif-row.is-upcoming .notif-icon { background: rgba(78, 168, 255, .15); color: var(--siq-accent); }
.notif-row.is-filter .notif-icon { background: rgba(155, 107, 255, .15); color: var(--siq-accent-alt); }
.notif-row.is-good .notif-icon { background: rgba(47, 163, 107, .15); color: var(--siq-good); }
.notif-main { display: flex; flex-direction: column; min-width: 0; }
.notif-title { font-weight: 700; color: var(--Heading); font-size: 14px; }
.notif-body { font-size: 13px; margin-top: 2px; }
.notif-meta { font-size: 11px; color: var(--Note); margin-top: 4px; }


/* ---- Messages page ------------------------------------------------------- */
.msg-layout { display: grid; grid-template-columns: minmax(220px, 300px) minmax(0, 1fr); gap: 16px; }
.msg-list { display: flex; flex-direction: column; gap: 6px; }
.msg-conv { display: flex; flex-direction: column; gap: 2px; padding: 10px 12px; border: 1px solid var(--Stroke); border-radius: 10px; text-decoration: none; color: var(--Body-Text); }
.msg-conv:hover, .msg-conv.is-active { border-color: var(--compare-strong); }
.msg-conv.is-unread .msg-conv-with { font-weight: 800; }
.msg-conv-top { display: flex; justify-content: space-between; gap: 6px; }
.msg-conv-with { color: var(--Heading); font-size: 13px; font-weight: 600; }
.msg-conv-subject { font-size: 12px; color: var(--Heading); }
.msg-conv-snippet { font-size: 12px; color: var(--Note); }
.msg-badge { font-size: 10px; font-weight: 800; color: #fff; background: var(--siq-accent-solid); border-radius: 999px; padding: 1px 7px; }
.msg-pane { border: 1px solid var(--Stroke); border-radius: 10px; padding: 14px; min-width: 0; }
.msg-thread-subject { font-weight: 800; color: var(--Heading); margin-bottom: 10px; }
.msg-thread { display: flex; flex-direction: column; gap: 10px; }
.msg-bubble { max-width: 80%; padding: 10px 12px; border-radius: 12px; background: var(--Stroke); color: var(--Body-Text); align-self: flex-start; }
.msg-bubble.is-mine { align-self: flex-end; background: rgba(78, 168, 255, .15); }
.msg-bubble-meta { font-size: 11px; color: var(--Note); margin-bottom: 4px; }
.msg-bubble-body { font-size: 14px; overflow-wrap: anywhere; }
.msg-form { display: flex; flex-direction: column; gap: 10px; }
.msg-form label { display: flex; flex-direction: column; gap: 4px; font-size: 13px; color: var(--Body-Text); }
.msg-form .tf-button { align-self: flex-start; }
.msg-list-heading { font-size: 11px; font-weight: 800; text-transform: uppercase; letter-spacing: .05em; color: var(--Note); margin: 4px 0 2px; }
.msg-broadcast { border-style: dashed; cursor: default; }
.msg-check { flex-direction: row !important; align-items: center; gap: 8px !important; }
.msg-check[hidden] { display: none !important; }


@media (max-width: 767px) {
    .msg-layout { grid-template-columns: minmax(0, 1fr); }
    .msg-bubble { max-width: 92%; }
}

/* Header on tablets and phones: bring back the profile, messages and
   notification icons. The template hides .user, .message and .noti (with the
   theme toggle, maximise and apps) at 991px and below, so none of the three
   were reachable on a phone. Same selector as the template's rule, placed
   later, so it wins without !important. The other items stay hidden to keep
   the bar from crowding. */
@media (max-width: 991px) {
    .layout-wrap .section-content-right .header-dashboard .wrap .header-grid > .user,
    .layout-wrap .section-content-right .header-dashboard .wrap .header-grid > .message,
    .layout-wrap .section-content-right .header-dashboard .wrap .header-grid > .noti {
        display: block;
    }
    .layout-wrap .section-content-right .header-dashboard .wrap .header-grid {
        gap: 10px;
    }
    /* Picture only: the name and role beside it would crowd a phone's bar.
       (The template hides this only when it is a div; here it is a span.) */
    .header-dashboard .popup-wrap.user .header-user > .flex-column,
    .header-dashboard .popup-wrap.user .header-user > div:last-child {
        display: none;
    }
    /* Keep a dropdown inside the screen: anchored to the right edge of the
       viewport rather than to its (now small) icon. */
    .header-dashboard .popup-wrap .dropdown-menu.hdr-dropdown,
    .header-dashboard .popup-wrap.user .dropdown-menu {
        position: fixed !important;
        top: 64px !important;
        right: 8px !important;
        left: auto !important;
        transform: none !important;
        width: min(380px, calc(100vw - 16px));
        max-height: calc(100vh - 80px);
        overflow-y: auto;
    }
}
@media (max-width: 575px) {
    .layout-wrap .section-content-right .header-dashboard .wrap .header-grid .header-item {
        width: 36px;
        height: 36px;
    }
}

/* ── The panel wears its own badge ──
   The logo was the theme vendor's bitmap wordmark, which is why the admin tab
   looked like a different product from the site it administers. The lockup is
   now markup, so the text follows the theme rather than needing a second image
   for dark mode, and the box is pinned to the 81px the layout reserves for it
   (the same overhang that pushed the site's menu across its own divider). */
.layout-wrap .section-menu-left > .box-logo { min-height: 81px; box-sizing: border-box; }
.panel-logo { display: inline-flex; align-items: center; gap: 10px; text-decoration: none; }
.panel-logo img { flex: 0 0 auto; }
/*
 * The wordmark and ADMIN beneath it, matching the site's lockup exactly -
 * same sizes, same accent, same stack. Text rather than image so the hyphen
 * in Sports-IQ is spelt and the colour follows the theme.
 */
.panel-logo-lockup { display: inline-flex; flex-direction: column; justify-content: center; min-width: 0; gap: 2px; }
.panel-logo-mark {
    font-size: 20px; font-weight: 800; line-height: 1;
    letter-spacing: -0.2px; color: var(--Heading); white-space: nowrap;
}
/* The mark's green is #3CD2A5, which sits at 9.3 on the dark sidebar but
   only 1.9 on a white one - under even the large-text bar. Dark mode keeps
   the brand green; light mode takes a deeper one of the same hue (4.1). */
.panel-logo-iq { color: #0E8F6B; }
.dark-theme .panel-logo-iq { color: #3CD2A5; }
/* --Heading is a muted blue-grey in dark mode, which is right for a
   heading and wrong for a wordmark; the mark is white, as it is drawn. */
.dark-theme .panel-logo-mark { color: #FFFFFF; }
.dark-theme .panel-logo-text { color: var(--Note); }
.panel-logo-text {
    font-size: 11px; font-weight: 700; line-height: 1.1;
    letter-spacing: .18em; text-transform: uppercase;
    /* Pinned rather than taking --Note, which measures 4.13 on white and is
   a decision still open elsewhere; a wordmark should not wait on it. */
    color: #5B6270; white-space: nowrap;
}

/* ── The user menu's avatar ──
   The same circle the site draws, because it is the same person out of the
   same users table: their photo when they have one, their initials when they
   do not. AvatarHelper writes the colour inline, derived from the name, so
   one person keeps the same circle on both sides of the product. */
.siq-avatar {
    width: 40px;
    height: 40px;
    border-radius: 50%;
    object-fit: cover;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    color: #FFFFFF;
    font-weight: 700;
    font-size: 15px;
    line-height: 1;
    letter-spacing: .02em;
    text-transform: uppercase;
    user-select: none;
}


/* =======================================================================
   THE PANEL WEARS THE SITE'S CLOTHES
   =======================================================================
   The palette was shared months ago (theme-tokens.css) but the SHAPES were
   not, so the two halves of the product still did not look like one thing.
   The site draws every panel as `.wg-box.stadium-style` - a 20px radius, a
   1px Stroke border, 24px of padding and an italic uppercase heading. The
   admin panel draws its 300-odd panels as plain Bootstrap `.card`, which is
   a square-cornered white block with a 16px pad and a small bold label.
   Beside each other they read as two products.

   Rather than rewrite the markup of 26 pages (and fight the dashboard, which
   is being rewritten alongside this), the mapping is done here: `.card`
   becomes the site's card, `.card-header` its heading, `.card-body` its body.
   Every page inherits it, including ones written after this.

   Everything below is the site's own value, taken from tennis/public/css -
   `.wg-box` and `.wg-box.stadium-style` (style.css ~L1204, ~L7305), the
   heading rules in site.css ~L4419, and the control shapes from
   `.players-search-input` / `.players-tab` (style.css ~L7893).
   ----------------------------------------------------------------------- */

/* ── The page's own heading ──
   The site has no separate page title: the card's heading IS the heading, and
   it is italic uppercase. The panel does need one (an admin page is a place,
   not a card), so it takes the same voice. */
.main-content-inner > .tf-section-2 > h3 {
    font-style: italic;
    text-transform: uppercase;
    letter-spacing: 0.5px;
}

/* ── .card is .wg-box.stadium-style ── */
.card {
    border-radius: 20px;
    border: 1px solid var(--Stroke);
    background: var(--White);
    box-shadow: 0px 4px 24px 2px rgba(20, 25, 38, 0.05);
}

/* The site's card is a flex column with `gap: 24px` between the heading and
   the body. Bootstrap's card is not, so the same 24px is built out of the
   header's missing bottom pad and the body's top pad - one gap, measured the
   same, rather than two paddings that happen to meet. */
.card > .card-header {
    padding: 24px 24px 0;
    border-bottom: 0;
    background: transparent;
    /* A header that carries a form or a link beside its title still lines
       them up on the baseline of a 22px heading. */
    min-height: 0;
}
.card > .card-body {
    padding: 24px;
}
/* A body straight under a header supplies the gap; on its own it is the
   card's own 24px pad. */
.card > .card-header + .card-body {
    padding-top: 24px;
}
/* Bootstrap's own utilities still win where a page asked for something else
   (.p-0 on a table-filling body, .pt-0 under a header) - they carry
   !important, so nothing here needs to make room for them. */

/* ── The card's heading is .stadium-style-title ──
   Same size, same italic, same uppercase, and the same 1.25 line box the site
   settled on after finding a 22px heading sitting in a 79px one. Scoped to a
   heading's usual places in this markup so a <strong> inside a header's form
   or badge is left alone. */
.card > .card-header > strong,
.card > .card-header > span > strong,
.card > .card-header > div > strong,
.card > .card-header > h2,
.card > .card-header > h3,
.card > .card-header > h4 {
    display: inline-block;
    font-size: 22px;
    font-weight: 700;
    font-style: italic;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    line-height: 1.25;
    margin: 0;
    color: var(--Heading);
}
/* The site colours the emphasised word of a title with the accent. Where a
   panel heading has one, the same <span> does the same thing here - but only
   a bare <span>, because a heading also carries things like the Cron page's
   count bubble, which has its own colours and had them overruled (white on
   red became accent blue on red, 1.27:1). */
.card > .card-header strong > span:not([class]),
.stadium-style-title span:not([class]) {
    color: var(--siq-accent);
}

/* ── Buttons are the site's button ──
   .tf-button is 12px radius, 700 weight, --Main. Bootstrap's .btn is 6px and
   400, in colours that are Bootstrap's rather than ours - the "success"
   green in particular was #198754, which is not a colour this product owns.

   Written as plain properties, not --bs-btn-* : the bundled Bootstrap here is
   5.1, which paints .btn-primary and friends directly. The variable API only
   arrived in 5.3, so a block of --bs-btn-bg would have set nothing at all. */
.btn {
    border-radius: 12px;
    font-weight: 700;
    font-size: 14px;
    transition: all 0.3s ease;
}
.btn-sm {
    border-radius: 8px;
    font-size: 13px;
}
/* The same darkened blue the site settled on for .tf-button: --Main under
   white text is 4.18:1. */
.btn-primary {
    color: #FFFFFF;
    background-color: #1266F0;
    border-color: #1266F0;
}
.btn-primary:hover,
.btn-primary:focus,
.btn-primary:active,
.btn-check:checked + .btn-primary {
    color: #FFFFFF;
    background-color: var(--siq-accent-solid);
    border-color: var(--siq-accent-solid);
}
/* Outline and secondary buttons are the site's `.tf-button.style-2`: the
   card's own surface, a Stroke border, body ink - not Bootstrap's grey, which
   on the dark card was a grey border round grey text. */
.btn-secondary,
.btn-outline-secondary {
    color: var(--Body-Text);
    background-color: var(--White);
    border-color: var(--Stroke);
}
.btn-secondary:hover,
.btn-secondary:focus,
.btn-secondary:active,
.btn-outline-secondary:hover,
.btn-outline-secondary:focus,
.btn-outline-secondary:active {
    color: #FFFFFF;
    background-color: #1266F0;
    border-color: #1266F0;
}
.btn-outline-primary {
    color: var(--siq-accent-solid);
    border-color: var(--siq-accent-solid);
}
.dark-theme .btn-outline-primary {
    color: var(--siq-accent);
    border-color: var(--siq-accent);
}
.btn-outline-primary:hover,
.btn-outline-primary:focus,
.btn-outline-primary:active {
    color: #FFFFFF;
    background-color: var(--siq-accent-solid);
    border-color: var(--siq-accent-solid);
}
/* The solid tokens, not the readable ones: white text sits on these, and the
   pale dark-mode inks are far too light under it. */
.btn-danger,
.btn-danger:hover,
.btn-danger:focus,
.btn-danger:active,
.btn-outline-danger:hover,
.btn-outline-danger:focus,
.btn-outline-danger:active {
    color: #FFFFFF;
    background-color: var(--siq-bad-solid);
    border-color: var(--siq-bad-solid);
}
.btn-outline-danger {
    color: var(--siq-bad);
    border-color: var(--siq-bad);
}
.btn-success,
.btn-success:hover,
.btn-success:focus,
.btn-success:active {
    color: #FFFFFF;
    background-color: var(--siq-good-solid);
    border-color: var(--siq-good-solid);
}

/* ── Form controls are the site's ──
   8px radius on a Stroke border over the card's surface, and the same focus
   ring the site's account fields use. Bootstrap's focus is its own blue with
   a 4px halo; ours is the accent. */
.form-control,
.form-select {
    border-radius: 8px;
    font-size: 14px;
}
/* style.css paints `form input[type="number"]` (and text, date, email and the
   rest) `color: var(--Heading); background: transparent`. That is one class
   plus one element - more specific than `.form-control` - so a typed value
   sat in the muted heading grey rather than body ink: #94A3B8 on the dark
   card, 4.05:1, under AA. Two classes here take it back. */
.main-content-inner .form-control,
.main-content-inner .form-select,
.main-content-inner .form-control:focus,
.main-content-inner .form-select:focus {
    color: var(--Body-Text);
    background-color: var(--White);
}
.form-control:focus,
.form-select:focus {
    border-color: var(--siq-accent);
    box-shadow: 0 0 0 3px rgba(0, 117, 229, 0.18);
}
/* One focus ring for everything else that takes focus, so tabbing through a
   page never loses the cursor - the site's own (site.css, .acc-segment and
   friends). */
.main-content-inner a:focus-visible,
.main-content-inner button:focus-visible,
.main-content-inner summary:focus-visible,
.main-content-inner [tabindex]:focus-visible {
    outline: 2px solid var(--siq-accent);
    outline-offset: 2px;
}

/* ── Links inside a page are the accent ──
   Bootstrap's #0d6efd is a fixed blue that knows nothing about the dark card,
   where it measured 3.0:1.

   Only a bare <a>. A link that carries a class has been given a look of its
   own - an active tab with a filled background, a map's attribution, a chip -
   and painting those too turned a white tab label accent-blue on accent-blue
   (1.17:1). The rule is for prose links, which is all it was ever for.

   The solid variant in light mode rather than the readable one: --siq-accent
   is 4.49:1 on a white card and 4.16:1 on the slightly tinted panels some
   pages use, which is under AA. The solid is 5.24:1. Dark mode keeps the
   readable ink, because the solid is far too dark on a dark card. */
.main-content-inner a:not([class]) {
    color: var(--siq-accent-solid);
}
.dark-theme .main-content-inner a:not([class]) {
    color: var(--siq-accent);
}

/* ── ATP and WTA are a pill, exactly as on the site ──
   Lifted from site.css ~L4382 so a tour label is the same object on both
   sides of the product. The tours keep their own colours, and they never
   share one. */
.stadium-tour-badge,
.tour-badge,
.tourn-b-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 2px 9px;
    border-radius: 999px;
    font-size: 11px;
    font-weight: 700;
    line-height: 1.5;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: #FFFFFF !important;
    white-space: nowrap;
    vertical-align: middle;
}
.stadium-tour-badge.tour-atp,
.tour-badge.tour-atp,
.tourn-b-badge.tour-atp { background: var(--tour-atp); }
.stadium-tour-badge.tour-wta,
.tour-badge.tour-wta,
.tourn-b-badge.tour-wta { background: var(--tour-wta); }
.dark-theme .stadium-tour-badge.tour-wta,
.dark-theme .tour-badge.tour-wta,
.dark-theme .tourn-b-badge.tour-wta { background: rgba(147, 51, 234, .8); }

/* ── Badges are the same pill ──
   Bootstrap's .badge is a 6px-radius rectangle in Bootstrap's palette. Same
   shape as the tour pill, in our own outcome colours, on the solid variants
   so the white text passes in both themes. */
.badge {
    border-radius: 999px;
    padding: 2px 9px;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    line-height: 1.5;
}
.badge.bg-success  { background-color: var(--siq-good-solid) !important; }
.badge.bg-danger   { background-color: var(--siq-bad-solid) !important; }
/* The warn token under white text measures 4.52:1 - AA, but with nothing
   to spare on an 11px pill. One step darker, and the same in both themes
   for the same reason the other solid fills are. */
.badge.bg-warning  { background-color: #8F5A1D !important; color: #FFFFFF !important; }
.badge.bg-primary  { background-color: var(--siq-accent-solid) !important; }
.badge.bg-info     { background-color: var(--siq-accent-solid) !important; }
.badge.bg-secondary {
    background-color: transparent !important;
    color: var(--Note) !important;
    box-shadow: inset 0 0 0 1px var(--Stroke);
}

/* ── Alerts ──
   Bootstrap's alerts are fixed pastel rectangles: on the dark card the text
   and the background are both light, and the colours are not ours either.
   These are the SITE's tone pairs - the ones site.css already uses for a
   tinted notification (`.sig-tone-red` and friends), light and dark, measured
   at 6.0:1 or better in both. A token ink over a 10% tint of itself measured
   3.9:1 and was not good enough. Plain properties again, for the 5.1
   Bootstrap this panel bundles. */
.alert {
    border-radius: 12px;
    border: 1px solid transparent;
    font-size: 14px;
}
.alert-success { color: #1D6742; background-color: #E4F4EC; border-color: rgba(29, 103, 66, 0.22); }
.alert-danger  { color: #A3181C; background-color: #FDECEC; border-color: rgba(163, 24, 28, 0.22); }
.alert-warning { color: #7A4E0E; background-color: #FBF0DC; border-color: rgba(122, 78, 14, 0.22); }
.alert-info,
.alert-primary { color: #0B4F9E; background-color: #E8F1FC; border-color: rgba(11, 79, 158, 0.22); }
.alert-secondary { color: var(--Body-Text); background-color: var(--Stroke); border-color: var(--Stroke); }
/* An alert carries its own ink, so anything bold or linked inside it has to
   follow rather than keep the page's colour. */
.alert strong,
.alert b,
.alert a { color: inherit; }

/* Dark mode is the site's dark pair for the same tone. */
.dark-theme .alert-success { color: #6FD3A2; background-color: #1E3A2D; border-color: rgba(111, 211, 162, 0.25); }
.dark-theme .alert-danger  { color: #F0999C; background-color: #3B2A2C; border-color: rgba(240, 153, 156, 0.25); }
.dark-theme .alert-warning { color: #F0C570; background-color: #39301C; border-color: rgba(240, 197, 112, 0.25); }
.dark-theme .alert-info,
.dark-theme .alert-primary { color: #8FCBFF; background-color: #1B2E44; border-color: rgba(143, 203, 255, 0.25); }

/* ── Nothing here yet ──
   Every page said it differently - a grey sentence pinned to the top-left of
   a card, or a table row with one muted cell in the first column. Same words,
   same ink, now the same shape: centred in the space the card already has,
   which is what the site does with an empty section. Matched on what those
   two shapes already are, rather than by adding a class to twenty-five
   pages; .panel-empty names it for anything written after this. */
.panel-empty,
.card > .card-body > p.text-muted:only-child,
.table td.text-muted[colspan] {
    padding: 28px 16px;
    text-align: center;
    color: var(--Note);
    font-size: 14px;
}

/* ── Tables keep their own width on a phone ──
   A .table-responsive already scrolls, but a table that is not in one pushed
   the whole page sideways. Nothing on an admin page needs the document to
   scroll horizontally. */
@media (max-width: 767px) {
    /* No blanket overflow:hidden on the page - that would clip a wide table
       rather than let it scroll, and hide the data. The table scrolls
       instead, inside the card. */
    .main-content-inner .table { display: block; overflow-x: auto; }
    .card > .card-header,
    .card > .card-body { padding-left: 16px; padding-right: 16px; }
    .card > .card-header > strong,
    .card > .card-header > span > strong,
    .card > .card-header > div > strong { font-size: 18px; }
    .main-content-inner > .tf-section-2 > h3 { font-size: 22px; }
}

/* ── Preformatted blocks ──
   The theme's `pre` is a fixed #f5f5f5 with a fixed border and no colour of
   its own, so in dark mode it was white body text on a near-white block: the
   crontab listing on the Cron page could not be read at all. Themed, and
   given the card's own corner radius where it fills one edge to edge. */
.main-content-inner pre,
.main-content-inner code {
    background: var(--Stroke);
    color: var(--Body-Text);
    border-color: var(--Stroke);
    border-radius: 10px;
    padding: 12px 14px;
}
.main-content-inner code { padding: 1px 6px; }
/* A log window sets its own colours; only its corners need to follow the
   card's, which is now 20px rather than 12. */
.card > .card-body.p-0 > pre {
    border-radius: 0 0 19px 19px;
}

/* ── The list inside a <select> ──
   style.css paints an <option> `color: var(--Heading); background:
   var(--backdrop)`. --backdrop is #F2F7FB and the dark theme never restates
   it, so in dark mode the list stayed a near-white panel while --Heading
   dropped to the muted #94A3B8: 2.38:1, well under AA, on every dropdown in
   the panel. Body ink on the card's surface instead, which is the pairing
   the rest of a form already uses - 7.0:1 light, 14.6:1 dark.
   (The list is drawn by the operating system, so this is the one thing on
   the page a screenshot cannot show.) */
select option,
.form-select option {
    color: var(--Body-Text);
    background-color: var(--White);
}

/* ── The site's own contrast corrections, which the panel never got ──
   site.css carries a short list of one-off fills it had to darken to clear
   4.5:1. The panel uses the same components out of the same style.css and
   had none of them: the primary button measured 4.18:1 under its white
   label, and the header's count bubble the same. Same values as the site,
   for the same reason. */
.tf-button { background: #1266F0; }
/* The outline variant writes --Main as ink on the card instead, which is the
   same 4.18:1 the other way round. */
.tf-button.style-1 { color: var(--siq-accent-solid); }
.tf-button.style-1:hover { color: #FFFFFF; background-color: #1266F0; }
.header-dashboard .hdr-badge { background: #D54400 !important; }

/* ── Outcome ink ──
   --siq-good / --siq-bad / --siq-warn are chosen to read on a card, and in
   light mode they land at 4.4-4.5:1 - right on the line, and under it for
   anything that is not large. These are the site's tone inks (the same pairs
   the alerts use), for a word of running text that has to be green or red. */
.panel-ink-good { color: #1D6742; }
.panel-ink-bad  { color: #A3181C; }
.panel-ink-warn { color: #7A4E0E; }
.dark-theme .panel-ink-good { color: #6FD3A2; }
.dark-theme .panel-ink-bad  { color: #F0999C; }
.dark-theme .panel-ink-warn { color: #F0C570; }

/* ── The last of Bootstrap's own greys and blues ──
   .form-text is #6c757d and .nav-link #0d6efd, both fixed: on the dark card
   they measured 3.12:1 and 3.25:1. The tokens know which theme they are in. */
.form-text {
    color: var(--Note);
    /*
     * Bootstrap sizes help text at .875em, and several of the theme's
     * containers set a 10px font - so it compounded down to 8.75px with a
     * line-height to match, which is smaller than anything on the page is
     * meant to be and sat tight against whatever followed it. Fixed in px so
     * it cannot inherit its way back down again.
     */
    font-size: 12px;
    line-height: 1.5;
    margin-top: 4px;
    margin-bottom: 0;
}
label,
.form-check,
.form-check-label {
    line-height: 1.45;
}
/* A checkbox row that is shorter than its own tick ends up overlapping
   whatever comes next, which is what the theme's 10px line-height did. */
.form-check {
    min-height: 1.5rem;
    margin-bottom: 8px;
}
.nav-link {
    color: var(--siq-accent-solid);
}
.dark-theme .nav-link {
    color: var(--siq-accent);
}
.nav-link.active,
.nav-tabs .nav-link.active {
    color: var(--Heading);
    background-color: var(--White);
    border-color: var(--Stroke) var(--Stroke) var(--White);
}

/* ── Sidebar groups as dropdowns ─────────────────────────────────────────
   Each heading is a button that folds its links away. Styled to read as the
   theme's own .center-heading (same size, weight and colour) with a chevron,
   so the menu looks the same as before until a group is folded. */
.layout-wrap .section-menu-left .center .sidebar-group-toggle {
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 100%;
    background: none;
    border: 0;
    padding: 6px 14px;
    margin-bottom: 4px;
    border-radius: 8px;
    cursor: pointer;
    text-align: left;
}
.layout-wrap .section-menu-left .center .sidebar-group-toggle:hover,
.layout-wrap .section-menu-left .center .sidebar-group-toggle:focus-visible {
    background: rgba(127, 127, 127, .10);
    color: var(--Main);
}
.sidebar-group-meta { display: inline-flex; align-items: center; gap: 6px; }
.sidebar-group-chevron { font-size: 14px; transition: transform .2s ease; }
.sidebar-group-toggle[aria-expanded="true"] .sidebar-group-chevron { transform: rotate(180deg); }
/* The heading's own badge only matters while its links are out of sight. */
.sidebar-group-toggle[aria-expanded="true"] .sidebar-group-badge { display: none; }
.layout-wrap .section-menu-left .center .sidebar-group { margin-bottom: 10px; }
.layout-wrap .section-menu-left .center .menu-list[hidden] { display: none; }
