/* ============================================================
   The original's look
   ============================================================

   Everything here is taken from `elbesistem\index.html` -- the site this one
   replaces -- and every value is the original's own, not an approximation.
   Where a rule quotes a figure, the figure came out of that file.

   Loaded by both shells, so a rule written once applies to staff and member
   screens alike. It is deliberately separate from adminlayout.css and
   theme-base.css: those are the starter template's, and keeping this apart
   means what is *ours* and what is *the original's* never gets tangled.

   Contrast is stated wherever a colour is chosen, measured against the surface
   it actually sits on.
   ============================================================ */


/* ===== 1. Typography ========================================

   The original sets Fraunces on every heading and Inter on body text. Having
   no display face at all was the single biggest reason the rebuild did not
   look like it -- see PROJECT-NOTES section 11.

   `--elbe-serif` rather than naming Fraunces at each use, so one line changes
   it everywhere if the school ever wants a different face. */

/*  The type faces and the shapes live here. The COLOURS do not -- they are in
    theme-base.css, one set per theme, because a colour that is written down
    once cannot follow a theme. See the note at the top of that file. */
:root {
    --elbe-serif: 'Fraunces', Georgia, 'Times New Roman', serif;
    --elbe-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;

    /* Corner radius, the original's. */
    --elbe-r: 14px;
}

h1, h2, h3, h4, h5, h6,
.elbe-serif {
    font-family: var(--elbe-serif);
    letter-spacing: -0.5px;
}

/* Fraunces carries its weight differently from Inter: 600 in Fraunces reads
   about as heavy as 700 in a sans, so headings are set at the original's 600
   rather than the template's bolder default. */
h1, h2, h3, h4, h5, h6 {
    font-weight: 600;
}


/* ===== 2. The page header ===================================

   Every screen in the original opens with the same block -- `head()` at
   index.html:

       function head(eb, title, sub, actions)

   an uppercase brass eyebrow, a serif title, a subtitle, and the actions
   pushed to the right. The rebuild had a plain h4 and a muted paragraph.

   Rendered by Views/Shared/_PageHead.cshtml. */

.page-head {
    display: flex;
    justify-content: space-between;
    align-items: flex-end;
    gap: 20px;
    margin-bottom: 16px;
    flex-wrap: wrap;
}

.page-head .eyebrow {
    font-size: 11px;
    letter-spacing: 3px;
    text-transform: uppercase;
    color: var(--elbe-eyebrow);
    font-weight: 600;
    margin-bottom: 8px;
}

.page-head .page-title {
    font-family: var(--elbe-serif);
    font-size: 32px;
    font-weight: 600;
    line-height: 1;
    letter-spacing: -0.5px;
    margin: 0;
}

.page-head .page-sub {
    color: var(--elbe-ivory-dim);
    margin: 8px 0 0;
    font-size: 14px;
    max-width: 600px;
    line-height: 1.55;
}

.page-head .page-actions {
    display: flex;
    gap: 9px;
    flex-wrap: wrap;
}

/* On a narrow screen the actions drop under the title rather than squeezing
   it. The original hides its top bar entirely on mobile; this is the same
   intent without rebuilding the shell. */
@media (max-width: 640px) {
    .page-head {
        align-items: flex-start;
    }

    .page-head .page-title {
        font-size: 26px;
    }
}


/* ===== 3. Avatars ===========================================

   The original draws a person's initials on a coloured tile in every list, and
   takes the colour from their id -- `colorFor(id)` -- so the same person is the
   same colour everywhere.

   Note the shape: the original's `.avatar` is a **rounded square at 9px**, not
   a circle, and it sets the initials in Fraunces. Both are copied here; the
   rest of this rule is the original's own declaration verbatim.

   The palette lives in Models/Avatar.cs, which also records why white
   lettering on it is left as the original has it. */

.elbe-avatar {
    width: 34px;
    height: 34px;
    flex: 0 0 34px;
    border-radius: 9px;
    display: inline-grid;
    place-items: center;
    font-family: var(--elbe-serif);
    font-weight: 700;
    font-size: 13px;
    color: #fff;
    flex-shrink: 0;
    user-select: none;
}

.elbe-avatar.lg { width: 46px; height: 46px; flex-basis: 46px; font-size: 17px; border-radius: 12px; }
.elbe-avatar.sm { width: 26px; height: 26px; flex-basis: 26px; font-size: 11px; border-radius: 7px; }

/* A person's name beside their circle, which is how every list in the
   original presents somebody. */
.elbe-person {
    display: flex;
    align-items: center;
    gap: 10px;
    min-width: 0;
}

