/*
 * Custom Bootstrap 5.3 color modes - generated from the club palette.
 *   light = "Clear Pool"   ·   dark = "Deep Ocean"
 *
 * Bootstrap (precompiled) is imported before this file in assets/app.js, so we
 * retheme by overriding its CSS variables. Component colors that Bootstrap
 * bakes in at Sass-compile time (buttons, form controls, active pills) are
 * re-pointed at the theme variables further down.
 *
 * See https://getbootstrap.com/docs/5.3/customize/color-modes/#custom-color-modes
 */

/* ---- "Clear Pool" (light) -------------------------------------------- */
[data-bs-theme="light"] {
    --theme-name: "Clear Pool";
    color-scheme: light;

    --bs-body-bg: #FFFFFF;              /* surface */
    --bs-body-bg-rgb: 255, 255, 255;
    --bs-surface: #FFFFFF;
    --bs-surface-rgb: 255, 255, 255;

    --bs-tertiary-bg: #F4F7F9;          /* body-bg (page background) */
    --bs-tertiary-bg-rgb: 244, 247, 249;
    --bs-secondary-bg: #F4F7F9;
    --bs-secondary-bg-rgb: 244, 247, 249;

    --bs-body-color: #1A2B36;
    --bs-body-color-rgb: 26, 43, 54;
    --bs-emphasis-color: #1A2B36;
    --bs-emphasis-color-rgb: 26, 43, 54;

    /* text-secondary (.text-muted). One step darker than the original #627D93,
       which only reached 4.3:1 on the white surface and 4.0:1 on the #F4F7F9 page
       background - under WCAG AA 4.5:1, and this color carries meaningful text
       (times, e-mail addresses, "nog niet genoteerd") that is often `small`.
       #54718A clears AA on both surfaces (5.11:1 and 4.75:1); dark mode was already
       fine and is unchanged. */
    --bs-secondary-color: #54718A;
    --bs-secondary-color-rgb: 84, 113, 138;
    --bs-tertiary-color: rgba(84, 113, 138, 0.65);

    --bs-primary: #00A3E0;
    --bs-primary-rgb: 0, 163, 224;
    --bs-primary-text-emphasis: #006386;
    --bs-primary-bg-subtle: #cceefa;
    --bs-primary-border-subtle: #99ddf6;

    --bs-success: #2EC4B6;
    --bs-success-rgb: 46, 196, 182;
    /* Darker teal for success used as *text* on the light surface: the base
       #2EC4B6 only reaches ~2:1 on white, below the WCAG AA 4.5:1 for normal
       text. This variant clears AA; filled badges keep the base color. */
    --bs-success-text-emphasis: #147D70;

    /* Link text sits on the light surface. The base primary #00A3E0 only
       reaches ~2.9:1 on white, below WCAG AA 4.5:1 for normal text (same
       problem success has - see the note above). Use the darker text-safe
       primary (== --bs-primary-text-emphasis, ~6.7:1); the base color is kept
       as a *fill* on buttons/badges, which carry dark text instead. */
    --bs-link-color: #006386;
    --bs-link-color-rgb: 0, 99, 134;
    --bs-link-hover-color: #004d66;
    --bs-link-hover-color-rgb: 0, 77, 102;

    --bs-border-color: rgba(84, 113, 138, 0.22);
    --bs-border-color-translucent: rgba(26, 43, 54, 0.1);
}

/* ---- "Deep Ocean" (dark) --------------------------------------------- */
[data-bs-theme="dark"] {
    --theme-name: "Deep Ocean";
    color-scheme: dark;

    --bs-body-bg: #16222F;              /* surface */
    --bs-body-bg-rgb: 22, 34, 47;
    --bs-surface: #16222F;
    --bs-surface-rgb: 22, 34, 47;

    --bs-tertiary-bg: #0B131A;          /* body-bg (page background) */
    --bs-tertiary-bg-rgb: 11, 19, 26;
    --bs-secondary-bg: #0B131A;
    --bs-secondary-bg-rgb: 11, 19, 26;

    --bs-body-color: #E6F1F8;
    --bs-body-color-rgb: 230, 241, 248;
    --bs-emphasis-color: #FFFFFF;
    --bs-emphasis-color-rgb: 255, 255, 255;

    --bs-secondary-color: #94A9BA;      /* text-secondary (.text-muted) */
    --bs-secondary-color-rgb: 148, 169, 186;
    --bs-tertiary-color: rgba(148, 169, 186, 0.65);

    --bs-primary: #00D2FF;
    --bs-primary-rgb: 0, 210, 255;
    --bs-primary-text-emphasis: #80e9ff;
    --bs-primary-bg-subtle: #002a33;
    --bs-primary-border-subtle: #007e99;

    --bs-success: #00F5D4;
    --bs-success-rgb: 0, 245, 212;
    /* Lighter teal for success-as-text on the dark surface (see the light-mode
       note); the base is already bright enough for badges. */
    --bs-success-text-emphasis: #5FE0CE;

    --bs-link-color: #00D2FF;
    --bs-link-color-rgb: 0, 210, 255;
    --bs-link-hover-color: #66e3ff;
    --bs-link-hover-color-rgb: 102, 227, 255;

    --bs-border-color: rgba(148, 169, 186, 0.2);
    --bs-border-color-translucent: rgba(230, 241, 248, 0.12);
}

/* ---- Re-point baked-in component colors at the theme variables ------- */

/* Panels/cards/list-groups sit on the surface color. */
.card,
.list-group {
    --bs-card-bg: var(--bs-surface);
    --bs-list-group-bg: var(--bs-surface);
}

/* A card that is itself a link (e.g. the dashboard stat tiles): give it a
   hover affordance so it reads as clickable. */
a.card {
    color: inherit;
    transition: border-color .15s ease, box-shadow .15s ease, transform .15s ease;
}
a.card:hover {
    border-color: var(--bs-primary);
    box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .15);
    transform: translateY(-2px);
}

/* Solid primary buttons carry DARK text on the bright primary fill, mirroring
   .btn-success below. White on the base primary is only 2.9:1 (light #00A3E0)
   / 1.8:1 (dark #00D2FF) - well under AA; dark #001018 gives ~6.7:1 (light) /
   ~10.7:1 (dark). The hover/active states darken the fill only *gently*
   (90% / 82% instead of the usual 85% / 75%): the primary is a dark enough hue
   that the default 75% active darkening would drop even pure black below 4.5:1,
   so a lighter touch keeps every state's text pair >= AA. */