.elbe-person .nm {
    font-weight: 600;
    line-height: 1.25;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.elbe-person .sub {
    font-size: 12px;
    color: var(--elbe-ivory-dim);
    line-height: 1.3;
}


/* ===== 4. Tags ==============================================

   The original names four semantic colours and uses them on small tags. The
   rebuild used Bootstrap's, which are a different palette entirely.

   Each is set as pale-background-with-dark-text rather than the reverse, which
   is what the original does and what keeps a small tag readable. */

.elbe-tag {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 2px 9px;
    border-radius: 999px;
    font-size: 11.5px;
    font-weight: 600;
    line-height: 1.7;
    white-space: nowrap;
}

/*  Each tag is its own colour as lettering over a wash of the same colour, so
    one line in theme-base.css moves both. This replaced twelve rules -- six
    light values and six dark ones written out again under `body.theme-brass`,
    which left the other five dark themes on the light set.

    The wash is mixed from the colour rather than stated, so it lands on
    whatever card the theme puts behind it. */

.elbe-tag.brass  { color: var(--elbe-brass-text);  background: color-mix(in srgb, var(--elbe-brass-text) 15%, transparent); }
.elbe-tag.felt   { color: var(--elbe-felt-text);   background: color-mix(in srgb, var(--elbe-felt-text) 15%, transparent); }
.elbe-tag.wine   { color: var(--elbe-wine-text);   background: color-mix(in srgb, var(--elbe-wine-text) 15%, transparent); }
.elbe-tag.sky    { color: var(--elbe-sky-text);    background: color-mix(in srgb, var(--elbe-sky-text) 15%, transparent); }
.elbe-tag.violet { color: var(--elbe-violet-text); background: color-mix(in srgb, var(--elbe-violet-text) 15%, transparent); }
.elbe-tag.muted  { color: var(--elbe-ivory-dim);   background: color-mix(in srgb, var(--elbe-ivory-dim) 13%, transparent); }


/* ===== 5. Surfaces and depth ================================

   What gives the original its warmth.

   This was written twice: once under `body.theme-elbe` and once under
   `body.theme-brass`, on the reasoning that the other themes had palettes of
   their own and a brass glow would look wrong on them. The consequence was
   that they got no glow, no card gradient, no sidebar and no button — a rule
   naming two themes fires on neither of the other six, so they fell through to
   the starter template and the school's look stopped at the two.

   The shape is stated once now and the colour comes from the theme, through
   the composites at the foot of theme-base.css. Every theme gets the warmth in
   its own accent, and adding a ninth means writing a palette, not a
   stylesheet. */

/* The page carries three faint brass glows, not a flat colour. The first two
   are the original's; the third sits low and centre and is what stops a long
   page going dead at the bottom. */
body {
    background:
        radial-gradient(1100px 680px at 84% -12%, var(--theme-wash-16), transparent 56%),
        radial-gradient(820px 560px at -8% 6%, var(--theme-wash-06), transparent 54%),
        radial-gradient(720px 640px at 50% 122%, var(--theme-wash-06), transparent 60%),
        var(--bs-body-bg);
    background-attachment: fixed;
}

/* A card is a soft vertical gradient with a warm shadow, not a flat white box
   with a grey one. `--r` is the original's 14px. */
.card,
.admin-card {
    background: var(--theme-card-grad);
    border: 1px solid var(--elbe-line);
    border-radius: var(--elbe-r);
    box-shadow: var(--elbe-shadow);
}

/* Site settings used to build its eight sections as `card bg-dark text-light`,
   from before this theme existed, and the theme took the lettering back with a
   rule scoped to `.content-area .card.bg-dark`.

   That worked while it was only ever read on the school's two themes. It could
   not survive being read on eight: `.bg-dark` paints #212529 with `!important`
   and wins over the card gradient, so on a light theme the card went dark and
   the rule then wrote the LIGHT theme's ink onto it — 1.11:1, forty-one
   unreadable pieces of text on one page.

   The view no longer asks for either class, so those cards are ordinary cards
   and follow the theme like every other card on the site. Nothing here has to
   know about them. The sign-in screens still use `bg-dark`, and still should:
   they are genuinely dark and are not themed. */

/* A card header wants separating from the body it sits on. */
.card > .card-header {
    background: var(--theme-wash-06);
    border-bottom: 1px solid var(--elbe-line);
    font-weight: 600;
    color: var(--theme-text);
}

/* The original pads a card 19px where the template pads it 24px. Small on its
   own, repeated on every card on every screen, and it is what makes the
   rebuild read as roomier than the site it replaces even with both browsers at
   the same scale.

   `.admin-card` only. Bootstrap's `.card` keeps its padding on `.card-body`,
   so padding it here would double up on every one of those. */
.admin-card {
    padding: 19px;
}

/* The primary button is a brass gradient, which is the original's most
   recognisable single element.

   Dark lettering on it, not white: #1C1814 on #FFC200 is 10.9:1, where white
   would be 1.6:1 and unreadable. */
.btn-admin-primary,
.btn-primary {
    background: var(--theme-accent-grad);
    border: none;
    color: var(--theme-on-brass);
    font-weight: 600;
    border-radius: 10px;
    box-shadow: 0 2px 10px var(--theme-wash-22);
}

.btn-admin-primary:hover,
.btn-primary:hover {
    background: var(--theme-accent-grad-hover);
    color: var(--theme-on-brass);
    box-shadow: 0 6px 18px var(--theme-wash-22);
}

/* Disabled still has the accent behind it, so it still needs the ink measured
   for that fill. Bootstrap leaves --bs-btn-disabled-color unset on a button
   that is not one of its own variants, and the colour fell through to the
   page's -- light lettering on a bright fill, 1.16:1 on Okyanus. */
.btn-admin-primary:disabled,
.btn-admin-primary.disabled,
.btn-primary:disabled,
.btn-primary.disabled {
    color: var(--theme-on-brass);
    background: var(--theme-accent-grad);
}

.btn-admin-primary:focus-visible,
.btn-primary:focus-visible {
    outline: 2px solid var(--theme-accent);
    outline-offset: 2px;
}

/* The figure tiles on the dashboard. The original marks each with a brass bar
   down its left edge rather than a coloured icon square. */
.stat-card {
    border-left: 4px solid var(--elbe-brass);
    border-radius: var(--elbe-r);
}

/* Tables sit on the card, so they should not paint their own white over the
   card's gradient. */
.table {
    --bs-table-bg: transparent;
}

.table > thead th {
    background: var(--theme-hover-bg);
    border-bottom: 1px solid var(--elbe-line);
    font-size: 11px;
    letter-spacing: 1.2px;
    text-transform: uppercase;
    color: var(--elbe-ivory-dim);
    font-weight: 600;
}


/* ===== 6. The sidebar =======================================

   The original's, rule for rule. It had been MyTemplate's flat white panel
   with Bootstrap icons; this is the warm gradient, the brass-ruled brand and
   the emoji the school knows.

   Scoped to the two Elbe themes, so the other eight keep the template's. */

.sidebar {
    background: var(--theme-sidebar-grad);
    border-right: 1px solid var(--elbe-line);
    padding: 20px 14px 16px;
}

/* The brand sits above a 2px brass rule -- a gradient, not a flat line, which
   is `border-image` in the original. */
.sidebar-header {
    display: flex;
    align-items: center;
    gap: 11px;
    padding: 2px 6px 16px;
    margin-bottom: 8px;
    border-bottom: 2px solid;
    border-image: linear-gradient(90deg, var(--theme-accent-light), var(--theme-brass), var(--theme-accent)) 1;
}

/*  The school's own logo (logoelbe.pdf, 2026-09-13): the "elbesatranç"
    wordmark with the crowned pawn standing in for the l. One black-on-
    transparent PNG serves every theme: it is used as a mask over a block of
    the theme's own text colour, so the mark is ink on a light ground and
    ivory on a dark one without a second file. Width follows the height at
    the artwork's own 1200:251. Set the height where it is used. */
.elbe-wordmark {
    display: inline-block;
    height: 28px;
    aspect-ratio: 1200 / 251;
    background-color: var(--theme-text);
    -webkit-mask: url('../img/elbe-wordmark.png') center / contain no-repeat;
    mask: url('../img/elbe-wordmark.png') center / contain no-repeat;
    flex-shrink: 0;
}

/* The desktop sidebar: the wordmark alone across the header, in place of
   the tile and the name beside it. An uploaded logo in Site settings still
   wins (see _Shell), so the fallback tile stays defined below. */
.sidebar-header .elbe-wordmark { height: 34px; }

/* The brand tile, where there is no logo to show. */
.sidebar-logo {
    width: 40px;
    height: 40px;
    border-radius: 11px;
    background: var(--theme-accent-grad);
    display: grid;
    place-items: center;
    color: var(--theme-on-brass);          /* on brass: 10.9:1 */
    font-size: 20px;
    flex-shrink: 0;
}

.sidebar-title {
    font-family: var(--elbe-serif);
    font-size: 18px;
    font-weight: 600;
    letter-spacing: -0.3px;
    color: var(--theme-text);
}

/* A menu entry. The original's own padding, radius, weight and size. */
.nav-link {
    display: flex;
    align-items: center;
    gap: 11px;
    padding: 9px 11px;
    border-radius: 9px;
    font-weight: 500;
    font-size: 13.5px;
    transition: 0.16s;
}

.nav-link:hover {
    background: var(--theme-sidebar-hover-bg);
    color: var(--theme-text);
}

/* The active entry: a brass wash left to right, and a brass bar inset down its
   left edge. This is the original's most recognisable menu detail. */
.nav-link.active {
    background: var(--theme-nav-active-grad);
    font-weight: 600;
    box-shadow: inset 3px 0 0 var(--elbe-brass);
}

.nav-link.active { color: var(--theme-text); }   /* 16.0:1 */

/* The section headings above each group of entries.

   The original sets these in `--ivory-faint` (#9A9384), which is 2.8:1 on its
   own sidebar -- too faint to read. Darkened to #6B6353, which is 5.4:1 and
   still reads as a quiet label rather than a heading. It is the one value on
   this screen not taken from the original, and it is here because a menu
   heading nobody can read is not worth copying. */
.nav-section {
    font-size: 9.5px;
    letter-spacing: 1.6px;
    text-transform: uppercase;
    color: var(--theme-sidebar-heading);
    font-weight: 700;
    padding: 14px 10px 6px;
}

/* The original marks each entry with an emoji rather than an icon font. Sized
   and boxed the way it does. */
.nav-link .elbe-ico {
    width: 18px;
    text-align: center;
    font-size: 15px;
    flex-shrink: 0;
    font-style: normal;
    line-height: 1;
}


/* ===== 7. Row actions =======================================

   The original labels its row buttons with words -- Profil, Düzenle, Pasife
   Al -- where the rebuild had icons and a tooltip. An icon with no label is
   slower to learn and invisible to anyone who does not hover.

   The label goes on the actions the original itself has. On a narrow screen it
   drops away and the icon carries on alone, which is what the `title` was
   always there for. */

.btn-label {
    font-size: 12px;
    font-weight: 600;
}

/* 1650px, measured rather than guessed: this table needs about 1277px for its
   nine columns and six actions with the labels showing, and the content area
   is the window less the 260px sidebar and the page padding. Below that the
   labels drop and the icons carry on with their tooltips, which is what they
   were always there for. A 1920 screen -- the common case -- shows them. */
@media (max-width: 1650px) {
    .btn-label {
        display: none;
    }
}


/* ===== 8. Badges ============================================

   Bootstrap's `bg-secondary` is a flat grey, and the original's tags are pale
   brass and its semantic colours. Only the greys are changed: `bg-success` and
   the rest already carry meaning and are left alone. */

.badge.bg-secondary {
    background: var(--theme-wash-16) !important;
    color: var(--theme-accent);                              /* 7.0:1 */
    font-weight: 600;
    border-radius: 999px;
    padding: 4px 10px;
}

/* The row's actions. A flex row rather than Bootstrap's `btn-group`, which
   stretches every button to the tallest one and turned a wrapped label into a
   three-line row. */
.row-actions {
    display: flex;
    align-items: center;
    gap: 4px;
    flex-wrap: nowrap;
}

/* These used to be tighter than everything else — 3px by 7px — because this
   table carries nine columns and six actions where the original has four and
   three, and Bootstrap's own padding pushed the last button past the edge.

   They take section 16's standard size now, like every other button. That
   standard is smaller than the `btn-sm` which was the problem in the first
   place, 12.5px lettering rather than 14: measured at 1152, 1440, 1680 and
   1920 CSS pixels the table still has room to spare, and the row does not grow
   — the avatar and the name beside the buttons were always the taller pair. */
.row-actions .btn {
    white-space: nowrap;
}


/* ===== 9. The member shell ==================================

   The student's and the parent's sidebar, brought to the same look as the
   staff one. It is a different shell with different class names, so the rules
   have to be written again rather than shared -- but every value below is the
   same as section 6's, which is the point. */

.member-sidebar {
    background: var(--theme-sidebar-grad);
    border-right: 1px solid var(--elbe-line);
}

.sidebar-link {
    display: flex;
    align-items: center;
    gap: 11px;
    padding: 9px 11px;
    border-radius: 9px;
    font-weight: 500;
    font-size: 13.5px;
    transition: 0.16s;
}

.sidebar-link { color: var(--theme-sidebar-text-muted); }        /* 6.4:1 */
.sidebar-link:hover { background: var(--theme-sidebar-hover-bg); color: var(--theme-text); }

.sidebar-link.active {
    background: var(--theme-nav-active-grad);
    font-weight: 600;
    box-shadow: inset 3px 0 0 var(--elbe-brass);
}

.sidebar-link.active { color: var(--theme-text); } /* 16.0:1 */

/* The member shell's top bar, which carries the school name. */
.member-topbar,
.member-navbar {
    background: var(--theme-card-grad);
    border-bottom: 1px solid var(--elbe-line);
}

/* A student's screens are built from these two, so they take the card
   treatment as the staff ones do. */
.member-content .card {
    background: var(--theme-card-grad);
    border: 1px solid var(--elbe-line);
    border-radius: var(--elbe-r);
    box-shadow: var(--elbe-shadow);
}

/*  The Lichess card on the profile edit screen (Kamal, 2026-09-04): the
    "connected" line is one row — the green tag, the name and date, what the
    key allows, and the two buttons pushed to the end — that wraps on a
    phone. Colours are the theme's own (text on the card ground, the felt
    green for the tag and the permission line), so both themes are covered
    by the palette that already defines them. */
.lk-status { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 10px; margin-bottom: 6px; }
.lk-status-text { font-size: .95rem; }
.lk-status-can { color: var(--elbe-felt-text); font-size: .9rem; }
.lk-status-btns { display: flex; gap: 6px; margin-inline-start: auto; }
.lk-steps { padding-inline-start: 1.25rem; margin-bottom: 8px; }
.lk-steps li { margin-bottom: 2px; }
.lk-ratings-only { border-top: 1px solid var(--elbe-line); margin-top: 12px; padding-top: 10px; }
.lk-sub { font-weight: 700; font-size: .9rem; }


/* ===== 10. On a phone =======================================

   The original's mobile shell: a top bar with a burger, a drawer holding the
   whole menu, and four quick tabs across the bottom with the menu as the
   fifth. Its own values throughout.

   This mattered more than a coat of paint. The bottom bar carried Home,
   Messages and Logout and nothing else -- there was no way to a menu at all on
   a phone, so a student could not reach their puzzles, homework or exams. */

/*  The ground and the edge are the theme's, not a fixed white.

    They were `background: #fff` with `color: var(--theme-text)` — the theme's
    lettering, which on the seven dark themes is a near-white, on a white
    button. Measured on Blue Sky in the running site: #f1f5f9 on #ffffff,
    1.10:1. The button was there and pressable and there was nothing to see on
    it, which on a phone is the only way to a menu at all.

    Taking the theme's card colour instead: on Elbe that is #FFFFFF and nothing
    changes, and on a dark theme it is the theme's card with its own lettering
    on it — 13.4:1 on Blue Sky. The bar behind is that same card colour, so the
    button's edge is what shows it is a button; `--elbe-line` is the one value
    every theme states in terms of its own surfaces, dark on the light themes
    and light on the dark ones, so it reads on both. */
.mob-burger {
    width: 40px;
    height: 40px;
    border: 1px solid var(--elbe-line);
    border-radius: 11px;
    background: var(--theme-card-bg);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 17px;
    cursor: pointer;
    flex-shrink: 0;
    color: var(--theme-text);
}

/* The drawer slides in from the leading edge and is the full height. */
.mob-drawer {
    position: fixed;
    top: 0;
    inset-inline-start: 0;
    bottom: 0;
    width: 284px;
    max-width: 86vw;
    z-index: 9000;
    background: var(--theme-sidebar-grad);
    border-inline-end: 1px solid var(--elbe-line);
    display: flex;
    flex-direction: column;
    transform: translateX(-102%);
    transition: transform 0.22s cubic-bezier(0.4, 0, 0.2, 1);
    overflow-y: auto;
}

[dir="rtl"] .mob-drawer { transform: translateX(102%); }

.mob-drawer.open { transform: translateX(0); }

.mob-overlay {
    position: fixed;
    inset: 0;
    z-index: 8900;
    background: rgba(28, 22, 8, 0.42);
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.22s;
}

.mob-overlay.open {
    opacity: 1;
    pointer-events: auto;
}

/* Who is signed in, above a brass rule -- the same brand treatment the desktop
   sidebar uses. */
.mob-drawer-head {
    display: flex;
    align-items: center;
    gap: 11px;
    padding: 14px 14px 16px;
    border-bottom: 2px solid;
    border-image: linear-gradient(90deg, var(--theme-accent-light), var(--theme-brass), var(--theme-accent)) 1;
}

.mob-drawer-head .nm {
    font-family: var(--elbe-serif);
    font-weight: 600;
    font-size: 15px;
    line-height: 1.2;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.mob-drawer-head .sub {
    font-size: 11.5px;
    color: var(--elbe-ivory-dim);
}

.mob-drawer-close {
    margin-inline-start: auto;
    border: 0;
    background: transparent;
    font-size: 26px;
    line-height: 1;
    cursor: pointer;
    color: var(--elbe-ivory-dim);
    padding: 0 4px;
}

.mob-drawer-nav {
    padding: 8px;
    flex: 1;
}

.mob-drawer-link {
    display: flex;
    align-items: center;
    gap: 11px;
    padding: 11px 11px;
    border-radius: 9px;
    color: var(--elbe-ivory-dim);       /* 6.4:1 on the drawer */
    font-weight: 500;
    font-size: 14px;
    text-decoration: none;
    transition: 0.16s;
}

/*  The same mistake, and it hid the entry being pointed at: a fixed white
    behind the theme's near-white lettering, over a drawer that is the theme's
    dark sidebar. The theme's own hover wash instead — a faint tint of the
    accent, which every theme states against its own sidebar, so the lettering
    stays the 16:1 it is at rest and only the ground moves. */
.mob-drawer-link:hover {
    background: var(--theme-hover-bg);
    color: var(--theme-text);
}

.mob-drawer-link.on {
    background: var(--theme-nav-active-grad);
    color: var(--theme-text);                      /* 16.0:1 */
    font-weight: 600;
    box-shadow: inset 3px 0 0 var(--elbe-brass);
}

.mob-drawer-sec {
    font-size: 9.5px;
    letter-spacing: 1.6px;
    text-transform: uppercase;
    color: var(--theme-sidebar-heading);                      /* 5.4:1, the same correction as the sidebar */
    font-weight: 700;
    padding: 14px 10px 6px;
}

/*  The student drawer's fold-out groups — the desktop sidebar's four, on a
    phone. The heading is a <button> wearing the link's dressing: background,
    border, width, font and alignment cleared here, and the colour comes from
    `.mob-drawer-link` itself (--elbe-ivory-dim, 6.4:1 on the drawer), so no
    near-black button default can sneak in on either theme. The chevron sits
    at the far end and turns when the group is open; the members are drawn
    in a little from the edge so the group reads as one thing. */
.mob-drawer-group-head {
    width: 100%;
    background: none;
    border: 0;
    font: inherit;
    font-weight: 600;
    font-size: 14px;
    text-align: start;
    cursor: pointer;
}

.mob-drawer-caret {
    margin-inline-start: auto;
    font-size: 12px;
    transition: transform 0.16s;
}

.mob-drawer-group.open .mob-drawer-caret { transform: rotate(180deg); }
.mob-drawer-group-body { display: none; }
.mob-drawer-group.open .mob-drawer-group-body { display: block; }
.mob-drawer-group-body .mob-drawer-link { padding-inline-start: 26px; padding-block: 9px; }

.mob-drawer-out {
    margin: 8px;
    border-top: 1px solid var(--elbe-line);
    border-radius: 0;
    padding-top: 14px;
}

/* Signing out is a posted form now, not a link, so that another site cannot
   sign somebody out by pointing at the address. That makes this row a
   `<button>`, which arrives with its own background, border, font, width and
   alignment — none of which the drawer wants. Cleared here so the row looks
   exactly as it did.

   The colour is not restated: `.mob-drawer-link` above sets it explicitly, so
   the button takes the same --elbe-ivory-dim on the same drawer, at the same
   6.4:1, rather than the near-black a button would otherwise default to. */
.mob-drawer-out-form {
    margin: 0;
}

.mob-drawer-out-form .mob-drawer-link {
    width: calc(100% - 16px);   /* .mob-drawer-out puts 8px either side */
    background: none;
    border: 0;
    border-top: 1px solid var(--elbe-line);
    font: inherit;
    font-size: 14px;
    font-weight: 500;
    text-align: start;
    cursor: pointer;
}

/*  The coach said no radio in this lesson: the player disappears for the
    students the switch is aimed at — inside the room (tr-hide-radio, set by
    classroom-settings.js) and anywhere else on the site while the lesson
    runs (radio-banned, set by the live word and the page-load check). Never
    for the coach. */
body.tr-hide-radio .sidebar-radio-panel,
body.tr-hide-radio .mobile-radio-panel,
body.radio-banned .sidebar-radio-panel,
body.radio-banned .mobile-radio-panel { display: none !important; }

/* The bottom tabs. The original's blur and its brass for the selected one.
   `env(safe-area-inset-bottom)` keeps them clear of the home indicator on a
   modern phone.

   The bar stands at 42 (Kamal, 2026-08-31), down from 56. It used to carry 6
   points of padding on the bar AND 6 more on every link, top and bottom — 24
   points of air around a 32-point stack, on the screen where space is
   scarcest. Both are 3 now, the icon is 17 and the word 10, which comes to
   about 41 and a half; the 42 in `--mobile-bottomnav-height` is the floor that
   rounds it. The room saved went to the page above. */
.mobile-bottomnav {
    background: var(--theme-card-bg);
    backdrop-filter: blur(10px);
    border-top: 1px solid var(--elbe-line);
    padding: 3px 4px calc(3px + env(safe-area-inset-bottom));
}

.mobile-bottomnav a {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 1px;
    /*  2, not 3. Measured in the app rather than added up on paper: the word
        renders at 11.2px there, not the 10 written below — the WebView boosts
        it — so the stack came to 36.9 and the bar to 43.6 against the 42 asked
        for. At 2 the stack is under the floor and the floor decides. */
    padding: 2px;
    font-size: 10px;
    line-height: 1.15;
    font-weight: 600;
    text-decoration: none;
}

.mobile-bottomnav a { color: var(--elbe-ivory-dim); }

/*  The word stays on one line and is centred under its icon. "Oyun Salonu"
    and "Ana Sayfa" are two words: on a narrow phone they were breaking onto
    two lines, and the second line sat left-aligned under the icon while the
    one-word English labels looked right (Kamal, 2026-09-13). One line keeps
    the bar at its 42 too; a label that ever outgrows its tab is clipped at
    the ends rather than allowed to push the others. */
.mobile-bottomnav a span {
    text-align: center;
    white-space: nowrap;
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
}

.mobile-bottomnav a.active { color: var(--elbe-eyebrow); }

.mobile-bottomnav .elbe-ico {
    font-size: 17px;
    line-height: 1;
    width: auto;
}

/* The original's own mobile adjustments to the page itself. */
@media (max-width: 820px) {
    .page-head {
        flex-direction: column;
        align-items: flex-start;
        gap: 8px;
    }

    .page-head .page-title { font-size: 24px; }

    /* iOS zooms the whole page when a field smaller than 16px is focused. */
    input, select, textarea {
        font-size: 16px !important;
    }

    /* A wide table scrolls on its own rather than stretching the page. */
    .table-responsive table {
        white-space: nowrap;
    }
}


/* ===== 11. The admin shell on a phone ========================

   The staff shell has one layout for every width with a sidebar that slides
   in, so it needed less than the member one -- but three things were wrong,
   and the first two made screens unusable rather than merely plain. */

@media (max-width: 991.98px) {
    /* The burger was `btn btn-link text-light`: white lettering on a white top
       bar, the same mistake the sidebar itself carried. It is the only way to
       the menu on a phone and it could not be seen. Now it matches the member
       shell's, and at 44px it is a real tap target rather than 30px. */
    .top-header .mob-burger {
        width: 44px;
        height: 44px;
        font-size: 18px;
    }

    /* Room to breathe. The staff screens were laid out for a desk. */
    .main-content {
        padding: 12px !important;
    }

    .admin-card,
    .card {
        padding: 14px;
        border-radius: 13px;
    }

    /* A nine-column table on a 390px screen shows two columns and hides the
       rest, including the actions -- which are the point of the row. Rather
       than shrink it to nothing, each row becomes a stacked block: the column
       name on the left, its value on the right, one line each.

       `data-label` is set from the header by the script below, so no view has
       to be rewritten and a column added later is picked up on its own. */
    .table-stacked thead {
        display: none;
    }

    .table-stacked,
    .table-stacked tbody,
    .table-stacked tr,
    .table-stacked td {
        display: block;
        width: 100%;
    }

    .table-stacked tr {
        border: 1px solid var(--elbe-line);
        border-radius: var(--elbe-r);
        margin-bottom: 10px;
        padding: 4px 2px;
        background: var(--theme-card-bg, #fff);
    }

    .table-stacked td {
        display: flex;
        align-items: center;
        justify-content: space-between;
        gap: 12px;
        border: 0;
        padding: 7px 12px;
        text-align: start;
    }

    /* The column's name, taken from the header. */
    .table-stacked td::before {
        content: attr(data-label);
        font-size: 10.5px;
        letter-spacing: 1.1px;
        text-transform: uppercase;
        font-weight: 700;
        color: var(--elbe-ivory-dim);
        flex-shrink: 0;
    }

    /* The first cell is the person, which needs no label and reads better
       across the full width. */
    .table-stacked td:first-child {
        border-bottom: 1px solid var(--elbe-line);
        padding-bottom: 10px;
        margin-bottom: 2px;
    }

    .table-stacked td:first-child::before {
        content: none;
    }

    /* A cell holding nothing is noise on a phone. */
    .table-stacked td.is-empty {
        display: none;
    }

    /* The actions go full width at the bottom of the block, where a thumb can
       reach them, and keep their labels rather than dropping to icons. */
    .table-stacked td:last-child {
        justify-content: flex-start;
        flex-wrap: wrap;
        border-top: 1px solid var(--elbe-line);
        margin-top: 2px;
        padding-top: 10px;
    }

    .table-stacked .row-actions .btn {
        padding: 7px 11px;
        font-size: 13px;
    }

    .table-stacked .btn-label {
        display: inline !important;
    }
}


/* ===== 12. The staff top bar =================================

   Radio, messages, zoom and language, each in its own group with a rule
   between them so the bar reads as four things rather than one row of
   ungrouped icons.

   The bar is kept SHORT on purpose. It had come out 97px tall because the
   radio was stacking its own two rows -- the aerial above the controls, the
   channel below them -- and the tallest thing in a flex row sets the height of
   everything. The radio is flattened to a single line below, which brings the
   bar down to about 60. */

/* ----- The gear menu on a student row -----

   Three actions in a small list rather than three more buttons on every row.
   Full-width targets with the icon in a fixed column so the labels line up. */
.student-actions-menu {
    display: flex;
    flex-direction: column;
    gap: 4px;
    text-align: start;
}

.student-action {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    padding: 10px 12px;
    border: 1px solid var(--elbe-line);
    border-radius: 10px;
    background: var(--theme-card-bg);
    color: var(--theme-text);
    font-size: 14px;
    font-weight: 500;
    text-align: start;
    cursor: pointer;
    transition: background 0.15s ease, border-color 0.15s ease;
}

.student-action:hover {
    background: var(--theme-wash-10);
    border-color: rgba(255, 194, 0, 0.55);
}

.student-action i {
    width: 18px;
    flex-shrink: 0;
    font-size: 16px;
    text-align: center;
}

/* SweetAlert focuses the first item as it opens, and the browser's default
   ring is a hard black rectangle that reads as an error against this palette.
   Brass instead, and still clearly visible for anyone using the keyboard. */
.student-action:focus,
.student-action:focus-visible {
    outline: none;
    border-color: #FFC200;
    box-shadow: 0 0 0 3px rgba(255, 194, 0, 0.35);
}

/* The two coloured entries. Both measured on white:
     #157347 ... 4.8:1      #B02A37 ... 5.9:1
   Bootstrap's own .text-success and .text-danger are 2.3:1 and 4.0:1 here,
   which is why they are not used. */
.student-action.text-success { color: #157347 !important; }
.student-action.text-danger { color: #B02A37 !important; }

/* The two coloured ones take the palette's lettering, which is already the
   right value on whichever theme is on. */
.student-action.text-success { color: var(--elbe-felt-text) !important; }
.student-action.text-danger { color: var(--elbe-wine-text) !important; }

/* The dialog rules that used to sit here — the reserved scrollbar gutter and
   the fixed overlays that must not be sized in viewport units — moved to
   wwwroot/css/dialogs.css. They apply wherever a dialog can open, including
   the signed-out pages, and this file is not loaded there. */

/* The bar's own structure, so it does not depend on the shell it is rendered
   in.

   These rules used to live in adminlayout.css, which only the staff shell
   loads. When the member shell started rendering the same bar it came out
   unstyled: no background, no border, not sticky, the two halves stacked. Both
   shells load THIS file, so the bar is defined here and stands on its own.

   Colours are written out rather than taken from --theme-*: the two shells use
   different variable names for the same idea (adminlayout.css says
   --theme-card-bg-solid, theme-base.css does not define it at all), and a bar
   that renders in both cannot rely on either. */
.top-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    position: sticky;
    top: 0;
    z-index: 100;

    background: var(--theme-card-bg);
    border-bottom: 1px solid var(--elbe-line);

    padding: 0.375rem 1.25rem;   /* 6px, so 6 + 32 + 6 = 44 */
    min-height: 45px;
}

/* The initials tile that opens the account menu. Also from adminlayout.css. */
.top-header .user-avatar {
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    font-weight: 700;
    cursor: pointer;
    overflow: hidden;
    transition: transform 0.15s ease, box-shadow 0.15s ease;
}

.top-header .user-avatar img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/* The notification list. It hung off a 320px inline style on the member bar
   and did not exist on the staff one. */
.topbar-notif-menu {
    min-width: 320px;
    max-height: 400px;
    overflow-y: auto;
}

.header-actions {
    display: flex;
    align-items: center;
    gap: 0;
}

.topbar-group {
    display: flex;
    align-items: center;
    gap: 4px;
    padding: 0 12px;
    border-inline-end: 1px solid var(--elbe-line);
}

/* The last group before the avatar keeps its spacing but drops the rule, so
   the bar does not end on a line. */
.header-actions > .topbar-group:last-of-type {
    border-inline-end: 0;
    padding-inline-end: 14px;
}

/* One button, used by messages, zoom and language alike. 36px keeps the bar
   short while still being a real target. */
.topbar-btn {
    position: relative;
    width: 32px;
    height: 32px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 5px;
    border: 1px solid transparent;
    border-radius: 10px;
    background: transparent;
    color: var(--elbe-ivory-dim);       /* 7.1:1 on the bar */
    font-size: 18px;
    cursor: pointer;
    transition: 0.16s;
    padding: 0;
    flex-shrink: 0;
}

.topbar-btn:hover:not(:disabled) {
    background: var(--theme-hover-bg);
    border-color: var(--elbe-line);
    color: var(--theme-text);
}

.topbar-btn:disabled { opacity: 0.35; cursor: default; }

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

/* The globe. Larger than the rest, because it is the whole language control
   rather than a label beside one, and it carries the current language so
   nobody has to open it to see where they are. */
.topbar-globe {
    width: auto;
    padding: 0 8px 0 9px;
    font-size: 20px;
}

.topbar-globe::after { display: none; }   /* Bootstrap's caret; the tag says more */

.topbar-lang-tag {
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.5px;
    line-height: 1;
}

/* Unread messages. */
.topbar-badge {
    position: absolute;
    top: 2px;
    inset-inline-end: 2px;
    min-width: 16px;
    height: 16px;
    padding: 0 4px;
    border-radius: 999px;
    background: var(--elbe-wine);
    color: #fff;                         /* 4.7:1 on that red */
    font-size: 10px;
    font-weight: 700;
    line-height: 16px;
    text-align: center;
}

/* The zoom figure, which is also the way back to 100%. */
.topbar-zoom-level {
    min-width: 40px;
    text-align: center;
    font-size: 12px;
    font-weight: 600;
    color: var(--elbe-ivory-dim);
    cursor: pointer;
    user-select: none;
    border-radius: 7px;
    padding: 2px;
}

.topbar-zoom-level:hover { background: var(--theme-hover-bg); color: var(--theme-text); }

/* The profile. 34px rather than 42, and the dropdown no longer stretches to
   the width of the name inside it. */
.top-header .user-avatar {
    width: 30px;
    height: 30px;
    border-radius: 9px;
    font-size: 11px;
    flex-shrink: 0;
}

.header-actions > .dropdown:last-child {
    margin-inline-start: 4px;
}


/* ----- The radio, on one line -----

   The partial renders two rows: the aerial and the controls, then the channel
   under them. That is right in a sidebar and wrong in a bar, where it made the
   whole header 97px tall.

   `display: contents` on both rows drops them out of the box tree, so their
   children become direct flex items of the panel and sit on a single line.
   Nothing in the markup has to change, and the sidebar keeps its own shape. */

.topbar-radio .sidebar-radio-panel {
    display: flex;
    align-items: center;
    gap: 7px;
    margin: 0;
    padding: 2px 7px;
    background: transparent;
    border: 1px solid var(--elbe-line);
    border-radius: 9px;
    flex-wrap: nowrap;
}

.topbar-radio .radio-panel-row,
.topbar-radio .radio-panel-controls {
    display: contents;
}

/* The aerial, first on the line rather than above it. */
.topbar-radio .radio-panel-left {
    display: flex;
    align-items: center;
    order: 0;
    min-width: 0;
    flex: 0 0 auto;
}

.topbar-radio .radio-panel-label {
    display: none;              /* the aerial says it */
}

.topbar-radio .radio-panel-icon {
    font-size: 15px;
    color: var(--elbe-brass-text);
}

/* Everything on one baseline, in reading order. */
.topbar-radio #radioPlayBtn      { order: 1; }
.topbar-radio #radioVolumeDown   { order: 2; }
.topbar-radio #radioVolume       { order: 3; }
.topbar-radio #radioVolumeUp     { order: 4; }
.topbar-radio .radio-channel-dropdown { order: 5; }

/* These controls are styled inline in _MemberDesktopLayout, which the staff
   shell does not load -- so in the bar they were falling back to the browser's
   own widgets: grey boxes, black text and a bright blue slider that belonged
   to no palette here. Painted properly below. */
.topbar-radio .radio-control-btn,
.topbar-radio .radio-volume-btn {
    width: 24px;
    height: 24px;
    min-width: 24px;
    padding: 0;
    border-radius: 8px;
    flex-shrink: 0;
    align-self: center;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: transparent;
    border: 1px solid var(--elbe-line);
    color: var(--elbe-ivory-dim);        /* 7.1:1 on the bar */
    font-size: 13px;
    cursor: pointer;
    transition: 0.16s;
}

.topbar-radio .radio-control-btn:hover:not(:disabled),
.topbar-radio .radio-volume-btn:hover:not(:disabled) {
    background: var(--theme-wash-16);
    border-color: rgba(255, 194, 0, 0.45);
    color: var(--theme-text);
}

.topbar-radio .radio-control-btn:disabled,
.topbar-radio .radio-volume-btn:disabled {
    opacity: 0.4;
    cursor: default;
}

/* Playing: the accent as a fill, and the ink measured for that fill -- not
   --theme-text, which is the ink for the page and is light on a dark theme. */
.topbar-radio .radio-control-btn.playing {
    background: var(--elbe-brass);
    border-color: var(--elbe-brass);
    color: var(--theme-on-brass);
}

.topbar-radio .radio-control-btn.loading { opacity: 0.7; }

.topbar-radio .radio-control-btn.error {
    background: rgba(214, 59, 42, 0.14);
    border-color: rgba(214, 59, 42, 0.45);
    color: #9B2A1D;
}

/* Elbe Marşı (2026-09-08): the same panel with a note, the song's name, the
   button, the time and the volume. Ink: the label in the bar's dim ivory
   (7.1:1), the time in the same; the button is the radio's. */
.topbar-anthem .anthem-icon { font-size: 14px; line-height: 1; }
.topbar-anthem .anthem-label { font-size: 12px; font-weight: 700; color: var(--elbe-ivory-dim); white-space: nowrap; margin-inline-start: 4px; }
.topbar-anthem .anthem-time { font-size: 11px; font-variant-numeric: tabular-nums; color: var(--elbe-ivory-dim); white-space: nowrap; min-width: 64px; text-align: center; }
.topbar-anthem .radio-volume-slider { width: 64px; }
/* Beside the radio (the school turned it on) the anthem folds to the note, the button and the time, so the bar still fits. */
.topbar-anthem.with-radio .anthem-label,
.topbar-anthem.with-radio .radio-volume-slider { display: none; }
.topbar-anthem.with-radio .anthem-time { min-width: 0; }

/* The slider. `accent-color` was `auto`, which is where the blue came from. */
.topbar-radio .radio-volume-slider {
    accent-color: var(--elbe-brass-text);
    height: 4px;
    background: rgba(28, 22, 8, 0.14);
    border-radius: 999px;
    cursor: pointer;
    -webkit-appearance: none;
    appearance: none;
}

.topbar-radio .radio-volume-slider::-webkit-slider-thumb {
    -webkit-appearance: none;
    width: 12px;
    height: 12px;
    border-radius: 50%;
    background: var(--elbe-brass-text);
    cursor: pointer;
    border: 0;
}

.topbar-radio .radio-volume-slider::-moz-range-thumb {
    width: 12px;
    height: 12px;
    border-radius: 50%;
    background: var(--elbe-brass-text);
    border: 0;
    cursor: pointer;
}

.topbar-radio .radio-volume-slider {
    width: 58px;
    min-width: 58px;
    align-self: center;
}

/* The channel picker: same height as the buttons beside it, so the row has one
   baseline rather than three. */
.topbar-radio .radio-channel-dropdown {
    flex: 0 0 auto;
    min-width: 0;
    /* The wrapper is a flex item of its own, and without this it stretched to
       the row and left the button inside it sitting a pixel below the rest. */
    display: flex;
    align-items: center;
    align-self: center;
}

.topbar-radio .radio-channel-toggle {
    height: 24px;
    max-width: 132px;
    padding: 0 8px;
    font-size: 12px;
    border-radius: 8px;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    background: transparent;
    border: 1px solid var(--elbe-line);
    color: var(--elbe-ivory-dim);
    font-weight: 500;
    cursor: pointer;
    transition: 0.16s;
}

.topbar-radio .radio-channel-toggle:hover {
    background: var(--theme-wash-10);
    border-color: rgba(255, 194, 0, 0.40);
    color: var(--theme-text);
}

.topbar-radio .radio-channel-label {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.topbar-radio .radio-channel-chevron {
    font-size: 10px;
    flex-shrink: 0;
    margin-inline-start: auto;
}

/* The button carries `dropdown-toggle`, so Bootstrap draws a caret of its own
   on top of the chevron already in the markup -- two arrows side by side. The
   markup's own is the one that gets rotated when the menu opens, so Bootstrap's
   goes. */
.topbar-radio .radio-channel-toggle::after {
    display: none;
}

/* The channel list, which had been inheriting the sidebar's width. */
/*  The panel itself is described once, in section 20, for wherever the radio
    appears — the top bar, the sidebar and the phone. What was here sized it for
    the top bar only, and styled `.dropdown-item`, which the player does not
    write: it writes `.radio-channel-item`. So that rule matched nothing and the
    list of stations had no styling at all. */

/* On a narrow screen the groups lose their rules and tighten, and the radio
   goes entirely -- it is the one thing here nobody needs on a phone. */
@media (max-width: 991.98px) {
    .topbar-group {
        padding: 0 5px;
        border-inline-end: 0;
    }

    /* A phone gets bigger targets than a desk, whatever the bar's height on a
       desktop: a thumb is not a mouse pointer. The bar grows to fit them. */
    .topbar-btn {
        width: 38px;
        height: 38px;
        font-size: 18px;
    }

    .top-header {
        min-height: 52px;
    }

    .top-header .mob-burger {
        width: 42px;
        height: 42px;
        font-size: 18px;
    }
}

/* The bar's zoom is for screens that have no other way to do it.

   Anything with a touch screen already zooms a page by pinching, so the two
   buttons and the figure between them are a second control for a job the
   device does natively — and a coach on a tablet, looking at a page zoom in
   the bar and a board zoom beside "Sınıf", reasonably asks which is which. On
   a touch screen the answer is: pinch for the page, buttons for the board.

   `hover: none` and `pointer: coarse` together mean a finger and nothing else.
   A laptop with a touch screen still reports a mouse, so it keeps the buttons,
   which is right — Ctrl and the scroll wheel are not something to rely on
   people knowing, and this control exists because staff asked to SEE what the
   zoom is set to rather than guess.

   This replaces an earlier rule that hid it below 576px for room on the bar.
   Every screen that narrow is a touch screen, so that case is covered here. */
@media (hover: none) and (pointer: coarse) {
    .top-header .topbar-zoom-group {
        display: none;
    }
}

/* One tap should do it.

   A page that allows zooming also allows double-tap-to-zoom, and the browser
   cannot tell a tap from the first half of a double tap until it has waited to
   see whether a second one arrives. So the button lights up at once and its
   action lands a third of a second later — which reads, entirely reasonably, as
   "the first tap did nothing" and gets tapped again.

   `manipulation` says this element is not a zoom target. Pinching still works,
   which is the whole reason the zoom control was taken off touch screens in the
   first place; only the double-tap gesture goes, and with it the wait.

   Every touch screen, phones included. It was tablets only, and phones sat
   through the same third of a second on every button they pressed — which is
   most of what "the taps feel unreliable" turned out to be (Kamal,
   2026-08-28: a tap must be a click, on the whole mobile side). */
/* Two widenings, 2026-08-28 (Kamal: a tap must be a click across the whole
   mobile side, and the taps must be as quick as they can be):

   1. `and` became `or`. A phone with a paired mouse, a keyboard case, a stylus
      or a browser in desktop mode does not report `hover: none`, so the whole
      rule was skipped on it and every button there kept the third of a second.
      This is the same test touch-tap-fix.js already uses.

   2. The list of controls became everything. A row in a list, a chip in a
      dialog, a card — anything built as a plain block with a tap handler on it
      — was not in the list and waited exactly as before. `*` carries no
      specificity, so the boards keep their own `touch-action: none` (a finger
      on a board moves a piece and never the page) and anything with its own
      rule keeps it. */
@media (hover: none), (pointer: coarse) {
    * {
        touch-action: manipulation;
    }
}

/*  The way back from a pinch — see unzoomButton in touch-tap-fix.js, which
    makes it, shows it only while the page is actually zoomed, and puts it in
    the corner of what is being looked at.

    Placed from the top left and moved by a transform, because on a pinched
    phone "bottom right" in CSS means the bottom right of the whole page, which
    is usually off the screen.

    Both palettes, written here together:
      light  #312E81 on #E0E7FF   11.4:1, border #A5B4FC   1.9:1 on white
      dark   #E5E7EB on #1F2937   12.6:1, border rgba(255,255,255,0.25)  */
.elbe-unzoom {
    position: fixed;
    top: 0;
    left: 0;
    width: 40px;
    height: 40px;
    padding: 0;
    display: none;
    align-items: center;
    justify-content: center;
    font-size: 1.05rem;
    line-height: 1;
    color: #312E81;
    background: #E0E7FF;
    border: 1px solid #A5B4FC;
    border-radius: 50%;
    box-shadow: 0 6px 18px -6px rgba(28, 22, 8, 0.55);
    transform-origin: top left;
    z-index: 2147483000;            /* over dialogs, over the bars, over everything */
    -webkit-tap-highlight-color: transparent;
}

    .elbe-unzoom.show {
        display: flex;
    }

body[data-bs-theme="dark"] .elbe-unzoom {
    color: #E5E7EB;
    background: #1F2937;
    border-color: rgba(255, 255, 255, 0.25);
    box-shadow: 0 6px 18px -6px rgba(0, 0, 0, 0.7);
}

/* On a phone the bar has to give something up.

   At 390px — the width of the phone most of these students are holding — the
   burger, face, messages, bell, globe and account came to more than the
   screen, so the account tile hung off the right-hand edge and the bar itself
   scrolled sideways. Zoom went first and is now gone on every touch screen by
   the rule above; the rest of this block is what else had to give. */
@media (max-width: 575.98px) {
    .top-header .topbar-zoom-group {
        display: none;
    }

    .topbar-group {
        padding: 0 3px;
    }

    .top-header {
        padding-inline: 0.5rem;
    }

    /* The bar is the full width of the screen and nothing in it may push past
       that edge, whatever is added to it later. */
    .header-actions {
        min-width: 0;
    }
}


/* ----- Who is signed in, on the left of the bar -----

   Picture, name, role. Sized to fit inside the 45px bar without touching its
   height: the face is 26px, which is under the 32px the bar allows. */

.topbar-whoami {
    display: flex;
    align-items: center;
    gap: 9px;
    min-width: 0;
}

/* The picture sits on top of the initials rather than replacing them. If
   there is none the image removes itself and the initials show through, so
   the bar never has to ask the data service whether a picture exists before
   it can draw. */
.topbar-whoami-face {
    position: relative;
    width: 26px;
    height: 26px;
    flex: 0 0 26px;
    display: inline-block;
}

.topbar-whoami-face .elbe-avatar {
    width: 26px;
    height: 26px;
    flex-basis: 26px;
    font-size: 11px;
    border-radius: 8px;
    background: var(--elbe-brass-text);

    /* --elbe-brass-text is the accent tuned to be READ on a card, so on a dark
       theme it is a bright colour. `.elbe-avatar` letters it white, which is
       5.1:1 on Elbe's #8A6A00 and 1.2:1 on Okyanus's #64ffda. The ink is the
       theme's, measured per theme in theme-base.css. */
    color: var(--theme-on-accent-fill);
}

.topbar-whoami-face img {
    position: absolute;
    inset: 0;
    width: 26px;
    height: 26px;
    border-radius: 8px;
    object-fit: cover;
    background: var(--elbe-brass-text);
}

.topbar-whoami-name {
    font-weight: 600;
    font-size: 13px;
    color: var(--theme-text);                        /* 16.4:1 on the bar */
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 240px;
}

.topbar-whoami-role {
    flex-shrink: 0;
    font-size: 10.5px;
    padding: 1px 8px;
    line-height: 1.6;
}

/* Full screen, a clear step clear of the role tag rather than tight against it */
.topbar-fullscreen {
    margin-inline-start: 14px;
    flex-shrink: 0;
}

/* On a phone the name goes and the picture and role stay -- the bar has the
   burger to fit as well, and a long Turkish name would push the tools off. */
@media (max-width: 991.98px) {
    .topbar-whoami-name {
        display: none;
    }
}

@media (max-width: 575.98px) {
    .topbar-whoami-role {
        display: none;
    }
}


/* ----- Where you are, in the middle of the bar -----

   The classroom's lesson number and its subject, standing in the gap the bar
   has always had between who is signed in and the radio. Nothing else on the
   site fills it in, so this element is not even rendered elsewhere.

   flex:1 with min-width:0 is what centres it: it takes the leftover room and
   centres its own contents in it, and lets a long subject be cut with an
   ellipsis rather than shoving the radio and the tools off the edge.

   Colours come from the school's own tag set, so both themes are already
   answered: the pill is --elbe-sky-text lettering on a 15% wash of itself
   (#1F5C86 on the ivory bar, 6.2:1; #7CB8E8 on the wood one), and the subject
   takes --theme-text, the same ink the signed-in name beside it uses. */
.topbar-lesson {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 12px;                  /* about the three spaces asked for */
    padding-inline: 12px;
    overflow: hidden;
}

.topbar-lesson-no {
    flex: 0 0 auto;
    font-size: 11.5px;
}

.topbar-lesson-topic {
    font-weight: 600;
    font-size: 13px;
    color: var(--theme-text);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Tight bars give up the subject first and keep the number, which is the half
   that says where you are. */
@media (max-width: 991.98px) {
    .topbar-lesson-topic {
        display: none;
    }
}

/* On a phone even the pill has nowhere to stand — the burger, the picture and
   the tools have the whole bar. */
@media (max-width: 575.98px) {
    .topbar-lesson {
        display: none;
    }
}

/* ===== 13. Segmented choice and the phone field ==============
   Two form controls the original has and Bootstrap does not.

   The original writes a small choice -- lesson type, gender -- as a pair of
   radios rather than a dropdown, because with two options a dropdown hides
   half the answer behind a click. Radios themselves look wrong beside the
   rest of this form, so the same idea is drawn as one segmented control:
   both options visible, the chosen one filled in brass.

   Kept out of the views because the student form is the first of several
   that want it -- coaches and groups have the same two-way choices.
   ============================================================ */

.elbe-seg {
    display: flex;
    border: 1px solid var(--elbe-line);
    border-radius: 10px;
    overflow: hidden;                      /* so the fill follows the corners */

    /* The theme's raised surface, not a white wash. Written as
       rgba(255,255,255,0.55) it made a pale bar across a dark page and the
       unlit segments, which letter in the theme's muted ink, came out at
       1.29:1 on it. */
    background: var(--bs-tertiary-bg);
}

.elbe-seg-btn {
    flex: 1 1 0;
    padding: 7px 14px;
    border: 0;
    background: transparent;
    font: inherit;
    font-size: 13px;
    font-weight: 600;
    line-height: 1.5;
    color: var(--elbe-ivory-dim);          /* 7.1:1 unselected */
    cursor: pointer;
    transition: background 0.15s ease, color 0.15s ease;
}

.elbe-seg-btn + .elbe-seg-btn {
    border-left: 1px solid var(--elbe-line);
}

.elbe-seg-btn:hover {
    background: var(--theme-wash-10);
}

/* The selected one is the same fill as the page's primary button, so it takes
   the same ink -- measured per theme against that fill, never --theme-text. */
.elbe-seg-btn.is-on {
    background: var(--elbe-brass);
    color: var(--theme-on-brass);
}

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

/* A segment that is a link, not a button: used where each side of the choice
   is an address of its own — the two puzzle banks, the roll and the people who
   have left it — so a list can be linked to and Back does what it should. */
a.elbe-seg-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    text-decoration: none;
}

/* Tabs standing among the buttons in a page header.

   They take the height of the row, which is the height of the buttons beside
   them, so the two are the same size and stay the same size at every zoom —
   a button is measured in rem and grows with the text, and a control pinned
   to a pixel height beside it would drift apart from it. The minimum is
   Bootstrap's own button box, for a header whose tabs stand alone.

   The width is the words, not a share of the row: tabs that resized as the
   header's buttons came and went would shift the block above a table that had
   not moved, which reads as a flicker. */
.page-actions .elbe-seg {
    align-self: stretch;
    flex: 0 0 auto;
    min-height: 2.25rem;
}

    .page-actions .elbe-seg-btn {
        flex: 0 0 auto;
        padding: 0 0.875rem;
        font-size: 0.875rem;
    }

/* The phone field: the country is a fixed column so the digits line up down
   the form however wide the popup is, and the digits get a little tracking
   because ten unbroken figures are hard to check by eye.

   Wide enough for the country this school actually dials -- "Türkiye +90" --
   rather than for the longest name in the list. A closed `select` clips what
   does not fit, but the list it drops down is sized by the browser to its own
   contents, so a longer name is still read in full where it is chosen. */
.elbe-phone {
    display: flex;
    gap: 6px;
}

/* Allowed to shrink, not to grow: it wants 10.5rem, but in a narrow dialog it
   gives way rather than pushing the digits out of the field -- which showed up
   as a scrollbar along the bottom of the coach form, where the popup is
   narrower than the student one. */
.elbe-phone-code {
    flex: 0 1 10.5rem;
    width: 10.5rem;
    min-width: 7rem;
}

.elbe-phone-num {
    flex: 1 1 auto;
    min-width: 0;
    letter-spacing: 0.04em;
    font-variant-numeric: tabular-nums;
}


/* ===== 14. The panel ========================================

   The admin panel -- the original's `a_dash`, index.html line 1147 -- and the
   pieces it is built from. Every figure below is the original's own, from
   index.html lines 102-129, 246-247 and the injected `dashCss` at line 7727.

   One substitution runs through the section: the original sets its figures and
   rank badges in JetBrains Mono. Only Fraunces and Inter are bundled
   (tools/fetch-fonts.js), so `--elbe-mono` falls back to the platform's own
   monospace face. That keeps what the choice is for -- a column of numbers
   whose digits line up -- without a third font to download. */

:root {
    --elbe-mono: ui-monospace, 'Cascadia Mono', 'Segoe UI Mono', Consolas, monospace;
}

.elbe-mono {
    font-family: var(--elbe-mono);
    font-variant-numeric: tabular-nums;
}


/* --- the figure tiles ---------------------------------------

   The original writes the glyph small and faint in the top corner, the label
   above the number and a word underneath saying what the number counts. The
   template had a filled colour square on the left and no word at all.

   `.stat-card` stays on the element for the brass left edge section 5 gives
   it -- the original's own `.card.stat` rule. */

.elbe-stat {
    display: block;            /* `.stat-card` is a flex row; this is not */
    padding: 19px;
}

.elbe-stat .ico {
    float: right;
    font-size: 20px;
    line-height: 1;
    opacity: 0.8;
}

/* The original uses `--ivory-faint` (#9A9384) for this label, which is 2.7:1 on
   the card. This is its `--ivory-dim` at 7.1:1 -- the same substitution
   section 5 already makes on table headers, for the same reason. */
.elbe-stat .k {
    font-size: 10.5px;
    letter-spacing: 1.3px;
    text-transform: uppercase;
    font-weight: 600;
    color: var(--elbe-ivory-dim);
}

.elbe-stat .v {
    font-family: var(--elbe-mono);
    font-variant-numeric: tabular-nums;
    font-size: 30px;
    font-weight: 700;
    letter-spacing: -1px;
    margin-top: 9px;
    line-height: 1.15;
}

.elbe-stat .d {
    font-size: 12px;
    color: var(--elbe-ivory-dim);
    margin-top: 5px;
}


/* --- a card's own heading -----------------------------------

   Fraunces at 17px with nothing beside it. The template put a boxed icon in
   front of every card title; the original never does on this screen. */

.elbe-card-title {
    font-family: var(--elbe-serif);
    font-size: 17px;
    font-weight: 600;
    letter-spacing: -0.5px;
    margin: 0;
}

/* The original drops the ranked table straight onto its card with only 8px
   around it, so the rows reach almost the card's full width, and pads just the
   heading strip back out.

   Two classes deep, so it outweighs the single-class 19px section 5 gives
   every other card. It used to name the two themes as well, back when the
   19px rule named them; now that the rule applies everywhere, so does this. */
.admin-card.elbe-card-tight,
.elbe-card-tight {
    padding: 8px;
}

.elbe-card-tight > .elbe-card-head {
    padding: 12px 12px 4px;
}


/* --- the ghost button ---------------------------------------

   The original's `btn ghost sm`: the brass button's shape with no fill, so it
   reads as secondary beside the solid one in the page header. */

.elbe-btn-ghost {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 7px;
    min-height: 32px;                        /* the one size — see section 16 */
    line-height: 1.2;
    padding: 7px 12px;
    border-radius: 10px;
    font-size: 12.5px;
    font-weight: 700;
    background: transparent;
    color: var(--theme-text);
    border: 1px solid color-mix(in srgb, var(--theme-text) 20%, transparent);
    text-decoration: none;
    transition: 0.16s;
    white-space: nowrap;
}

.elbe-btn-ghost:hover {
    background: var(--theme-hover-bg);
    color: var(--theme-text);
}

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

/* Its hover was written twice, once per school theme. The theme's own hover
   wash covers both, and the six others besides. */
.elbe-btn-ghost:hover {
    background: var(--theme-hover-bg);
    color: var(--theme-text);
    border-color: color-mix(in srgb, var(--theme-text) 34%, transparent);
}


/* --- the rank badge -----------------------------------------

   The original numbers every row and paints the first three gold, silver and
   bronze. The rebuild had emoji medals for the top three and a bare digit for
   the rest, which left the column ragged and the medals rendering at whatever
   size the reader's emoji font chose. */

.elbe-rank {
    display: inline-grid;
    place-items: center;
    width: 25px;
    height: 25px;
    border-radius: 7px;
    font-family: var(--elbe-mono);
    font-weight: 700;
    font-size: 12px;
    background: var(--bs-tertiary-bg);       /* the original's --raise */
    color: var(--elbe-ivory-dim);            /* 6.3:1 on that */
}

/* Contrast stated against the darker stop of each gradient, which is where the
   lettering is worst off. */
.elbe-rank.r1 { background: linear-gradient(135deg, #E6C476, #B8902F); color: #1B1813; }  /* 5.5:1 */
.elbe-rank.r2 { background: linear-gradient(135deg, #CFD2D6, #9AA0A6); color: #1B1813; }  /* 6.9:1 */
.elbe-rank.r3 { background: linear-gradient(135deg, #CD9A6B, #A06A3C); color: #1B1813; }  /* 4.7:1 */


/* --- the announcement rows ----------------------------------

   Each announcement is its own bordered row with the type's glyph beside it,
   not a flat tinted strip. */

.elbe-announce {
    display: flex;
    gap: 13px;
    padding: 11px 13px;
    border-radius: 11px;
    background: var(--theme-card-bg);
    border: 1px solid var(--elbe-line);
}

.elbe-announce .ai {
    font-size: 20px;
    line-height: 1.3;
    flex-shrink: 0;
}

.elbe-announce .ttl {
    font-weight: 600;
    font-size: 13.5px;
}

.elbe-announce .meta {
    font-size: 11.5px;
    color: var(--elbe-ivory-dim);
    margin-top: 3px;
}


/* --- the section rule ---------------------------------------

   A Fraunces heading with a hairline running out to the right edge, which is
   how the original separates a block from the one above it. */

.elbe-section-title {
    font-family: var(--elbe-serif);
    font-size: 20px;
    font-weight: 600;
    margin: 30px 0 15px;
    display: flex;
    align-items: center;
    gap: 11px;
}

.elbe-section-title .rule {
    flex: 1;
    height: 1px;
    background: var(--elbe-line);
}


/* --- the sparkline ------------------------------------------

   `sparkline()` at index.html line 1144: a filled area under the line, the
   line itself, and a dot on the last reading. Green when the run ends at or
   above where it started, wine when it ends below -- so the colour says which
   way the rating went without the reader having to trace the curve. */

.elbe-spark {
    display: block;
}


/* The original's own table metrics -- 13.5px rows on 12px/13px padding, where
   Bootstrap's default is a roomier 16px box. Scoped to this card rather than
   applied to every table on the site: the panel is what is being brought to
   the original here, and this one has to fit a name, a figure and a sparkline
   into half the page. */
.elbe-card-tight .table {
    --bs-table-cell-padding-x: 13px;
    --bs-table-cell-padding-y: 12px;
    font-size: 13.5px;
}

/* The student column takes whatever width is left over. Without this the cell
   refuses to be narrower than its longest name and the widest one pushed the
   Gelişim column clean off the right of the card. */
.elbe-card-tight .table td.elbe-name-cell {
    width: 100%;
    max-width: 0;
}

/* Lists elsewhere hold a name to one line and cut it with an ellipsis, which
   is right where a row is one line tall. Here the card is only half the page
   and the original lets a long name run onto a second line rather than hiding
   the end of it, so this cell wraps. */
.elbe-card-tight .table td.elbe-name-cell .nm {
    white-space: normal;
    overflow: visible;
}

/* The group names underneath are the one thing that can be arbitrarily long --
   a student in four groups -- so they still cut off. */
.elbe-card-tight .table td.elbe-name-cell .sub {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}


/* ===== 15. Screens built round a board ======================

   A board is square, so its height is its width. Left to a grid column it grows
   as tall as the column is wide, and anything stacked above it — the three-row
   page header, a row of figures, a progress bar — pushes it off the bottom of a
   laptop screen. That is how Puzzle Storm ended up needing a scroll to see the
   position you were meant to be solving.

   The recipe is the original's own (`storm-screen`, index.html line 323): give
   the row a height, then let the board's WIDTH follow from it. Two shapes,
   depending on what shares the card with the board:

     A. the card holds the board and nothing else -- Puzzle Storm

        <div class="board-screen">
          <div class="card board-screen-board"><div class="card-body">
            <div id="..." class="elbe-board"></div>
          </div></div>
          <div class="card board-screen-side"><div class="card-body"> ... </div></div>
        </div>

     B. the card holds the board plus its own furniture -- the players' names,
        the move buttons. The card cannot be square, so the board is capped
        instead, and the screen declares how much room the furniture takes:

        <div class="board-screen" style="--board-extras: 150px"> ... 
          <div class="card board-screen-board-fit"><div class="card-body">
            <div class="board-fit"><div id="..." class="elbe-board"></div></div>
            ... names, buttons ...
          </div></div>

   `--board-chrome` is the top bar and the padding above and below the row.
   Override it per screen only if that screen carries something else outside the
   row. */

.board-screen {
    --board-chrome: 120px;
    --board-extras: 0px;

    display: flex;
    gap: 16px;
    align-items: stretch;
    height: calc(100vh - var(--board-chrome));
    min-height: 400px;

    /* A board screen is the one place a wider window does not help. The board
       is sized from the height and the panel is held narrow on purpose, so
       extra width only opens a gulf between the two — `space-between` pushes
       them to opposite walls. Capped, they stay the arm's length apart they
       were drawn to be. */
    max-width: 1400px;

    /* Width neither side needs opens between them rather than piling up on the
       right, which puts the panel against the right edge. */
    justify-content: space-between;
}

/* Shape A: the card is exactly as wide as the square inside it. */
.board-screen-board {
    flex: 0 0 auto;
    height: 100%;
    aspect-ratio: 1 / 1;
    max-width: none;
    margin: 0;
}

    .board-screen-board > .card-body {
        display: flex;
        padding: 12px;
    }

/* Shape B: the card cannot be square, because the names and the move buttons
   share it with the board. So the board's side is worked out — the height left,
   less what the furniture takes — and the card is given that width plus its own
   padding. Stated on the card rather than only on the board so the card ends
   where the board ends, and no white space is left beside it. */
.board-screen-board-fit {
    flex: 0 0 auto;
    margin: 0;
    min-width: 0;
    width: min(100%, calc(100vh - var(--board-chrome) - var(--board-extras) + 32px));
}

.board-fit {
    width: 100%;
    max-width: min(100%, calc(100vh - var(--board-chrome) - var(--board-extras)));
    margin: 0 auto;
}

@media (max-width: 991.98px) {
    .board-screen-board-fit {
        width: 100%;
    }
}

/* The panel. Bounded so it does not sprawl on a wide monitor, and it scrolls
   inside itself rather than letting the page scroll: a leaderboard must never
   push the board out of view. */
.board-screen-side {
    flex: 1;
    min-width: 268px;
    max-width: 520px;
    margin: 0;

    /* On the side itself rather than on a card body inside it, because a side
       is not always one card -- a miniature puts a result box above its move
       list. This way it scrolls either way round. */
    overflow-y: auto;
}

    .board-screen-side > .card-body {
        height: 100%;
    }

/* Stacked, the two are no longer sharing a row, so tying either to the window
   height would only crop it -- the original does the same at index.html line
   328. The board takes the full width and the panel runs on underneath, which
   is the right way round once the board is on top. */
@media (max-width: 991.98px) {
    .board-screen {
        flex-direction: column;
        height: auto;
        min-height: 0;
    }

    .board-screen-board {
        width: 100%;
        height: auto;
        aspect-ratio: auto;
    }

    .board-screen-side {
        max-width: none;
    }

        .board-screen-side > .card-body {
            height: auto;
        }

    .board-screen-side {
        overflow-y: visible;
    }
}


/* The segmented control sharing its row with the dropdown the choice governs
   -- Ders Türü and the group or the private coach, on the student form.

   Apart on three rows of their own they cost the form about ninety pixels and
   pushed the Save button below the fold. Together they also read better: the
   dropdown only means anything once a side has been picked, and now it sits
   where the picking happens. */
.elbe-seg-row {
    display: flex;
    align-items: center;
    gap: 8px;
}

.elbe-seg-row-field {
    flex: 1 1 auto;
    min-width: 0;
}

/* Compact: the buttons hug their words rather than splitting the width between
   them, which leaves the room for the dropdown beside them. */
.elbe-seg-sm {
    flex: 0 0 auto;
}

    .elbe-seg-sm .elbe-seg-btn {
        flex: 0 0 auto;
        padding: 5px 12px;
        font-size: 12.5px;
    }

/* ---- who a repertoire is for -------------------------------------------
   Shared by the editor, which asks while the repertoire is being written,
   and the list, which asks about one that already exists. Checkboxes in a
   wrapping row, as the original had them. */
.op-share-field {
    margin-bottom: 14px;
}

.op-share-list {
    display: flex;
    flex-wrap: wrap;
    gap: 8px 14px;
}

/* The school has 114 students; the list of them has to stop somewhere. */
.op-share-scroll {
    max-height: 210px;
    overflow-y: auto;
    padding-right: 4px;
}

.op-share-box {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: 13px;
    cursor: pointer;
    margin: 0;
}

    .op-share-box input {
        margin: 0;
        flex-shrink: 0;
    }

.op-share-detail {
    font-size: 11px;
    opacity: 0.6;
    margin-left: 5px;
}

/* Who it reaches, at the foot of a card on the list. */
.op-audience {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-wrap: wrap;
    font-size: 12px;
}

/* ---- a board that has to share the window ------------------------------
   `.elbe-board` is width:100% and square, which is right inside a card the
   size of a card and wrong when the column it sits in goes full width: below
   992px the editor's two columns stack, and the board became 830px of squares
   running off the bottom of the window with everything else pushed under it.

   Capped both ways. 480px is the width the board was drawn for; the vh term
   keeps the whole of it on a short screen, and because the cap is on this
   wrapper rather than on the board itself the eight ranks stay square — a
   max-height on the board would fight its aspect-ratio and squash them. */
.elbe-board-fit {
    width: 100%;
    max-width: min(480px, calc(100vh - 250px));
    margin: 0 auto;
}


/* ===== 16. The button, one size =============================

   Asked for in so many words: one size and one shape for every button on a
   signed-in screen, and the colour left to say what the button is for.

   Before this the same row could hold three sizes. Öğretici Minyatürler is the
   screen that prompted it: *Seviyeler* and *PGN ile Ekle* were Bootstrap
   `btn-sm` outlines, beside *Minyatür Ekle*, which is `.btn-admin-primary` at
   10.4px by 24px and 16px lettering — three buttons in a line, no two the same
   height.

   The size taken is the one already used on Bulmaca ve Storm, `.elbe-btn-ghost`
   above: 7px by 12px, 10px corners, 12.5px at 700. That is the original's own
   `btn ghost sm`, so standardising on it moves the application towards
   elbesistem rather than away from it.

   Scoped to `.content-area`, the region `_Shell.cshtml` puts every signed-in
   screen inside. The public site and the top bar are outside it and are left
   alone.

   ...and to `.swal2-popup`, which is the same screen in a box.

   SweetAlert appends its dialog to <body>, so `.content-area` does not reach
   it, and everything sections 16 to 20 take back from the template was handed
   straight back the moment a dialog opened. A Kaydet on the page was brass at
   12.5px; the same Kaydet in a dialog was Bootstrap blue at 16px. Worse where
   the constant is unreadable rather than merely wrong: `.badge.bg-success` fell
   back to white on adminlayout's green gradient, 3.4:1, and the dialogs are
   where the school reads what it is about to delete.

   The two are named together rather than the rules being copied into
   dialogs.css, so a colour taken back from Bootstrap is taken back once and
   holds wherever the control appears. `:is()` keeps the specificity of the
   single class it replaces, so nothing below reorders.

   Not the Bootstrap modal: it draws its surface from the theme's own card
   colour and its lettering is the theme's, so it is correct already —
   dialogs.css section 7 records the same exception for the same reason. */

:is(.content-area, .swal2-popup) .btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    /* Buttons very often sit in a `d-flex` row beside a field or a tab strip,
       and a flex item stretches to the tallest thing in the row by default —
       which is how *Öğrenci Ekle* came out four pixels taller than the same
       button on the screen next door. The size is the size. */
    align-self: center;
    gap: 7px;
    min-height: 32px;
    padding: 7px 12px;
    border-radius: 10px;
    border: 1px solid transparent;
    font-size: 12.5px;
    font-weight: 700;
    line-height: 1.2;
    white-space: nowrap;
    transition: 0.16s;
}

/* Bootstrap's three sizes all resolve to the one size. Answered here rather
   than swept out of the views: `btn-sm` is on 197 elements and `btn-lg` on
   three, and a class that no longer changes anything is harmless where finding
   every one of them is not. */
:is(.content-area, .swal2-popup) .btn-sm,
:is(.content-area, .swal2-popup) .btn-lg {
    min-height: 32px;
    padding: 7px 12px;
    border-radius: 10px;
    font-size: 12.5px;
}

/* The gap above spaces the icon from the word. Nearly every button in the
   views also carries `me-1` or `me-2` on its icon, from before there was a
   gap, and those are Bootstrap utilities — `!important`, so nothing short of
   `!important` takes them off again. */
:is(.content-area, .swal2-popup) .btn > i,
:is(.content-area, .swal2-popup) .btn > .bi,
:is(.content-area, .swal2-popup) .btn > svg {
    margin: 0 !important;
    font-size: 13.5px;
    line-height: 1;
}

/* An icon on its own wants a square, not a wide slab. */
:is(.content-area, .swal2-popup) .btn.btn-icon {
    padding: 7px;
    min-width: 32px;
}

/* Grouped buttons still have to read as one control: Bootstrap squares off
   the inner corners and the 10px above would undo it. Logical corners, because
   three of the site's five languages run right to left. */
:is(.content-area, .swal2-popup) .btn-group > .btn:not(:first-child) {
    border-start-start-radius: 0;
    border-end-start-radius: 0;
}

:is(.content-area, .swal2-popup) .btn-group > .btn:not(:last-child) {
    border-start-end-radius: 0;
    border-end-end-radius: 0;
}

/* --- what the colour says ------------------------------------

   The shape is settled above; what is left to a button is what it is for.
   Bootstrap's own names are kept — `btn-outline-danger` is still the red one —
   so no view has to be rewritten to get its colour, and a screen written
   tomorrow gets it by habit.

       primary / admin-primary   the brass fill: the thing the screen is for.
                                 One to a screen.
       outline-primary           brass, unfilled: the same kind of action when
                                 it is not the main one.
       outline-secondary         the ghost: everything ordinary.
       danger / outline-danger   deleting, and nothing else.
       success / warning / info  a state or a category, not a verb.

   Contrast is measured on `#FBF9F3`, the card these sit on. */

/*  Written once, in the palette's names, so a theme gets its buttons by being
    a theme. This was two blocks -- one under `body.theme-elbe` and one under
    `body.theme-brass`, the same eleven roles stated twice in different values.
    The six other themes matched neither block and fell through to Bootstrap's
    own colours, which is why a secondary button on Ocean came out in a blue
    that appears nowhere else on the screen.

    Outlines take the lettering colour of their meaning and a border mixed from
    it. Fills take the colour itself with white on top -- the four fill hues are
    the same on every theme, so white stays legible on them. */

:is(.content-area, .swal2-popup) .btn-outline-secondary,
:is(.content-area, .swal2-popup) .btn-secondary,
:is(.content-area, .swal2-popup) .btn-admin-secondary,
:is(.content-area, .swal2-popup) .btn-light,
:is(.content-area, .swal2-popup) .btn-outline-light {
    background: transparent;
    color: var(--theme-text);
    border-color: color-mix(in srgb, var(--theme-text) 20%, transparent);
}

:is(.content-area, .swal2-popup) .btn-outline-secondary:hover,
:is(.content-area, .swal2-popup) .btn-secondary:hover,
:is(.content-area, .swal2-popup) .btn-admin-secondary:hover,
:is(.content-area, .swal2-popup) .btn-light:hover,
:is(.content-area, .swal2-popup) .btn-outline-light:hover {
    background: var(--theme-hover-bg);
    color: var(--theme-text);
    border-color: color-mix(in srgb, var(--theme-text) 34%, transparent);
}

/* Primary and warning are both the brass role: the affirmative. */
:is(.content-area, .swal2-popup) .btn-outline-primary,
:is(.content-area, .swal2-popup) .btn-outline-warning {
    background: transparent;
    color: var(--elbe-brass-text);
    border-color: color-mix(in srgb, var(--elbe-brass-text) 50%, transparent);
}

:is(.content-area, .swal2-popup) .btn-outline-primary:hover,
:is(.content-area, .swal2-popup) .btn-outline-warning:hover {
    background: color-mix(in srgb, var(--elbe-brass-text) 12%, transparent);
    color: var(--elbe-brass-text);
    border-color: color-mix(in srgb, var(--elbe-brass-text) 85%, transparent);
}

:is(.content-area, .swal2-popup) .btn-outline-danger {
    background: transparent;
    color: var(--elbe-wine-text);
    border-color: color-mix(in srgb, var(--elbe-wine-text) 45%, transparent);
}

:is(.content-area, .swal2-popup) .btn-outline-danger:hover {
    background: color-mix(in srgb, var(--elbe-wine-text) 12%, transparent);
    color: var(--elbe-wine-text);
    border-color: color-mix(in srgb, var(--elbe-wine-text) 75%, transparent);
}

:is(.content-area, .swal2-popup) .btn-outline-success {
    background: transparent;
    color: var(--elbe-felt-text);
    border-color: color-mix(in srgb, var(--elbe-felt-text) 45%, transparent);
}

:is(.content-area, .swal2-popup) .btn-outline-success:hover {
    background: color-mix(in srgb, var(--elbe-felt-text) 12%, transparent);
    color: var(--elbe-felt-text);
}

:is(.content-area, .swal2-popup) .btn-outline-info {
    background: transparent;
    color: var(--elbe-sky-text);
    border-color: color-mix(in srgb, var(--elbe-sky-text) 45%, transparent);
}

:is(.content-area, .swal2-popup) .btn-outline-info:hover {
    background: color-mix(in srgb, var(--elbe-sky-text) 12%, transparent);
    color: var(--elbe-sky-text);
}

/* The filled pair. Their hue does not move between themes -- a delete button
   is the same red on ivory and on wood -- so the white on them holds. */
:is(.content-area, .swal2-popup) .btn-danger {
    background: var(--elbe-wine);
    color: #FFFFFF;                             /* 4.6:1 */
    border-color: transparent;
}

:is(.content-area, .swal2-popup) .btn-danger:hover {
    background: #BE3123;
    color: #FFFFFF;
}

:is(.content-area, .swal2-popup) .btn-success {
    background: var(--elbe-felt);
    color: #FFFFFF;
    border-color: transparent;
}


/* Disabled says so by fading, not by changing shape. */
:is(.content-area, .swal2-popup) .btn:disabled,
:is(.content-area, .swal2-popup) .btn.disabled {
    opacity: 0.45;
    box-shadow: none;
}

:is(.content-area, .swal2-popup) .btn:focus-visible {
    outline: 2px solid var(--theme-accent);
    outline-offset: 2px;
    box-shadow: none;
}


/* ===== 17. Pagers ===========================================

   Bootstrap's pager is a strip of #0d6efd links. Nothing else in this shell
   is that blue, and on the ivory page it reads as somebody else's control
   dropped into the middle of a screen. It also states its colours outright,
   so on the four dark themes it stayed white-on-white.

   Everything below comes from the theme: the page one is on is filled with
   the theme's accent, the rest are quiet until pointed at. Sized to match
   section 16's button, so a row of pager buttons and a real button standing
   next to each other are the same height. */

:is(.content-area, .swal2-popup) .pagination {
    --bs-pagination-padding-x: 10px;
    --bs-pagination-padding-y: 4px;
    --bs-pagination-font-size: 12.5px;
    --bs-pagination-border-radius: 9px;
    gap: 4px;
}

:is(.content-area, .swal2-popup) .page-link {
    min-width: 32px;
    min-height: 32px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-weight: 600;
    border-radius: 9px;
    border: 1px solid var(--theme-border);
    background: var(--theme-card-bg);
    color: var(--theme-text);
    transition: background 0.14s ease, border-color 0.14s ease;
}

:is(.content-area, .swal2-popup) .page-link:hover {
    background: var(--theme-hover-bg);
    border-color: var(--theme-accent);
    color: var(--theme-text);
}

/* The page one is on is marked by the brass ring and the wash behind it, and
   keeps the ordinary ink for its number. Colouring the number brass instead
   reads well on the dark themes and only 4.2:1 on ivory -- and a page number
   is the one thing on a pager that has to be legible. */
:is(.content-area, .swal2-popup) .page-item.active .page-link {
    background: var(--theme-active-bg);
    border-color: var(--theme-accent);
    color: var(--theme-text);
    box-shadow: inset 0 0 0 1px var(--theme-accent);
}

:is(.content-area, .swal2-popup) .page-item.disabled .page-link {
    background: transparent;
    border-color: var(--theme-border);
    color: var(--theme-text-muted);
    opacity: 0.5;
}

:is(.content-area, .swal2-popup) .page-link:focus,
:is(.content-area, .swal2-popup) .page-link:focus-visible {
    box-shadow: none;
    outline: 2px solid var(--theme-accent);
    outline-offset: 2px;
}


/* ===== 17b. The student's home pictures (Site settings) ================

   Four screenshots, one chosen. The same ring-and-tick language as the theme
   cards below it, but the card holds a picture rather than a swatch. Colours
   are the theme's own; the picture supplies its own. */
.shd-grid {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 14px;
}
@media (max-width: 1199.98px) { .shd-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

.shd-card {
    position: relative;
    border: 2px solid var(--elbe-line);
    border-radius: 12px;
    padding: 8px;
    background: var(--theme-card-grad);
    color: var(--theme-text);
    opacity: 0.78;
    transition: border-color 0.16s, box-shadow 0.16s, transform 0.16s, opacity 0.16s;
}
.shd-card:hover { opacity: 1; border-color: color-mix(in srgb, var(--theme-brass) 45%, transparent); transform: translateY(-1px); }
.shd-card input[type="radio"] { position: absolute; opacity: 0; pointer-events: none; }
.shd-card:has(input:checked) {
    opacity: 1;
    border-color: var(--theme-brass);
    box-shadow: 0 0 0 3px var(--theme-wash-22), 0 10px 22px -12px rgba(0, 0, 0, 0.5);
    transform: translateY(-2px);
}
.shd-card:has(input:checked)::after {
    content: "\2713";
    position: absolute;
    top: -9px;
    inset-inline-end: -9px;
    width: 22px;
    height: 22px;
    border-radius: 999px;
    background: var(--theme-brass);
    color: var(--theme-on-brass, #1C1814);
    display: grid;
    place-items: center;
    font-size: 12px;
    font-weight: 700;
    line-height: 1;
}
.shd-pic {
    display: block;
    width: 100%;
    padding: 0;
    margin: 0;
    border: 1px solid var(--elbe-line);
    border-radius: 8px;
    overflow: hidden;
    background: #1C1814;   /* the pictures are dark-themed; a dark mount shows no white edge */
    color: var(--theme-text);
    cursor: zoom-in;
}
.shd-pic img { display: block; width: 100%; height: auto; }
.shd-name { display: flex; align-items: center; gap: 8px; margin-top: 8px; font-weight: 600; font-size: 0.9rem; color: var(--theme-text); }
.shd-num {
    width: 22px; height: 22px; border-radius: 999px;
    background: var(--theme-brass); color: var(--theme-on-brass, #1C1814);
    display: grid; place-items: center; font-size: 12px; font-weight: 800;
}
/* The dialog: a wide picture, the title in the card's ink, two tabs above it. */
.swal2-popup.shd-swal .swal2-html-container { margin: 8px 12px 0; }
/* One row that scrolls sideways when the tabs outgrow the dialog (Kamal, 2026-09-08):
   every tab keeps its full wording, nothing shrinks or wraps. Centred while they fit. */
.shd-tabs { display: flex; gap: 8px; justify-content: safe center; margin-bottom: 10px; flex-wrap: nowrap; overflow-x: auto; overflow-y: hidden; padding: 2px 2px 6px; scrollbar-width: thin; scroll-behavior: smooth; }
.shd-tab {
    flex: 0 0 auto; white-space: nowrap;
    padding: 7px 16px; border-radius: 999px; font-weight: 700; font-size: 0.9rem; cursor: pointer;
    border: 1px solid var(--elbe-line); background: var(--bs-tertiary-bg); color: var(--theme-text);
}
.shd-tab:hover { background: var(--theme-hover-bg); color: var(--theme-text); }
.shd-tab.on { background: var(--theme-brass); color: var(--theme-on-brass, #1C1814); border-color: transparent; }

/* ===== 18. The theme picker =================================

   Two sections on Site settings, one light and one dark, and a card per theme.

   Each card wears the theme it offers -- `.thm-card.theme-ocean` -- so the
   swatch inside it paints itself from that theme's own variables. Nothing here
   states a colour. A ninth theme is a palette in theme-base.css and a line in
   Helpers/Themes.cs; this file does not need to hear about it. */
.thm-side {
    border: 1px solid var(--elbe-line);
    border-radius: var(--elbe-r);
    padding: 14px;
    height: 100%;
}

.thm-side-head {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 11px;
    letter-spacing: 1.2px;
    text-transform: uppercase;
    font-weight: 700;
    color: var(--theme-text-muted);
    margin-bottom: 12px;
}

.thm-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(148px, 1fr));
    gap: 10px;
}

/* The card is the theme, so the swatch below reads that theme's variables. */
.thm-card {
    display: block;
    position: relative;        /* the tick hangs off this */
    cursor: pointer;
    border: 2px solid var(--elbe-line);
    border-radius: 11px;
    padding: 8px;
    background: var(--theme-card-grad);
    transition: border-color 0.16s, box-shadow 0.16s, transform 0.16s, opacity 0.16s;
    margin: 0;

    /* Unchosen cards stand back a little so the chosen one carries. */
    opacity: 0.72;
}

    .thm-card:hover {
        opacity: 1;
        border-color: color-mix(in srgb, var(--theme-brass) 45%, transparent);
        transform: translateY(-1px);
    }

    /*  Chosen.

        Written against `:has(input:checked)`, not a class the server puts on.
        The class was the whole of it before, so the radio changed on a click
        and nothing on the screen did -- which reads as the card not being
        selectable at all.

        And it is only `:has()`. Keeping the class beside it as a fallback lit
        two cards in the same group at once: the class stayed where the server
        had put it while `:has()` followed the click. The radio carries
        `checked` from the server, so `:has()` is right at first paint too and
        the class has nothing left to add.

        The theme's own accent, so each card is ringed in its own colours. */
    .thm-card:has(input:checked) {
        opacity: 1;
        border-color: var(--theme-brass);
        box-shadow: 0 0 0 3px var(--theme-wash-22), 0 10px 22px -12px rgba(0, 0, 0, 0.5);
        transform: translateY(-2px);
    }

    /* A tick in the corner, because a ring alone is easy to miss on a page of
       eight cards that are all already coloured. */
    .thm-card:has(input:checked)::after {
        content: "✓";
        position: absolute;
        top: -9px;
        inset-inline-end: -9px;
        width: 22px;
        height: 22px;
        border-radius: 999px;
        background: var(--theme-brass);
        color: var(--theme-on-brass);
        display: grid;
        place-items: center;
        font-size: 12px;
        font-weight: 700;
        line-height: 1;
        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.35);
    }

    /* Off screen rather than merely invisible: a 13px box sitting at the top
       of the page under `position: absolute` with nothing positioned above it
       is a stray click target in the middle of the form. */
    .thm-card input {
        position: absolute;
        inset-inline-start: -9999px;
        opacity: 0;
        pointer-events: none;
    }

/* A sidebar, a top bar and two lines of content -- the shape of the site in
   36 pixels. */
.thm-swatch {
    display: flex;
    height: 46px;
    border-radius: 7px;
    overflow: hidden;
    background: var(--bs-body-bg);
    border: 1px solid var(--elbe-line);
}

.thm-swatch-side {
    width: 26%;
    background: var(--theme-sidebar-grad);
    border-inline-end: 1px solid var(--elbe-line);
}

.thm-swatch-body { flex: 1; padding: 5px 6px; }

.thm-swatch-bar {
    display: block;
    height: 7px;
    border-radius: 3px;
    background: var(--theme-accent-grad);
    margin-bottom: 5px;
    width: 58%;
}

.thm-swatch-line {
    display: block;
    height: 4px;
    border-radius: 2px;
    background: var(--theme-text);
    opacity: 0.32;
    margin-bottom: 4px;
}

    .thm-swatch-line.short { width: 62%; }

.thm-name {
    display: block;
    margin-top: 7px;
    font-size: 11.5px;
    font-weight: 600;
    color: var(--theme-text);
    text-align: center;
    line-height: 1.3;
}


/* ===== 19. Taking back the template's fixed colours =========

   Bootstrap ships a handful of colours as constants -- `.text-secondary` is
   #6c757d whatever is behind it, `code` is pink, `.text-muted` is grey. They
   were chosen for a white page and they are the same on all eight themes, so
   each one is a contradiction waiting on the theme that does not suit it.
   Measured on the school's dark: `.text-secondary` came out 3.29:1.

   Given the theme's own muted ink instead, which every palette measures. */
:is(.content-area, .swal2-popup) .text-secondary,
:is(.content-area, .swal2-popup) .text-muted,
:is(.content-area, .swal2-popup) small.text-muted {
    color: var(--theme-text-muted) !important;
}

:is(.content-area, .swal2-popup) code:not([class]) {
    color: var(--elbe-brass-text);
    background: var(--theme-wash-06);
    padding: 1px 5px;
    border-radius: 5px;
}

/* A menu heading on a light sidebar was 4.36:1 -- under the line by a hair,
   and it is the one piece of text on the sidebar nobody can enlarge. */
.nav-section,
.sidebar-section {
    color: var(--theme-sidebar-heading);
}

/* Bootstrap's alerts paint a background but take their lettering from whatever
   they inherit, so the muted override above landed inside one and wrote a grey
   on a dark amber -- 4.29:1. Each says its own ink now, against its own
   background, on every theme. */
:is(.content-area, .swal2-popup) .alert-warning,
:is(.content-area, .swal2-popup) .alert-primary,
:is(.content-area, .swal2-popup) .alert-secondary {
    color: var(--theme-text) !important;
}

:is(.content-area, .swal2-popup) .alert-danger { color: var(--elbe-wine-text) !important; }
:is(.content-area, .swal2-popup) .alert-success { color: var(--elbe-felt-text) !important; }
:is(.content-area, .swal2-popup) .alert-info { color: var(--elbe-sky-text) !important; }


/* ===== 20. Badges, in the theme's own colours ===============

   `adminlayout.css` paints Bootstrap's badges as fixed gradients -- a green,
   a red, an amber -- with white lettering, chosen once for a white page and
   `!important` so nothing can reason with them. White on that green is 3.4:1,
   and `.bg-primary` takes the theme's accent, which on Okyanus is a bright
   mint: white on it is 1.25:1.

   Given the same treatment `.elbe-tag` already has: the colour as lettering
   over a wash of itself. Both halves come from the theme, and both are
   measured there against the card, so a badge cannot be unreadable on a theme
   without that theme's own tag colours being wrong first. */
:is(.content-area, .swal2-popup) .badge.bg-success {
    background: color-mix(in srgb, var(--elbe-felt-text) 15%, transparent) !important;
    color: var(--elbe-felt-text) !important;
}

:is(.content-area, .swal2-popup) .badge.bg-danger {
    background: color-mix(in srgb, var(--elbe-wine-text) 15%, transparent) !important;
    color: var(--elbe-wine-text) !important;
}

:is(.content-area, .swal2-popup) .badge.bg-info {
    background: color-mix(in srgb, var(--elbe-sky-text) 15%, transparent) !important;
    color: var(--elbe-sky-text) !important;
}

/* =========================================================================
   Touch screens: a tap is a click — never a hover, never a select.
   touch-tap-fix.js cuts the :hover rules out of the stylesheets on these
   devices; the rules here stop the other two impostors — the tap flash
   and accidental text selection. Typing and reading stay selectable.
   ========================================================================= */
/*  Any screen a finger is used on — a phone that reports a hover because a
    stylus or a mouse is paired with it still needs these (Kamal, 2026-08-28). */
@media (hover: none), (pointer: coarse) {
    * { -webkit-tap-highlight-color: transparent; }

    body {
        -webkit-user-select: none;
        user-select: none;
    }

    input, textarea, select, [contenteditable],
    .chat-messages, .chat-text, .swal2-html-container,
    .tr-notation-body, pre, code {
        -webkit-user-select: text;
        user-select: text;
    }
}

:is(.content-area, .swal2-popup) .badge.bg-warning,
:is(.content-area, .swal2-popup) .badge.bg-primary {
    background: color-mix(in srgb, var(--elbe-brass-text) 15%, transparent) !important;
    color: var(--elbe-brass-text) !important;
}

:is(.content-area, .swal2-popup) .badge.bg-secondary {
    background: color-mix(in srgb, var(--elbe-ivory-dim) 13%, transparent) !important;
    color: var(--elbe-ivory-dim) !important;
}

/* Bootstrap's own text helpers, which are the same six constants in badge
   form and fail in the same places. */
:is(.content-area, .swal2-popup) .text-success { color: var(--elbe-felt-text) !important; }
:is(.content-area, .swal2-popup) .text-danger { color: var(--elbe-wine-text) !important; }
:is(.content-area, .swal2-popup) .text-info { color: var(--elbe-sky-text) !important; }
:is(.content-area, .swal2-popup) .text-warning { color: var(--elbe-brass-text) !important; }
:is(.content-area, .swal2-popup) .text-primary { color: var(--elbe-brass-text) !important; }

/* ===== 21. Signing in, and joining ==========================
   These rules live here rather than in `landing.css` because the folder they
   dress is not purely a signed-out one. `AccessDenied` is reached by somebody
   already signed in far more often than by a stranger, and that renders inside
   the member shell, which never loads the landing page's stylesheet. Anything
   built only there would arrive unstyled for exactly the people most likely to
   see it.

   So the card is defined for both shells, and only the picture beside it is
   scoped to the signed-out one. */
.auth {
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 26px 18px;
}

/*  The picture stands beside the card, not behind it.

    `login-hero.jpg` has the original's own sign-in panel painted into it -- a
    blurred glass rectangle across the middle third, from 36.5% to 63.3% of the
    width. Laid full-bleed behind our card, that panel showed all round it: our
    card is 396px where the painted one is nearer 470, so a pale halo framed
    every one of these screens and looked like a mistake, because it was one.

    `auth-side.jpg` is the picture's left half, cut clear of that panel at
    596px. It stands as its own composition -- the boy on the steps, the king,
    the sun -- and at roughly half the window it is shown near its own size
    rather than blown up two and a half times, so the drawing stays sharp. */
body.elbe-public .auth {
    padding: 0;
    align-items: stretch;
    background: var(--bs-body-bg);
}

    body.elbe-public .auth::before {
        content: "";
        flex: 0 0 44%;
        max-width: 640px;
        /* A breath of the page's own colour over it, so a bright drawing sits
           beside a dark panel as one screen rather than two. Barely there on a
           light theme; on a dark one it settles the picture down. */
        background:
            linear-gradient(180deg,
                color-mix(in srgb, var(--bs-body-bg) 10%, transparent),
                color-mix(in srgb, var(--bs-body-bg) 26%, transparent)),
            url('../img/auth-side.jpg') center / cover no-repeat;
        border-inline-end: 1px solid var(--elbe-line);
    }

    /* `auto` on every side centres the card in whatever the picture leaves. The
       width keeps a gutter at any window rather than letting the card shrink
       until it touches the edge. */
    body.elbe-public .auth > .auth-card {
        margin: auto;
        width: min(396px, 100% - 56px);
    }

    body.elbe-public .auth > .auth-card.is-wide { width: min(540px, 100% - 56px); }

/* Narrow enough that the picture would leave the card no room: the card takes
   the window on the theme's own ground. */
@media (max-width: 900px) {
    body.elbe-public .auth { padding: 26px 18px; }

    body.elbe-public .auth::before { display: none; }

    body.elbe-public .auth > .auth-card { width: min(470px, 100%); }
}

/* The card is measured to stand inside the window on its own. A sign-in form
   that has to be scrolled to reach its own button is the one screen where that
   is least forgivable -- it is the first thing anybody sees. So everything in
   it is tightened: labels small, rows paired, nothing loose. */
.auth-card {
    width: 100%;
    max-width: 396px;
    background: var(--theme-card-bg);
    border: 1px solid var(--elbe-line);
    border-top: 3px solid var(--theme-brass);
    border-radius: 14px;
    box-shadow: 0 18px 44px rgba(0, 0, 0, 0.16);
    padding: 26px 26px 22px;
}

    /* Joining needs six fields where signing in needs two, so it is paired into
       columns and given a little more room to do it in. */
    .auth-card.is-wide { max-width: 540px; }

.auth-head { text-align: center; margin-bottom: 16px; }

.auth-pawn { line-height: 1; }
.auth-pawn .elbe-wordmark { height: 30px; }

.auth-title {
    font-family: var(--elbe-serif);
    font-size: 22px;
    font-weight: 700;
    color: var(--theme-text);
    margin: 5px 0 0;
}

.auth-sub {
    font-size: 12.5px;
    line-height: 1.5;
    color: var(--theme-text-muted);
    margin: 5px 0 0;
}

.auth-card .form-label {
    font-size: 12px;
    font-weight: 600;
    color: var(--theme-text-muted);
    margin-bottom: 3px;
}

.auth-card .form-control {
    min-height: 38px;
    padding: 6px 11px;
    font-size: 14px;
}

.auth-card .alert {
    padding: 8px 12px;
    font-size: 13px;
    margin-bottom: 12px;
}

.auth-card .btn-primary {
    min-height: 42px;
    font-size: 14.5px;
    font-weight: 600;
}

.auth-foot {
    text-align: center;
    font-size: 12.5px;
    color: var(--theme-text-muted);
    margin: 13px 0 0;
}

    .auth-foot a { color: var(--elbe-brass-text); font-weight: 600; text-decoration: none; }
    .auth-foot a:hover { text-decoration: underline; }

/* The captcha, tightened to one row. */
.auth-card .captcha-question {
    font-weight: 700;
    font-size: 15px;
    color: var(--theme-text);
    white-space: nowrap;
}

.auth-card .captcha-input { max-width: 118px; }

/* The message screens -- verified, expired, no permission. Same card, one
   symbol and a way onward. */
.auth-mark { font-size: 40px; line-height: 1; }

.auth-note { font-size: 13.5px; line-height: 1.6; color: var(--theme-text-muted); margin: 9px 0 0; }

@media (max-width: 480px) {
    body.elbe-public .auth { padding: 18px 14px; }
    .auth-card { padding: 20px 18px 18px; }
}


/* ===== 19. Who is online, on the top bar ====================================

   The list of people who are here, dropped from the chat icon on every screen.

   It exists because there was nowhere else to see it. The chat moved into a
   window of its own, and with it went the only list of who was online — so the
   question "is anybody about" could only be answered by opening a window, and
   staff, whose sidebar has never carried a chat row, could not answer it at all.

   Every colour here is the theme's own rather than a fixed value, because unlike
   the chat window — which is deliberately one ivory surface on all nine themes —
   this sits on the page and must belong to whichever theme is on. The three that
   carry text are each defined per theme against that theme's card:

     --theme-text        the name.   16.4:1 on ivory, 13.4:1 on Blue Sky
     --elbe-ivory-dim    the role.   held at 7:1 by every theme, not 4.5
     --elbe-line         the edges.  dark on the light themes, light on the dark

   The panel is the same colour as the bar it hangs from, so what separates them
   is the hairline and the shadow, not a change of ground. */

.topbar-presence {
    position: relative;
}

/*  Somebody is here. Only that — it carries no number, so it needs no reading,
    which is the whole point of a dot. Placed opposite the unread badge so the
    two cannot be mistaken for each other. */
.topbar-online-dot {
    position: absolute;
    left: 3px;
    bottom: 3px;
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--elbe-felt);
    box-shadow: 0 0 0 2px var(--theme-card-bg-solid);
}

[dir="rtl"] .topbar-online-dot {
    left: auto;
    right: 3px;
}

.presence-panel {
    position: absolute;
    top: calc(100% + 8px);
    inset-inline-end: 0;
    z-index: 1040;
    width: 290px;
    max-height: 60vh;
    display: flex;
    flex-direction: column;
    background: var(--theme-card-bg);
    border: 1px solid var(--elbe-line);
    border-radius: 12px;
    box-shadow: var(--elbe-shadow);
    overflow: hidden;
}

.presence-panel[hidden] {
    display: none;
}

.presence-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    padding: 9px 12px;
    border-bottom: 1px solid var(--elbe-line);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 1px;
    text-transform: uppercase;
    color: var(--elbe-ivory-dim);
}

.presence-count {
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0;
    padding: 1px 7px;
    border-radius: 999px;
    background: var(--theme-wash-16);
    color: var(--elbe-brass-text);
}

.presence-list {
    flex: 1 1 auto;
    overflow-y: auto;
    padding: 4px;
}

.presence-empty {
    padding: 18px 12px;
    text-align: center;
    font-size: 12.5px;
    color: var(--elbe-ivory-dim);
}

/*  A row is a button: the whole of it is the target, because a name is easier to
    aim at than an icon beside it. */
.presence-row {
    display: flex;
    align-items: center;
    gap: 9px;
    width: 100%;
    padding: 7px 9px;
    border: none;
    border-radius: 9px;
    background: transparent;
    color: var(--theme-text);
    text-align: start;
    cursor: pointer;
    transition: background 0.14s;
}

.presence-row:hover {
    background: var(--theme-hover-bg);
}

/*  Somebody who has asked not to be sent chat requests. Still shown -- knowing
    they are here is worth something -- but plainly not to be disturbed, and it
    says why on the row rather than leaving the reader to be refused and guess.
    Faded rather than hidden, and the fade is gentle enough that the name is
    still read comfortably: 7.4:1 at 0.55 on the ivory card. */
.presence-row[data-unavailable] {
    opacity: 0.55;
}

.presence-dot {
    flex: 0 0 auto;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--elbe-felt);
}

.presence-who {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 1px;
}

.presence-name {
    font-size: 13px;
    font-weight: 600;
    line-height: 1.25;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.presence-role {
    font-size: 11px;
    line-height: 1.25;
    color: var(--elbe-ivory-dim);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.presence-go {
    flex: 0 0 auto;
    font-size: 13px;
    color: var(--elbe-brass-text);
    opacity: 0;
    transition: opacity 0.14s;
}

/* A finger cannot hover — the go-mark shows from the start on touch. */
@media (hover: none) {
    .presence-go { opacity: 1; }
}

.presence-row:hover .presence-go {
    opacity: 1;
}

.presence-open-chat {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 7px;
    padding: 9px 12px;
    border: none;
    border-top: 1px solid var(--elbe-line);
    background: transparent;
    color: var(--elbe-brass-text);
    font-size: 12.5px;
    font-weight: 700;
    cursor: pointer;
}

.presence-open-chat:hover {
    background: var(--theme-hover-bg);
}

/* "Add a conference" under "Open the chat" (Kamal, 2026-09-11): the same row, its own line above. */
.presence-add-conference { border-top: 1px dashed var(--elbe-line); }

/*  On a phone the bar is narrow and a 290px panel pinned to one end would hang
    off the screen. Pinned to both instead, which also makes it easier to hit. */
@media (max-width: 576px) {
    .presence-panel {
        position: fixed;
        top: calc(var(--navbar-height) + 6px);
        inset-inline: 8px;
        width: auto;
    }
}


/* ===== 20. The radio's channel list ========================================

   Brought over from MyChessClub, where this list reads well: a scrollable panel,
   the stations gathered under their language, a heading that stays put while you
   scroll past it, and rows that plainly respond to being pointed at.

   Nothing about the markup needed changing to get it. `radio-player.js` has always
   grouped the stations by language and has always emitted these three classes --
   `radio-language-group`, `radio-language-header`, `radio-channel-item`, the same
   names MyChessClub styles. What was missing was any rule for them: the only
   thing styled here was the panel's size and a `.dropdown-item` rule that matches
   nothing the player writes. So the grouping existed in the markup and was
   invisible on the screen, which is a good deal less obvious than it sounds.

   Written against the theme's own variables rather than fixed colours. The list
   is part of the page, so it must belong to whichever of the nine themes is on --
   and the panel is a Bootstrap dropdown, whose own background follows the light or
   dark side, so it is stated here instead and both halves of every pair are ours.

   Each of these is defined per theme against that theme's card:
     --theme-text        the station's name
     --elbe-ivory-dim    the language heading, held at 7:1 rather than 4.5
     --elbe-brass-text   the two-letter language mark, the accent AS TEXT
     --theme-brass /
       --theme-on-brass  the station now playing, and the ink that reads on it  */

.radio-channels-dropdown {
    min-width: 232px;
    max-height: 320px;
    overflow-y: auto;
    overscroll-behavior: contain;      /* the page behind does not scroll on */

    padding: 0;                        /* the groups run to the edges */
    background: var(--theme-card-bg);
    border: 1px solid var(--elbe-line);
    border-radius: 12px;
    box-shadow: var(--elbe-shadow);
}

/*  A thin bar rather than the platform's, which is half the width of the list
    itself on Windows. */
.radio-channels-dropdown::-webkit-scrollbar {
    width: 7px;
}

.radio-channels-dropdown::-webkit-scrollbar-thumb {
    background: var(--elbe-line);
    border-radius: 4px;
}

.radio-language-group + .radio-language-group {
    border-top: 1px solid var(--elbe-line);
}

/*  Which language you are looking at, and it stays while you scroll past it —
    which is the whole reason to group a list of thirty-six things. */
.radio-language-header {
    position: sticky;
    top: 0;
    z-index: 1;

    padding: 7px 12px;
    background: var(--theme-bg-secondary);
    border-bottom: 1px solid var(--elbe-line);

    font-size: 10px;
    font-weight: 700;
    letter-spacing: 1.1px;
    text-transform: uppercase;
    color: var(--elbe-ivory-dim);
}

.radio-channel-item {
    display: flex;
    align-items: center;
    gap: 9px;

    /*  Room to breathe, and room to hit. Thirty-six stations in a list want to be
        scannable and want to be aimed at, and 8px of vertical padding on a 13px
        line gives a 33px row — comfortable with a mouse, reachable on a tablet. */
    padding: 8px 12px;

    font-size: 13px;
    color: var(--theme-text);
    cursor: pointer;
    transition: background 0.14s, color 0.14s;
}

.radio-channel-item:hover {
    background: var(--theme-hover-bg);
}

/*  The one playing. The theme's accent as a fill, with the ink each theme defines
    to be read on that fill — the one pair that must not be guessed at, since a
    bright accent takes dark ink and a deep one takes light. */
.radio-channel-item.active {
    background: var(--theme-brass);
    color: var(--theme-on-brass);
    font-weight: 600;
}

.radio-channel-item .channel-icon {
    flex: 0 0 auto;
    font-size: 14px;
    line-height: 1;
}

/*  Which language the station is in, for when the heading has scrolled away. */
.radio-channel-item .channel-lang {
    flex: 0 0 auto;
    min-width: 20px;
    font-size: 9.5px;
    font-weight: 700;
    letter-spacing: 0.4px;
    color: var(--elbe-brass-text);
}

.radio-channel-item.active .channel-lang {
    color: var(--theme-on-brass);
    opacity: 0.8;
}

.radio-channel-item .channel-name {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/*  The Android app's download button wears the same shape as the bar's other
    icons, but it is an anchor rather than a <button> — so it needs the
    underline taken off, and its colour restated: an <a> does not inherit the
    class's colour through the browser's own link rule. Same ivory as its
    neighbours, 7.1:1 on the bar. */
a.topbar-btn { text-decoration: none; color: var(--elbe-ivory-dim); }
a.topbar-btn:hover { color: var(--theme-text); }

/* =========================================================================
   A screen that takes the whole phone
   -------------------------------------------------------------------------
   Some screens are the reason a child opened the app — a puzzle, a lesson, a
   game in the hall — and the row of shortcuts along the foot is 56 points of
   somebody else's business across them. On those, the page says so with
   `elbe-fullview` on the body: the shortcuts go, and their reserved strip
   with them.

   Every such screen carries a door of its own instead — the gate icon in its
   top right, which asks before it leaves (Kamal, 2026-08-28).
   ========================================================================= */
body.elbe-fullview .mobile-bottomnav { display: none !important; }
body.elbe-fullview { padding-bottom: env(safe-area-inset-bottom, 0px) !important; }

/*  The door itself. Same size and shape as the icons it stands beside, in the
    theme's own wine so it reads as "out" rather than as one more tool —
    #9B2A1D on the light card is 6.1:1, #F58B7C on the dark one 7.2:1, the
    pair every warning on this site uses. */
.elbe-exit-btn {
    color: var(--elbe-wine-text);
    border-color: var(--elbe-wine-text);
}


/* =====================================================================
   "Logging you in as …" — the popup that holds the screen while the
   sign-in goes off (Kamal, 4 September 2026). It sits on the site's
   dialog surface, white to ivory in both themes (dialogs.css), so its
   inks are chosen once against white: #1C1814 (16:1), #5E584C (7:1),
   and the brass-as-text #8A6A00 (5.1:1) for the pawn and the bar.
   ===================================================================== */
.login-wait {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    padding: 8px 0 4px;
    text-align: center;
}

.login-wait-pawn {
    font-size: 44px;
    line-height: 1;
    color: #8A6A00;
    animation: login-wait-bob 1.1s ease-in-out infinite;
}

.login-wait-title {
    font-family: var(--elbe-serif, Georgia, serif);
    font-size: 1.25rem;
    font-weight: 700;
    color: #1C1814;
    line-height: 1.25;
}

.login-wait-who {
    font-size: .95rem;
    font-weight: 600;
    color: #5E584C;
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    direction: ltr;
}

/* An indeterminate bar: a brass run that keeps crossing a pale track. */
.login-wait-bar {
    position: relative;
    width: 180px;
    height: 4px;
    margin-top: 8px;
    border-radius: 999px;
    background: rgba(28, 22, 8, .12);
    overflow: hidden;
}

.login-wait-bar span {
    position: absolute;
    top: 0;
    bottom: 0;
    width: 40%;
    border-radius: 999px;
    background: #8A6A00;
    animation: login-wait-run 1.2s ease-in-out infinite;
}

@keyframes login-wait-bob {
    0%, 100% { transform: translateY(0); }
    50% { transform: translateY(-6px); }
}

@keyframes login-wait-run {
    0% { left: -40%; }
    100% { left: 100%; }
}

/* A calmer hand for anyone who asked their phone for less movement. */
@media (prefers-reduced-motion: reduce) {
    .login-wait-pawn, .login-wait-bar span { animation: none; }
    .login-wait-bar span { left: 30%; }
}

.login-wait-popup.swal2-popup { padding-top: 10px !important; padding-bottom: 14px !important; }

/* =========================================================================
   Home tiles and the tournaments strip.

   Written for the student's phone home (Home/Dashboard.cshtml) and moved
   here on 2026-09-05 when the coach's phone home (Coach/Index.cshtml) took
   the same two. Both shells load this file; every colour is a theme token,
   so the tiles are the page's own on all nine themes and in both modes.
   ========================================================================= */
/*  The five shortcuts under the chat on a phone.

    The icon and the word sit side by side rather than stacked: stacked they
    cost twice the height for the same two things, and five of them plus the
    chat have one short screen to share.

    The surface is named rather than left to `.card`. On a phone `.card`
    has no background at all: the rule that gives one is written
    `.content-area .card`, and the phone shell's main is not `.content-area`
    — so a plain card there is a border round nothing. Measured on the
    device: background rgba(0,0,0,0) on a page of rgb(15,23,42). Taken from
    the theme's own token so it follows all nine themes; a colour written
    out here would be right in one of them and wrong in the rest.

    `padding: 0` because elbe-look.css puts 14px on `.card` itself below
    992px, on top of the padding `.card-body` already has — which is why
    these came out 72px tall for one line of text.

    `color: inherit` only because an <a> would otherwise paint itself
    link-blue over the card. */
.dash-tile {
    color: inherit;
    padding: 0;
    background: var(--theme-card-bg, var(--theme-sidebar-bg));
    border-color: var(--theme-border);
}

.dash-tile .card-body {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 12px;
    min-width: 0;
}

.dash-tile i {
    font-size: 1.35rem;
    line-height: 1;
    flex: 0 0 auto;
    color: var(--theme-accent, #0d6efd);
}

/*  Beside its icon with one gap, never pushed to the far side of the card.
    Truncated rather than wrapped so a long word in Turkish or Kurdish
    cannot make one tile taller than the one next to it. */
.dash-tile span {
    font-size: 0.9rem;
    font-weight: 600;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.dash-tile:active {
    background: var(--theme-hover-bg, rgba(255, 255, 255, 0.06));
}

/*  What is on, under the tiles. Built on the same .card the tiles are, so
    the ground and the lettering are the page's own in both themes and
    cannot come out light on light; the only colours written here are the
    brass edge that marks it as an announcement and the accent the tiles
    already use for their icons.

    One line each, and the name truncates rather than wraps — three
    tournaments must not push the chat off a short screen. */
.dash-tourn-list {
    display: flex;
    flex-direction: column;
    gap: 8px;
    margin-top: 8px;
}

/*  On the student's phone home the strip is the first thing on the screen,
    above the day's puzzle (2026-09-15): no gap above, a small one below.
    The coach's home keeps it under the tiles. */
.dash-student-col .dash-tourn-list {
    margin-top: 0;
    margin-bottom: 10px;
}

.dash-tourn {
    color: inherit;
    padding: 0;
    background: var(--theme-card-bg, var(--theme-sidebar-bg));
    border-color: var(--theme-border);
    border-inline-start: 3px solid var(--elbe-brass, #B58900);
}

.dash-tourn .card-body {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 12px;
    min-width: 0;
}

.dash-tourn > .card-body > i {
    font-size: 1.2rem;
    line-height: 1;
    flex: 0 0 auto;
    color: var(--theme-accent, #0d6efd);
}

.dash-tourn-text {
    display: flex;
    flex-direction: column;
    min-width: 0;
    flex: 1 1 auto;
    line-height: 1.25;
}

.dash-tourn-name {
    font-size: 0.9rem;
    font-weight: 600;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.dash-tourn-facts {
    font-size: 0.72rem;
    opacity: 0.75;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/*  The word that says what it wants next. Wine for check-in, the moment a
    child must act or forfeit; the page's accent otherwise. Both are read
    off the theme, so both themes are covered. */
.dash-tourn-go {
    flex: 0 0 auto;
    font-size: 0.75rem;
    font-weight: 700;
    padding: 3px 10px;
    border-radius: 999px;
    border: 1px solid var(--theme-accent, #0d6efd);
    color: var(--theme-accent, #0d6efd);
    white-space: nowrap;
}

.dash-tourn:active {
    background: var(--theme-hover-bg, rgba(255, 255, 255, 0.06));
}

/* The grid the tiles stand in, two to a row and stretched to share whatever
   height the page gives them; the lettering a step larger than a lone tile's. */
.dash-tiles > [class*="col-"] { display: flex; }

.dash-tiles .dash-tile {
    flex: 1 1 auto;
    display: flex;
    align-items: stretch;
}

/* Grown room deserves grown lettering — centered, a step larger. */
.dash-tiles .dash-tile .card-body {
    flex: 1 1 auto;
    justify-content: center;
}

.dash-tiles .dash-tile i { font-size: 1.6rem; }
.dash-tiles .dash-tile span { font-size: 1rem; }

/* =========================================================================
   The line that says the next page is on its way (menu-nav-feedback.js,
   2026-09-05). Three points of brass sliding along the very top of the
   screen, above everything, in both modes and on all nine themes: it is
   its own colour and sits on nothing. Hidden until a menu link is pressed.
   ========================================================================= */
.elbe-nav-wait {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    height: 3px;
    z-index: 30000;
    pointer-events: none;
    overflow: hidden;
    display: none;
}

.elbe-nav-wait.on { display: block; }

.elbe-nav-wait::before {
    content: "";
    position: absolute;
    top: 0;
    bottom: 0;
    left: -40%;
    width: 40%;
    background: linear-gradient(90deg, transparent, var(--elbe-brass, #B58900), transparent);
    animation: elbe-nav-wait-run 1.1s linear infinite;
}

@keyframes elbe-nav-wait-run {
    0% { left: -40%; }
    100% { left: 100%; }
}

/*  "Please wait…" (menu-nav-feedback.js, Kamal 2026-09-06): a small pill near the
    top, shown only once something a tap set going has taken more than two
    seconds. One fixed palette on every theme and layout: white on #1e293b is
    14.6:1, the ring rgba(255,255,255,.18) marks its edge on a light page too.
    It sits above everything and takes no taps. */
.elbe-please-wait {
    position: fixed;
    left: 50%;
    top: 64px;
    transform: translateX(-50%);
    z-index: 30001;
    display: none;
    align-items: center;
    gap: 10px;
    padding: 9px 16px;
    border-radius: 999px;
    background: #1e293b;
    color: #fff;
    font-weight: 600;
    font-size: 0.95rem;
    line-height: 1.2;
    border: 1px solid rgba(255, 255, 255, 0.18);
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
    pointer-events: none;
    white-space: nowrap;
}
.elbe-please-wait.on { display: inline-flex; animation: elbe-pw-in 0.15s ease-out; }
.elbe-please-wait-spin {
    width: 16px;
    height: 16px;
    flex: 0 0 auto;
    border: 2px solid rgba(255, 255, 255, 0.35);
    border-top-color: #fff;
    border-radius: 50%;
    animation: elbe-pw-spin 0.8s linear infinite;
}
@keyframes elbe-pw-spin { to { transform: rotate(360deg); } }
@keyframes elbe-pw-in { from { opacity: 0; transform: translateX(-50%) translateY(-6px); } to { opacity: 1; transform: translateX(-50%); } }
@media (prefers-reduced-motion: reduce) {
    .elbe-please-wait-spin { animation: none; }
    .elbe-please-wait.on { animation: none; }
}

@media (prefers-reduced-motion: reduce) {
    .elbe-nav-wait::before { animation: none; left: 0; width: 100%; opacity: 0.7; }
}

/* =========================================================================
   The memorize trainer (memorize-trainer.js, 2026-09-05), wherever a game
   is memorized: the method and side switches, and the trainer's panel.
   Colours are the theme's tokens or the brass the site's buttons wear —
   the chosen word #2A2118 on #F0B90B, 9.4:1 — so both modes are covered.
   A <button> inherits neither colour nor ground, so both are said.
   ========================================================================= */
.qs-seg { display: inline-flex; border-radius: 99px; overflow: hidden; border: 1px solid rgba(181, 137, 0, 0.55); vertical-align: middle; white-space: nowrap; }
.qs-seg-btn { border: 0; background: transparent; color: inherit; font: inherit; font-size: 12px; font-weight: 600;
              padding: 2px 10px; cursor: pointer; line-height: 1.6; border-radius: 0; }
.qs-seg-btn.on { background: #F0B90B; color: #2A2118; }
.qs-seg-btn:focus-visible { outline: 2px solid #B58900; outline-offset: -2px; }

/*  Memorize (Kamal, 2026-09-05): the trainer sits UNDER the moves list, which
    stays as it always is. Two rows on the card's ground in the page's ink —
    the method and side switches; then the action row: four fixed cells the
    trainer fills (◀ ▶ main test), the speed chip and the X. A cell left
    empty keeps its width, so a button coming or going never moves another.
    Under the rows, one line of feedback. Both themes come from the layout's
    tokens (text, muted text, border); the phase tags and the good/bad inks
    are fixed pairs that read on the light card and the dark one alike. */
.mz-bar { display: flex; flex-direction: column; gap: 6px; padding: 6px 8px; border-top: 1px solid var(--theme-border); color: var(--theme-text); }
.mz-row { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.mz-row-act { flex-wrap: nowrap; }
.mz-chip { display: inline-flex; align-items: center; gap: 6px; font-size: 12.5px; color: var(--theme-text); }
.mz-chip-label { color: var(--theme-text-muted); white-space: nowrap; }

.mz-act { flex: 1 1 auto; min-width: 0; display: grid; grid-template-columns: 32px 32px minmax(0, 1fr) minmax(0, 1fr); gap: 6px; align-items: stretch; }
.mz-cell { display: flex; min-width: 0; min-height: 31px; }
.mz-cell > .btn { flex: 1 1 auto; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; padding-inline: 6px; }
.mz-cell > .mz-nav { padding-inline: 0; }
.mz-speed { flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center; gap: 4px; min-width: 62px; white-space: nowrap; font-variant-numeric: tabular-nums; }
.mz-exit { flex: 0 0 auto; }

.mz-strip { padding: 4px 10px 6px; color: var(--theme-text); border-top: 1px solid var(--theme-border); }
.mz-line { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; min-height: 22px; font-size: 13px; }
.mz-feedback { font-size: 13px; min-width: 0; }

.mz-phase { display: inline-block; font-size: 11.5px; font-weight: 700; letter-spacing: .4px; text-transform: uppercase;
            padding: 1px 8px; border-radius: 99px; background: rgba(255, 210, 77, 0.18); color: #8A6A00; }
.mz-phase.recall, .mz-phase.quiz { background: rgba(46, 125, 107, 0.16); color: #2E7D6B; }
.mz-good { color: #2E7D6B; }
.mz-bad { color: #B23A2E; }
.mz-count { font-family: var(--elbe-mono); font-size: 12.5px; color: var(--theme-text-muted); }
/*  On the dark card the same voices, lighter: amber #FFD24D and mint #6BD79B
    on the slate card (both above 7:1), coral #F58B7C for a slip (5.6:1). */
body[data-bs-theme="dark"] .mz-phase { background: rgba(255, 210, 77, 0.16); color: #FFD24D; }
body[data-bs-theme="dark"] .mz-phase.recall, body[data-bs-theme="dark"] .mz-phase.quiz { background: rgba(107, 215, 155, 0.16); color: #6BD79B; }
body[data-bs-theme="dark"] .mz-good { color: #6BD79B; }
body[data-bs-theme="dark"] .mz-bad { color: #F58B7C; }

/*  The moves list while memorizing: a move not yet played in a quiz is kept
    in place but unseen; the one being asked for in Learn wears a dashed edge. */
#moveListBody .mz-hid { visibility: hidden; }
#moveListBody .mz-next { outline: 1px dashed #B58900; outline-offset: -1px; font-weight: 700; }

/* Memorize: the Watch/Stop button carries both faces on the same spot, so it
   keeps the wider one's width whichever shows and the row never shifts. */
.mz-watch { display: inline-grid; align-items: center; justify-items: center; }
.mz-watch > span { grid-area: 1 / 1; white-space: nowrap; }
.mz-watch:not(.is-stop) .mz-w-stop, .mz-watch.is-stop .mz-w-play { visibility: hidden; }
/*  The Chessable way's row on the memorize trainer: Watch · Learn · Quiz, three equal cells (Kamal, 2026-09-09). */
.mz-act.mz-act-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
/*  The action row on a narrow panel (Kamal, 2026-09-09): the four (or three) cells keep at least 250px, so the speed chip and the X drop under them instead of being cut. */
.mz-row-act { flex-wrap: wrap; row-gap: 4px; }
.mz-act { min-width: 250px; }

/*  The voice chat in the bar's middle gap (Kamal, 2026-09-11) — the same
    place the lesson plaque takes in a classroom. Its pieces are styled in
    game-hall.css, which every page that fills this loads. */
.topbar-voice {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-wrap: wrap;
    gap: 6px 8px;
    padding-inline: 12px;
}
.topbar-voice[hidden] { display: none; }
.topbar-voice .t-strip-btn { margin-inline-start: 0; }