.btn-primary {
    --bs-btn-bg: var(--bs-primary);
    --bs-btn-border-color: var(--bs-primary);
    --bs-btn-hover-bg: color-mix(in srgb, var(--bs-primary) 90%, #000);
    --bs-btn-hover-border-color: color-mix(in srgb, var(--bs-primary) 85%, #000);
    --bs-btn-active-bg: color-mix(in srgb, var(--bs-primary) 82%, #000);
    --bs-btn-active-border-color: color-mix(in srgb, var(--bs-primary) 78%, #000);
    --bs-btn-disabled-bg: var(--bs-primary);
    --bs-btn-disabled-border-color: var(--bs-primary);
    --bs-btn-color: #001018;
    --bs-btn-hover-color: #001018;
    --bs-btn-active-color: #001018;
    --bs-btn-disabled-color: #001018;
}
.btn-outline-primary {
    /* Resting text sits on the surface, so it uses the text-safe primary
       (--bs-primary-text-emphasis, ~6.7:1 light / ~11.5:1 dark) rather than the
       raw primary (2.9:1 on white). The border keeps the brand primary; the
       filled hover/active states use dark text on the bright fill. */
    --bs-btn-color: var(--bs-primary-text-emphasis);
    --bs-btn-border-color: var(--bs-primary);
    --bs-btn-hover-bg: var(--bs-primary);
    --bs-btn-hover-border-color: var(--bs-primary);
    --bs-btn-hover-color: #001018;
    --bs-btn-active-bg: var(--bs-primary);
    --bs-btn-active-border-color: var(--bs-primary);
    --bs-btn-active-color: #001018;
}
/* Primary badges/toasts (.text-bg-primary) bake white text in at compile time;
   override to dark for AA on the bright primary fill (mirrors .text-bg-success). */
.text-bg-primary {
    color: #001018 !important;
}

.btn-success,
.text-bg-success {
    --bs-btn-bg: var(--bs-success);
    --bs-btn-border-color: var(--bs-success);
    --bs-btn-hover-bg: color-mix(in srgb, var(--bs-success) 85%, #000);
    --bs-btn-hover-border-color: color-mix(in srgb, var(--bs-success) 80%, #000);
    --bs-btn-active-bg: color-mix(in srgb, var(--bs-success) 75%, #000);
    --bs-btn-active-border-color: color-mix(in srgb, var(--bs-success) 70%, #000);
    --bs-btn-color: #00231f;
    --bs-btn-hover-color: #00231f;
    --bs-btn-active-color: #00231f;
}

.text-bg-success {
    color: #00231f !important;
}

/* Badges that are links (the trainer-load pills on the planning board) carry a
   colored fill, which says nothing about being clickable. The underline on hover
   and keyboard focus is that hint; the fill stays as it is, because its color
   carries meaning of its own (the load tone). Needs !important to beat the
   .text-decoration-none utility that keeps the resting state clean. */
a.badge:hover,
a.badge:focus-visible {
    text-decoration: underline !important;
}

/* The pill of the trainer the board is currently zoomed in on. The ring lands just
   outside the pill, so it has to contrast with the page and not with the fill:
   currentColor would be the dark-on-yellow text of a warning pill, invisible against
   the dark background it is actually drawn on. --bs-body-color is the one color
   guaranteed to read against the page in both themes. A box-shadow rather than an
   outline, so it never competes with the focus ring. */
a.badge[aria-current] {
    box-shadow: 0 0 0 2px var(--bs-body-color);
}

/* Flash toasts whose fill is light with dark text (warning, info, and - thanks to
   the overrides above - success and primary) mark themselves data-bs-theme="light"
   and use the plain dark close button. That attribute alone is not enough: in dark
   mode Bootstrap sets `[data-bs-theme=dark] .btn-close { filter: <invert> }` as a
   descendant rule on `filter` itself, not as a custom property a subtree can
   redefine, and ships no light-theme counter-rule. Without this the close button
   turns near-white on yellow - and since warning/error toasts no longer auto-hide,
   an invisible close button would leave them stuck on screen. */
.toast[data-bs-theme="light"] .btn-close {
    filter: none;
}

/* Grijze omlijnde knoppen. Bootstrap bakt hier gray-600 (#6c757d) in, voor élke
   staat, en die kleur weet niets van dit palet: als rustende tekst haalt hij 3,99:1
   op de donkere paginakleur (#0B131A) en 3,43:1 op het donkere oppervlak (#16222F) -
   onder de 4,5:1 die gewone tekst hoort te halen. Het raakt echte tekst: "Afwezigheid
   doorgeven" op /agenda, en de niet-gekozen helften van de schakelaars "Eén lijst /
   Per startblok" en "Voornaam / Achternaam" - juist de helft die je moet kunnen lezen
   om hem te kiezen.

   Zelfde aanpak als .btn-outline-primary/-success hierboven: de rustende tekst en de
   rand nemen de tekstveilige variant uit het thema (--bs-secondary-color, 7,70:1 op
   #0B131A en 6,63:1 op #16222F in donker, 5,11:1 op wit en 4,75:1 op #F4F7F9 in licht -
   in licht was #6c757d met 4,36:1 op de paginakleur trouwens ook net te laag),
   en de gevulde staten (hover, active, en de .active-helft van een btn-group) draaien
   het om naar het oppervlak - hetzelfde kleurenpaar, dus dezelfde verhouding. Wit op
   #94A9BA zou 2,43:1 zijn geweest, dus daar kan Bootstraps --bs-btn-hover-color niet
   blijven staan; #16222F erop is 6,63:1. */
.btn-outline-secondary {
    --bs-btn-color: var(--bs-secondary-color);
    --bs-btn-border-color: var(--bs-secondary-color);
    --bs-btn-hover-bg: var(--bs-secondary-color);
    --bs-btn-hover-border-color: var(--bs-secondary-color);
    --bs-btn-hover-color: var(--bs-surface);
    --bs-btn-active-bg: var(--bs-secondary-color);
    --bs-btn-active-border-color: var(--bs-secondary-color);
    --bs-btn-active-color: var(--bs-surface);
    --bs-btn-disabled-color: var(--bs-secondary-color);
    --bs-btn-disabled-border-color: var(--bs-secondary-color);
    --bs-btn-focus-shadow-rgb: var(--bs-secondary-color-rgb);
}

.btn-outline-success {
    /* Resting text/border sit on the surface, so they use the text-safe success
       (--bs-success-text-emphasis, ~5:1 light / ~10:1 dark) rather than the raw
       success (2.2:1 on white - see the note on --bs-success). The filled
       hover/active states keep the bright success fill with dark text. */
    --bs-btn-color: var(--bs-success-text-emphasis);
    --bs-btn-border-color: var(--bs-success-text-emphasis);
    --bs-btn-hover-bg: var(--bs-success);
    --bs-btn-hover-border-color: var(--bs-success);
    --bs-btn-hover-color: #00231f;
    --bs-btn-active-bg: var(--bs-success);
    --bs-btn-active-border-color: var(--bs-success);
    --bs-btn-active-color: #00231f;
}

/* Form controls & switches follow the theme primary. */
.form-check-input:checked {
    background-color: var(--bs-primary);
    border-color: var(--bs-primary);
}
.form-check-input:focus,
.form-control:focus,
.form-select:focus {
    border-color: color-mix(in srgb, var(--bs-primary) 50%, transparent);
    box-shadow: 0 0 0 0.25rem rgba(var(--bs-primary-rgb), 0.25);
}

/* Active admin sub-nav pills. */
.nav-pills {
    --bs-nav-pills-link-active-bg: var(--bs-primary);
}

/* Surface-colored navbar: theme-aware text instead of the old white-on-primary. */
.navbar {
    --bs-navbar-color: rgba(var(--bs-body-color-rgb), 0.7);
    /* Hover/active are nav *text* on the surface, so they take the text-safe
       primary (== --bs-link-color, ~6.7:1 light / ~8.9:1 dark) and not the raw
       brand primary, which only reaches ~2.9:1 on white - see the note on
       --bs-link-color. Without this the highlighted item would be the least
       legible one in the navbar. */
    --bs-navbar-hover-color: var(--bs-link-color);
    --bs-navbar-active-color: var(--bs-link-color);
    --bs-navbar-brand-color: var(--bs-body-color);
    --bs-navbar-brand-hover-color: var(--bs-body-color);
}
/* Bootstrap hardcodes .nav-link's focus ring to its own blue instead of reading a
   variable - off-palette, and in dark mode it sits on a near-black surface.
   Re-point it at the club primary for both themes. This covers the two <button>s
   in the navbar (theme toggle, uitloggen) too: they carry `nav-link` and nothing
   else on purpose. Adding `btn` to them would hand Bootstrap's
   `:not(.btn-check)+.btn:active:focus-visible` (0,4,0) the last word over this
   rule (0,3,0), and it sets box-shadow from --bs-btn-focus-shadow-rgb, which a
   bare `.btn` leaves undefined - the ring would simply disappear while the button
   is held down. The `.btn:active` colour rules beat `.nav-link:hover` the same
   way. .nav-link already does the <button> reset, so `btn` buys nothing. */
.navbar-nav .nav-link:focus-visible {
    box-shadow: 0 0 0 .25rem rgba(var(--bs-primary-rgb), .35);
    border-radius: var(--bs-border-radius);
}

/* The section row (Agenda / Training geven / Beheer ...) lives outside the
   collapse, so it stays on screen when the hamburger is shut - see the note in
   base.html.twig. Below md it takes the full width, which makes .navbar's
   flex-wrap drop it onto its own line under the logo; from md up it shrinks back
   to its content and sits inline next to the brand, as it always did. Bootstrap
   ships no responsive width utility, hence the two rules rather than a class in
   the template. */
.navbar-sections {
    width: 100%;
}
@media (max-width: 767.98px) {
    /* Two lines of text in a tile the width of a fifth of a phone: the labels are
       full words ("Training geven"), so they get the small size the rest of the
       app uses for secondary text rather than being shortened into jargon. */
    .navbar-sections .nav-link {
        font-size: .8125rem;
    }
}
@media (min-width: 768px) {
    .navbar-sections {
        width: auto;
    }
}

/* Second, non-color signal for "you are here": in light mode the active and
   resting colors are two dark tints that differ mainly in hue, so weight-not
   color alone-has to carry the distinction. The admin sub-navs need no
   equivalent; nav-pills/nav-underline already add a shape cue. */
.navbar-nav .nav-link.active {
    font-weight: 600;
}

/* ---- Planning board (collapsed weeks + focus filter) ------------------ */
/* Week collapse is owned by the `planning` Stimulus controller via the
   `is-collapsed` class, shared by the admin planning screen and the trainer
   board. Chevron points right when collapsed, rotates down when open.
   Styling deliberately stays on the class rather than moving to the header
   button's aria-expanded: the class sits on the week card, which is the element
   that must hide its body, while the aria state belongs on the button that
   controls it. setExpanded() writes both from one place so they cannot diverge. */
[data-planning-target="week"] .bi-chevron-right {
    /* Same reason as the rail's chevron below: without inline-block the rotate is
       silently dropped, which is why these chevrons never actually turned. */
    display: inline-block;
    transition: transform .15s ease;
}
[data-planning-target="week"]:not(.is-collapsed) .bi-chevron-right {
    transform: rotate(90deg);
}
[data-planning-target="week"].is-collapsed [data-week-body] {
    display: none;
}

/* `.fold` is a fold that keeps no state: a real <details>, no controller. Used by
   the trainer-load rail and by the trainerindeling of an afgelaste training, both
   on the planning board. It borrows the week cards' chevron and rotation so every
   fold on one screen reads as one gesture.

   The browser's own marker cannot do the job here: `display: flex` on a <summary>
   drops it outright (the rail's summary is a flex row), and where it does survive
   it is a different shape from every other fold on this board. Hence
   list-style: none and one icon rule for all of them. */
.fold > summary {
    cursor: pointer;
    list-style: none;
}

.fold > summary::-webkit-details-marker {
    display: none;
}

/* inline-block, or nothing happens: a transform on a non-replaced inline element
   is ignored outright, and bootstrap-icons only sets inline-block on the ::before
   glyph, not on the <i> the rule lands on. The computed style still reports the
   rotation, so this looks correct in devtools while the chevron sits still. */
.fold > summary .bi-chevron-right {
    display: inline-block;
    transition: transform .15s ease;
}

.fold[open] > summary .bi-chevron-right {
    transform: rotate(90deg);
}

/* The admin planning board folds a session's lanes into one block per group
   (feedback 2026-08-03, taak 4). Two groups training at the same time need a
   visible seam between their blocks; the first block needs none, hence the
   sibling selector instead of a rule on every block. */
[data-group-block] + [data-group-block] {
    border-top: 1px solid var(--bs-border-color);
    padding-top: .5rem;
    margin-top: .5rem;
}

/* Auto-submitting controls (the agenda aan-/afmelden switch, the per-lane
   trainer <select>) fire a Turbo request on change. Turbo sets aria-busy on the
   submitting form while it's in flight; dim it and block pointer input so a slow
   poolside tap can't double-submit before the morph lands. */
form[aria-busy="true"] {
    opacity: .65;
    pointer-events: none;
}

/* ---- Indeelbord (the planning grid) ---------------------------------- */
/* The planning as the club prints it: weeks across, (weekday, time, group) down.
   See docs/INDEELBORD_PLAN.md.

   The board scrolls inside its own frame, and the page never scrolls sideways:
   the week columns keep a minimum width, so more weeks than fit push the board
   aside rather than squeezing the columns to nothing. That frame is also why it
   has a height - a sticky header sticks to the scrollport it lives in, and with
   only overflow-x the scrollport would be the page, where `top: 0` would put the
   week numbers under the navbar. */
.board-scroll {
    overflow: auto;
    max-height: min(72vh, 46rem);
    border: 1px solid var(--bs-border-color);
    border-radius: var(--bs-border-radius);
    background: var(--bs-surface);
    overscroll-behavior-x: contain;
}

/* separate + zero spacing, not collapse: a collapsed table draws its borders on
   the table itself, and those do not travel with a sticky cell - the header row
   would scroll away from its own underline. Every rule below therefore hangs its
   border on the cell. */
table.board {
    border-collapse: separate;
    border-spacing: 0;
    width: 100%;
    margin: 0;
    font-size: .84rem;
    font-variant-numeric: tabular-nums;
}

/* background-COLOR, not the shorthand: the concept hatch below is a background
   image on the same cells, and a shorthand here would reset it away. */
table.board th,
table.board td {
    text-align: left;
    vertical-align: top;
    background-color: var(--bs-surface);
}

table.board thead th,
table.board td {
    min-width: 8.5rem;
}

table.board thead th {
    position: sticky;
    top: 0;
    z-index: 3;
    padding: .5rem .6rem;
    font-weight: 600;
    white-space: nowrap;
    border-bottom: 2px solid var(--bs-border-color);
}

table.board thead th .board-sub {
    display: block;
    font-weight: 400;
    font-size: .78rem;
    color: var(--bs-secondary-color);
}

/* The label column stays put while the weeks slide past it: without it a cell
   halfway down the board says four names and nothing about whose evening it is. */
table.board th.board-rowhead,
table.board thead th.board-corner,
table.board tr.board-day th {
    position: sticky;
    left: 0;
    z-index: 2;
    min-width: 12rem;
    width: 12rem;
    border-right: 2px solid var(--bs-border-color);
}

table.board thead th.board-corner {
    z-index: 4; /* crosses both sticky axes */
}

/* The day band. Subtle rather than the printed sheet's navy: on paper it separates
   the days of a season, here it also has to stay out of the way of the states in
   the cells, which are the only colour worth reading. */
table.board tr.board-day th,
table.board tr.board-day td {
    background: var(--bs-primary-bg-subtle);
    color: var(--bs-primary-text-emphasis);
    padding: .25rem .6rem;
    white-space: nowrap;
}

table.board tr.board-day th {
    font-weight: 700;
}

table.board th.board-rowhead {
    padding: .4rem .6rem;
    font-weight: 400;
    border-bottom: 1px solid var(--bs-border-color);
}

table.board th.board-rowhead .board-time {
    color: var(--bs-secondary-color);
    white-space: nowrap;
}

/* The group is the identity of the line, so it is the part set in bold - the same
   choice the printed sheet makes. */
table.board th.board-rowhead .board-group {
    display: block;
    font-weight: 600;
}

table.board th.board-rowhead .board-need {
    color: var(--bs-secondary-color);
    font-size: .76rem;
}

/* position: relative so the whole cell can be the click target: the link lies over
   it (Bootstrap's .stretched-link) rather than wrapping its contents, which keeps
   the names plain text and gives the link a name of its own. */
table.board td.board-cell {
    position: relative;
    padding: .35rem .55rem;
    border-bottom: 1px solid var(--bs-border-color);
    border-right: 1px solid var(--bs-border-color);
}

/* The cell reacts as one thing, keyboard and mouse alike - .stretched-link's own
   ::after carries the focus ring nowhere visible, so the cell wears it. */
table.board td.board-cell:hover,
table.board td.board-cell:focus-within {
    background-color: var(--bs-tertiary-bg);
}

table.board td.board-cell:focus-within {
    outline: 2px solid var(--bs-focus-ring-color, var(--bs-primary));
    outline-offset: -2px;
}

.board-cell-link:focus-visible {
    outline: none; /* the cell around it is the visible target */
}

.board-names {
    display: flex;
    flex-wrap: wrap;
    gap: .1rem .45rem;
}

.board-name {
    white-space: nowrap;
}

/* The one thing in a cell somebody has to act on. Same tone as the "N open" badge
   on the list board (text-bg-warning), without the pill: a cell of four names and
   a badge reads as five things. */
.board-open {
    color: var(--bs-warning-text-emphasis);
    font-weight: 700;
    white-space: nowrap;
}

.board-cancelled {
    font-style: italic;
    color: var(--bs-secondary-color);
}

.board-none {
    color: var(--bs-tertiary-color);
}

/* Concept: past the publication boundary, so nobody but the planner sees it. A
   hatch rather than a colour - it is not a state to act on, it is where the
   planning has got to.

   Fainter and wider apart than it first was (2026-08-15): the names in those cells
   are the reading, and stripes at full tertiary strength every 10px sat on top of
   them like a warning. Half strength on a 16px pitch still reads as "this side is
   not published" when you look at the board as a whole, which is the only distance
   from which the question is ever asked. The swatch in the legend keeps the tighter
   pitch, or a 1.6rem block would show a single stripe. */
table.board td.board-concept,
.board-swatch.board-concept {
    background-image: repeating-linear-gradient(
        135deg,
        transparent 0 15px,
        color-mix(in srgb, var(--bs-tertiary-color) 35%, transparent) 15px 16px
    );
}

.board-swatch.board-concept {
    background-image: repeating-linear-gradient(
        135deg,
        transparent 0 5px,
        color-mix(in srgb, var(--bs-tertiary-color) 35%, transparent) 5px 6px
    );
}

/* The boundary itself, as a line down the board on the week it falls in. */
table.board th.board-boundary,
table.board td.board-boundary {
    border-right: 3px solid var(--bs-primary);
}

.board-boundary-note {
    color: var(--bs-primary);
    font-weight: 700;
}

/* Status bar and legend markers. */
.board-dot,
.board-swatch {
    display: inline-block;
    width: .55rem;
    height: .55rem;
    border-radius: 50%;
    vertical-align: baseline;
}

.board-swatch {
    width: 1.6rem;
    height: .7rem;
    border-radius: 2px;
    border: 1px solid var(--bs-border-color);
}

.board-dot-open { background: var(--bs-warning); }
.board-dot-away { background: var(--bs-danger); }
.board-dot-swap { background: var(--bs-info); }

/* ---- Oplichten ---------------------------------------------------------- */
/* De opvolger van de focusknoppen van het lijstbord. Daar verdwenen weken uit
   beeld; hier blijft alles staan waar het staat, want op een raster is de plaats
   van een cel zelf informatie. Zie src/Schedule/BoardHighlight.php.

   De ring is naar binnen gelegd (outline-offset) zodat hij binnen de celranden
   blijft en de kolommen niet een pixel opschuiven, en de vulling is zwak genoeg
   om de namen erin gewoon leesbaar te houden - het gaat erom dat je de cel vindt
   als je over het bord kijkt, niet dat hij schreeuwt als je hem al gevonden hebt. */
table.board td.board-cell.board-cell-mark {
    outline: 2px solid var(--bs-primary);
    outline-offset: -2px;
    background-color: color-mix(in srgb, var(--bs-primary) 9%, var(--bs-surface));
}

/* De naam zelf, want een cel van vier namen die oplicht zegt nog niet welke.

   Het zachte paar en niet de volle clubkleur: die is in het donker een fel cyaan,
   waar witte letters op wegvallen. subtle/emphasis horen bij elkaar en houden in
   allebei de thema's hun contrast; de ring om de cel doet het vindwerk, dit zegt
   alleen wélke naam het was. */
.board-name-mark {
    background: var(--bs-primary-bg-subtle);
    color: var(--bs-primary-text-emphasis);
    border-radius: var(--bs-border-radius-sm);
    padding: 0 .25rem;
    font-weight: 700;
}

/* De tellingen in de statusbalk zijn ook de knoppen die ze aanwijzen: één ding, dus
   één element. Een link die niet als knop is opgemaakt maar wel als knop reageert -
   het getal blijft de hoofdzaak, de omlijsting komt pas bij hover en bij de aan-stand. */
.board-mark-toggle {
    display: inline-flex;
    align-items: baseline;
    gap: .3rem;
    padding: .15rem .45rem;
    border: 1px solid transparent;
    border-radius: var(--bs-border-radius-sm);
    color: inherit;
    text-decoration: none;
}

a.board-mark-toggle:hover {
    border-color: var(--bs-border-color);
    background: var(--bs-tertiary-bg);
}

.board-mark-toggle-on {
    border-color: var(--bs-primary);
    background: color-mix(in srgb, var(--bs-primary) 12%, transparent);
}

/* Nul: er valt niets aan te wijzen, dus het is geen knop. */
.board-mark-toggle-off {
    color: var(--bs-secondary-color);
}

/* Het capaciteitstekort. Geen oplicht-knop maar een deur naar de analyse, dus geen
   ring die aan en uit gaat - alleen de kleur van de melding zelf, want dit is het
   enige item in de balk dat over een probleem gaat in plaats van over werk. */
a.board-mark-short {
    color: var(--bs-danger-text-emphasis);
}

a.board-mark-short:hover {
    border-color: var(--bs-danger-border-subtle);
    background: var(--bs-danger-bg-subtle);
}

/* ---- Weekrooster als raster -------------------------------------------- */
/* De dagen naast elkaar, de tijd naar beneden, de tegels op ware hoogte
   (docs/WEEKROOSTER_PLAN.md). Hiervoor waren het zeven dagkaarten in drie kolommen,
   en dan staat woensdag 06:45 naast maandag 18:30.

   Twee schaalgetallen, en verder rekent App\Schedule\Week\WeekGrid: één rij is een
   kwartier, en de tijdkolom is precies breed genoeg voor "06:45". */
/* `--bs-surface` en niet de paginakleur, zoals de cellen van het indeelbord
   (`table.board th, table.board td`): dat bord is één lichte plaat op een donkere
   pagina, en dit raster stond ernaast als één donkere. Twee rasters in hetzelfde
   menu horen van hetzelfde materiaal te zijn (2026-08-18). De tegels erin zijn
   daarom de trapkleur, precies omgekeerd aan hoe het was. */
.rooster {
    --rooster-rij: 21px;
    --rooster-tijdkolom: 3.25rem;
    display: grid;
    grid-template-columns: var(--rooster-tijdkolom) repeat(7, minmax(0, 1fr));
    background: var(--bs-surface);
    border: 1px solid var(--bs-border-color);
    border-radius: var(--bs-border-radius);
    /* overflow: hidden zodat de afgeronde hoeken de bandkoppen afsnijden - die lopen
       van rand tot rand en zouden er anders buiten steken. */
    overflow: hidden;
}

.rooster-kop {
    padding: .4rem .6rem;
    font-weight: 600;
    font-size: .82rem;
    border-bottom: 1px solid var(--bs-border-color);
    border-left: 1px solid var(--bs-border-color);
}

.rooster-kop-hoek {
    border-left: 0;
}

/* Een dag waarop de club niet zwemt: het woord staat er, en verder niets. Geen
   banenstrip, geen kleur - "zo fancy hoeft het allemaal niet" (2026-08-18). */
.rooster-kop-leeg {
    color: var(--bs-secondary-color);
    font-weight: 500;
}

.rooster-kop-leeg small {
    display: block;
    font-weight: 400;
    font-size: .7rem;
}

/* Ochtend / Avond. Het gat ertussen is dichtgeklapt, dus deze regel is de plek waar
   acht uur niets gebeurt - vandaar dat hij zegt waar je weer bent.
   Dezelfde kleuren als de dagregel van het indeelbord (`table.board tr.board-day`),
   want het is dezelfde soort regel: de streep die zegt dat je aan iets nieuws begint. */
.rooster-bandkop {
    grid-column: 1 / -1;
    padding: .25rem .6rem;
    font-size: .7rem;
    text-transform: uppercase;
    letter-spacing: .05em;
    color: var(--bs-primary-text-emphasis);
    background: var(--bs-primary-bg-subtle);
    border-bottom: 1px solid var(--bs-border-color);
}

.rooster-bandkop + .rooster-band {
    border-bottom: 1px solid var(--bs-border-color);
}

/* Dezelfde kolommen als de kopregel, want een band is een eigen raster: subgrid is
   het nog niet overal, en de kolombreedtes staan hierboven als variabele. De rijen
   komen inline mee (`grid-template-rows`), want alleen de server weet hoe hoog deze
   band is - en de scheidingslijnen lopen tot `-1`, wat impliciete rijen niet kennen. */
.rooster-band {
    grid-column: 1 / -1;
    display: grid;
    grid-template-columns: var(--rooster-tijdkolom) repeat(7, minmax(0, 1fr));
}

/* Naar boven getrokken zodat de tijd op de lijn staat waar hij hoort en niet eronder. */
.rooster-tick {
    grid-column: 1;
    padding-right: .35rem;
    text-align: right;
    font-size: .66rem;
    color: var(--bs-secondary-color);
    transform: translateY(-.4em);
}

/* De dag doet op een breed scherm zelf niet mee aan de opmaak: zijn tegels liggen
   rechtstreeks in het raster van de band. Het kolomnummer erft naar binnen als
   custom property, wat de enige manier is om het één keer per dag te zeggen. */
.rooster-dag {
    display: contents;
}

.rooster-dag-kop {
    display: none;
}

.rooster-scheiding {
    grid-column: var(--rooster-kolom);
    border-left: 1px solid var(--bs-border-color);
}

/* Standaard één strook over de volle breedte; overlappende momenten krijgen er meer van
   het sjabloon mee (`--rooster-stroken`) en gaan dan naast elkaar staan in plaats van
   over elkaar heen. De percentages rekenen tegen de breedte van het rastervak, dus tegen
   de dagkolom - bij één strook komt dit uit op de `margin: 1px 3px` die er stond. */
.rooster-tegel {
    --rooster-strook: 0;
    --rooster-stroken: 1;
    /* Een tegel meet zichzelf, zodat het label erin op de breedte van de dagkolom kan
       reageren en niet op die van het venster - dat laatste klopt niet zodra de lade
       ernaast staat of de zijbalk anders is. */
    container: tegel / inline-size;
    grid-column: var(--rooster-kolom);
    justify-self: start;
    width: calc((100% - 6px) / var(--rooster-stroken));
    margin: 1px 0 1px calc(3px + (100% - 6px) * var(--rooster-strook) / var(--rooster-stroken));
    padding: .2rem .4rem .25rem;
    border: 1px solid var(--bs-border-color);
    border-radius: var(--bs-border-radius-sm);
    background: var(--bs-tertiary-bg);
    color: inherit;
    text-decoration: none;
    display: flex;
    flex-direction: column;
    gap: .05rem;
    /* Een tegel is zo hoog als de training lang is, dus een kwartiertje is 21px: wat
       er niet in past hoort niet buiten de tegel te gaan hangen, want daar staat de
       volgende. */
    overflow: hidden;
}

.rooster-tegel:hover,
.rooster-tegel:focus-visible {
    border-color: var(--bs-primary);
    background: color-mix(in srgb, var(--bs-primary) 8%, var(--bs-tertiary-bg));
    color: inherit;
}

.rooster-tegel-top {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: .3rem;
}

/* Op een laptop is een dagkolom ~118px, en dan vraagt `18:30-19:15` naast `4 blokken`
   er 124. Wat er dan wijkt is het woord en niet het getal, en niet een van de
   groepsregels: bij een gesplitst bad is de verdeling het nieuws, en een tegel van drie
   rijen heeft geen vierde regel om het aantal naar te laten zakken. Het getal blijft
   staan en het woord blijft voor een schermlezer bestaan - niets verhuist naar een
   `title`, want daar is het onbereikbaar ({@see HoverOnlyTooltipTest}). */
@container tegel (max-width: 8.5rem) {
    .rooster-blokken-woord {
        position: absolute;
        width: 1px;
        height: 1px;
        overflow: hidden;
        clip-path: inset(50%);
        white-space: nowrap;
    }
}

/* En deelt de tegel zijn kolom met een overlappend moment, dan is er ~84px en wijkt ook
   de eindtijd. Wat overblijft is de begintijd, en de hoogte van de tegel zegt de rest -
   dat is de belofte van een raster op ware hoogte. Ook hier: visueel weg, niet weg. */
@container tegel (max-width: 6rem) {
    .rooster-tijd-eind {
        position: absolute;
        width: 1px;
        height: 1px;
        overflow: hidden;
        clip-path: inset(50%);
        white-space: nowrap;
    }
}

.rooster-tijd {
    font-size: .72rem;
    font-weight: 600;
    white-space: nowrap;
}

.rooster-blokken {
    font-size: .66rem;
    color: var(--bs-secondary-color);
    white-space: nowrap;
}

/* Eén regel per groep, en afkappen in plaats van afbreken: een gesplitst bad heeft er
   twee, en een omgebroken eerste regel duwt de tweede groep uit een tegel van drie
   rijen. `Trimgroep 1 baan 1-...` afgekapt lezen is beter dan `Groep 4` kwijt zijn. */
.rooster-wie {
    font-size: .76rem;
    line-height: 1.25;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* De banen als bijzin, want ze zijn er alleen als er iets te verdelen valt. */
.rooster-wie i {
    font-style: normal;
    font-size: .7rem;
    color: var(--bs-secondary-color);
}

/* Vrijzwemmen: er ligt niemand ingedeeld, dus er valt niets aan te wijzen. */
.rooster-tegel-vrij .rooster-wie {
    color: var(--bs-secondary-color);
}

/* Het enige op dit raster dat over werk gaat dat er nog ligt. */
.rooster-wie-open {
    color: var(--bs-warning-text-emphasis);
}

/* De aanhaking voor opruimen: een stille regel onder het raster, met een streep erboven
   die hem van het rooster losmaakt. Bewust geen knop in de balk - het is een
   seizoensactie die de trainingen zelf weggooit, en een rode knop naast "Trainingen
   aanmaken" leest als de tegenhanger van iets veel kleiners. */
.rooster-opruimen {
    margin: 1.25rem 0 0;
    padding-top: .75rem;
    border-top: 1px solid var(--bs-border-color);
    font-size: .85rem;
}

.rooster-opruimen a {
    color: var(--bs-secondary-color);
}

/* Op een telefoon is een raster van zeven kolommen geen raster meer maar zeven
   streepjes, dus valt het terug op een dagstapel per band: de tijdas, de
   scheidingslijnen en de kopregel gaan eruit, en de dag zegt zijn eigen naam. Dezelfde
   tegels, dezelfde links - alleen de opmaak eromheen verandert.

   Een dag zonder tegels in deze band valt weg: op een raster is een lege kolom de
   mededeling "hier niets", in een stapel is een lege kop alleen ruimte. */
@media (max-width: 991.98px) {
    .rooster {
        display: block;
        border: 0;
        border-radius: 0;
        overflow: visible;
    }

    .rooster-kop,
    .rooster-tick,
    .rooster-scheiding {
        display: none;
    }

    .rooster-bandkop {
        padding: .25rem 0;
        background: none;
        border-bottom: 0;
    }

    .rooster-bandkop + .rooster-band {
        border-bottom: 0;
    }

    .rooster-band {
        display: block;
    }

    .rooster-dag {
        display: block;
        margin-bottom: .6rem;
    }

    .rooster-dag-leeg {
        display: none;
    }

    .rooster-dag-kop {
        display: block;
        font-size: .82rem;
        font-weight: 600;
        margin: 0 0 .2rem;
    }

    .rooster-tegel {
        margin: 0 0 .25rem;
        padding: .35rem .5rem .4rem;
        overflow: visible;
    }

    .rooster-tijd,
    .rooster-wie {
        font-size: .85rem;
    }

    .rooster-blokken {
        font-size: .72rem;
    }
}

/* ---- Zijpaneel van het indeelbord ------------------------------------- */
/* Één training, naast het bord. Een Turbo Frame, en een leeg frame is een dicht
   paneel: vandaar `:empty` in plaats van een klasse die iemand moet aan- en
   uitzetten. Het bord eronder blijft staan waar het stond.

   Vast aan de rechterkant en niet in de bladstroom, omdat het bord al breder is dan
   het scherm: een paneel dat ernaast komt te staan zou het bord smaller maken en dus
   verschuiven onder de hand van de planner. */
.board-panel:empty {
    display: none;
}

.board-panel {
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    z-index: 1045; /* boven de sticky koppen van het bord, onder een modal (1055) */
    width: min(26rem, 100%);
    display: flex;
    flex-direction: column;
    background: var(--bs-body-bg);
    /* Geen slagschaduw eronder (2026-08-15): de lade ligt op het bord en dat is aan
       zijn eigen vlak al te zien. De rand is de scheiding, en die is daarom iets
       steviger dan de lijnen ín het bord - anders loopt hij weg tussen de kolommen. */
    border-left: 2px solid var(--bs-secondary-color);
}

/* Als hele pagina (geen JavaScript, of een training rechtstreeks geopend) is het
   geen paneel maar gewoon een kaart: er is niets om overheen te schuiven. Dezelfde
   inhoud, dus een eigen omhulsel in plaats van `.board-panel` terugdraaien. */
.board-panel-page {
    max-width: 32rem;
    border: 1px solid var(--bs-border-color);
    border-radius: var(--bs-border-radius);
}

.board-panel-head {
    display: flex;
    align-items: start;
    justify-content: space-between;
    gap: .75rem;
    padding: .75rem 1rem;
    border-bottom: 1px solid var(--bs-border-color);
}

/* Dezelfde link, twee woorden: in de lade doe je hem dicht, als hele pagina ga je
   terug. Welke het is kan alleen het scherm weten - het is één adres, en het bord
   haalt precies die pagina op om hem naast zich te zetten - dus staan ze er allebei
   en kiest de CSS. */
.board-panel .board-panel-back,
.board-panel-page .board-panel-close {
    display: none;
}

/* min-height: 0 so the body is what scrolls in the fixed panel and not the panel
   itself - a flex item's floor is its content, and without this a long training
   pushes the footer off the bottom of the screen. Ignored on the page, where the
   panel has no height to fill. */
.board-panel-body {
    flex: 1 1 auto;
    min-height: 0;
    padding: .75rem 1rem;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    gap: 1rem;
}

.board-panel-foot {
    margin-top: auto;
    display: flex;
    flex-wrap: wrap;
    gap: .5rem;
    padding: .75rem 1rem;
    border-top: 1px solid var(--bs-border-color);
}

/* De regel over de periode: de weken naast elkaar, maar dan onder elkaar. */
.board-panel-line {
    border-top: 1px solid var(--bs-border-color);
    padding-top: .75rem;
}

/* De regels waar automatisch indelen zich aan houdt. Bovenaan de lade en in een eigen
   vlak, want het is het enige blok dat niets vraagt: het antwoordt op "wat gaat dit ding
   straks doen" (2026-08-16). Stond hiervoor als grijze slotzin onder de knop, waar je
   het pas las nadat je besloten had. */
.indeel-regels {
    padding: .6rem .75rem;
    border-radius: var(--bs-border-radius);
    background: var(--bs-tertiary-bg);
}

/* Week voor week, en elke week een deur naar die week (2026-08-15). Datum en namen
   in twee kolommen, zodat de data onder elkaar staan en het oog langs de namen kan
   lopen in plaats van langs de inspringing. */
.board-line-entry {
    border-radius: var(--bs-border-radius-sm);
}

.board-line-link,
.board-line-current {
    display: flex;
    gap: .5rem;
    padding: .15rem .35rem;
    color: inherit;
    text-decoration: none;
    border-radius: var(--bs-border-radius-sm);
}

.board-line-link:hover {
    background: var(--bs-tertiary-bg);
    color: inherit;
}

.board-line-date {
    flex: 0 0 3.6rem;
    color: var(--bs-secondary-color);
    white-space: nowrap;
}

.board-line-who {
    flex: 1 1 auto;
}

/* Waar je staat: een gevulde regel, en verder niets. De vulling doet het werk - een
   streep of een schaduw ernaast is een tweede manier om hetzelfde te zeggen, en het
   woord "hier" was een derde (2026-08-15). */
.board-line-current {
    background: var(--bs-primary-bg-subtle);
    color: var(--bs-primary-text-emphasis);
    font-weight: 600;
}

.board-line-current .board-line-date {
    color: inherit;
}

/* ---- Tom Select (searchable account picker on the member form) -------- */
/* Tom Select's bootstrap5 skin is imported in assets/app.js, but that skin is
   light-only: it hardcodes white/near-black instead of reading the Bootstrap
   color-mode variables, so in dark mode the dropdown would be white-on-white.
   Same job as the button/badge overrides above - hand a third-party component
   the theme variables so it follows the club palette in both modes. */
.ts-control,
.ts-control input,
.ts-control > .item,
.ts-dropdown,
.ts-dropdown .option {
    color: var(--bs-body-color);
}
.ts-control,
.ts-dropdown {
    background-color: var(--bs-body-bg);
    border-color: var(--bs-border-color);
}
.ts-control input::placeholder {
    color: var(--bs-secondary-color);
}
/* Keyboard/hover-highlighted row. */
.ts-dropdown .active {
    background-color: var(--bs-primary-bg-subtle);
    color: var(--bs-emphasis-color);
}
/* The already-chosen row: a subtle tint, not a second focus state. */
.ts-dropdown .option.selected {
    background-color: var(--bs-tertiary-bg);
}
.ts-wrapper.focus .ts-control {
    border-color: var(--bs-primary);
    box-shadow: 0 0 0 .25rem rgba(var(--bs-primary-rgb), .25);
}
/* While the dropdown is open (i.e. searching), hide the chosen item so the
   search box is empty instead of showing the selection with a caret after it.
   The item reappears once the dropdown closes. */
.ts-wrapper.single.dropdown-active .ts-control > .item {
    display: none;
}

/* ---- Custom utilities ------------------------------------------------- */
/* Bootstrap ships no min-width:0 utility; flex children need it so that
   `text-truncate` actually shrinks instead of overflowing (e.g. long swimmer
   names beside the aan-/afmelden toggle on a narrow phone). */
.min-w-0 {
    min-width: 0;
}

/* Poolside target size, phones only. Bootstrap's default .btn is 38px high, which
   is a small thing to hit with wet hands, one-handed, at the bad - and the actions
   this is on commit you to an evening (oppakken, overnemen, ruilen, een lijst
   invullen). 44px is the smallest comfortable target (WCAG 2.5.5 AAA, iOS HIG), and
   full width because a phone has one column anyway.
   Above the sm breakpoint both utilities do nothing: the same buttons stretched over
   a 1280px card read as a wall of colour, and a mouse does not need 44px. Also
   deliberately a utility rather than a blanket rule on .btn - the small buttons in
   dense admin tables are mouse work and would only grow the rows. */
@media (max-width: 575.98px) {
    .btn-touch {
        min-height: 44px;
        width: 100%;
    }
    .btn-group-touch {
        width: 100%;
    }
    .btn-group-touch > .btn {
        min-height: 48px;
    }
    /* The same 44px for an icon-only action that shares its line with other buttons -
       the "notering wissen" undo beside Aanwezig/Afwezig on the attendance list. Full
       width is wrong there (it would push those two off the row), so this squares the
       target instead: the icon stays small and muted, only the box around it grows.
       That does not make it the button you hit by accident - what keeps it out of the
       thumb's path is the gap beside it, that it only exists on a row that already has
       a status, and the confirmation it asks for. */
    .btn-touch-icon {
        min-height: 44px;
        min-width: 44px;
    }
}

/* The wordmark is black line art on transparent - invert it in dark mode so it
   stays visible on dark surfaces (the navbar and the landing hero). */
.navbar-logo {
    width: auto;
}
.hero-logo {
    width: 100%;
    max-width: 360px;
    height: auto;
}
[data-bs-theme="dark"] .navbar-logo,
[data-bs-theme="dark"] .hero-logo {
    filter: invert(1);
}

/* De statusregel boven het weekrooster leest als de statusbalk van het indeelbord, dus
   heeft hij dezelfde kleur: die balk is een `card-footer` en staat op de kaartkleur met
   de 3%-tint van Bootstrap erover. Hier staat de regel los op de pagina, dus wordt die
   som hier uitgeschreven (2026-08-18). Niet `bg-body-tertiary`: dat is de paginatrap en
   dan zijn twee balken in hetzelfde menu van ander materiaal. */
.rooster-statusbalk {
    background-color: color-mix(in srgb, var(--bs-body-color) 3%, var(--bs-surface));
}

/* De Aanwezig/Afwezig-knoppen op de lijst (browsertest 2026-09-24, K9). Bootstrap vult
   een outline-knop bij hover helemaal, en dan ziet een knop waar alleen de muis of een
   vinger boven hangt eruit als "aanwezig" - op een aanraakscherm blijft die hover na
   een tik zelfs staan. Met een muis wordt hover nu een lichte tint, op een aanraakscherm
   verandert er niets. Alleen de outline-knoppen: een gekozen knop moet gevuld blijven. */
.attendance-choice .btn-outline-success {
    --bs-btn-hover-bg: var(--bs-success-bg-subtle);
    --bs-btn-hover-color: var(--bs-success-text-emphasis);
    --bs-btn-hover-border-color: var(--bs-success);
}
.attendance-choice .btn-outline-danger {
    --bs-btn-hover-bg: var(--bs-danger-bg-subtle);
    --bs-btn-hover-color: var(--bs-danger-text-emphasis);
    --bs-btn-hover-border-color: var(--bs-danger);
}
@media (hover: none) {
    .attendance-choice .btn-outline-success,
    .attendance-choice .btn-outline-danger {
        --bs-btn-hover-bg: transparent;
        --bs-btn-hover-color: var(--bs-btn-color);
        --bs-btn-hover-border-color: var(--bs-btn-border-color);
    }
}
