/*  Kiosk shell.

    Three rules drive everything here, and they come from the display rather than
    from taste:

      nothing scrolls   — the page is exactly one screen, always. overflow:hidden
                          on the root is the enforcement, not a fallback: if a
                          layout is wrong it must be visibly wrong, not quietly
                          scrollable on a screen nobody can scroll.

      nothing is clicked — no hover states, no focus rings, no cursor. The only
                          exception is the dashboard, which styles itself.

      the panel varies  — a 32" monitor and an 85" TV both run this unchanged, so
                          every size is a function of the viewport. vmin, not vw:
                          a very wide panel should not blow the type up to match
                          its width when the height is what constrains reading.  */

html, body {
    height: 100%;
    margin: 0;
    overflow: hidden;
    /*  The overscan frame. When the kiosk is scaled in below, the revealed border
        shows this — same colour as the kiosk ground, so a TV that does not
        overscan sees no frame at all and one that does crops into dark, never
        into content. */
    background: var(--bg, #0a120a);
}

/*  The cursor vanishes only inside the kiosk itself. On the root it also hid the
    pointer on the sign-in page — the one screen someone might drive with a mouse
    (initial setup, a desktop admin) — leaving them clicking blind. .kiosk-home
    restores it again for the dashboard below. */
.kiosk { cursor: none; }

.kiosk {
    height: 100vh;
    display: grid;
    grid-template-rows: auto 1fr;
    background: var(--bg, #0a120a);
    color: var(--text, #e2e8f0);
    /*  Everything downstream is expressed in em, so this one line rescales the
        entire screen for a different panel. */
    /*  Measured, not guessed: at 1.35vmin the body text came out 14.6px on a
        1080p panel — 1.35% of screen height, which stops resolving at the ~2.5x
        viewing distance a TV is actually watched from. 2.1vmin puts it near 23px
        (2.1%), which is the low end of what broadcast UI uses.

        The floor is 14px rather than 16px on purpose. Page capacities are fixed row
        counts that only stay correct while type scales strictly with the viewport;
        at 720p, 2.1vmin is 15.1px, so a 16px floor would clamp — making rows
        proportionally taller than the canvas and pushing the last row of every full
        page off a 720p screen. 14px keeps the scaling honest down to 667px. */
    /*  Fallback first: clamp() is Chrome 79 and the television is a Chrome 66
        WebView, which drops the declaration entirely — leaving .kiosk at the
        inherited 16px and every em-based size on every screen ~30% wrong. Plain
        vmin has worked since Chrome 20 and gives the same scaling; only the 14px
        floor and 44px ceiling are lost, and those bite solely at the extremes. */
    font-size: 2.1vmin;
    font-size: clamp(14px, 2.1vmin, 44px);
    font-family: var(--sans, system-ui, sans-serif);

    /*  TV OVERSCAN SAFE-AREA.

        Consumer TVs crop 2-5% of every edge. The whole 100vh × 100vw kiosk is
        scaled down uniformly and centred, so the cropped band falls on the dark
        body frame and never on content.

        Scale rather than padding on purpose: the root font-size above is vmin —
        tied to the VIEWPORT, not to any inset box — so padding would shrink the
        canvas while the em-sized content stayed full size, pushing pages already
        at 98% fill past their edge. A uniform transform scales the content and its
        canvas by the same factor, so the fill RATIO is exactly preserved and
        nothing that fitted before can clip now. (The old body-padding overscan
        rule, long removed, is the mistake this avoids.)

        Transforms are Chrome 36+, so this holds on the Chrome 66 TV.

        TUNABLE per install. A pixel-perfect HDMI box or Android TV stick wants 1
        (no frame); a TV that visibly crops wants less. Override --tv-safe-scale on
        :root. 0.96 ≈ a 2% frame each edge — modest, and clears typical overscan. */
    transform: scale(var(--tv-safe-scale, 0.96));
    transform-origin: center center;
}

/* ── the band that never moves ────────────────────────────────────────────── */

.k-head {
    display: grid;
    grid-template-columns: auto 1fr auto auto auto;
    align-items: center;
    gap: 1.4em;
    padding: 0.9em 1.6em;
    background: linear-gradient(90deg, #0a1a0a, #122012);
    border-bottom: 0.14em solid rgba(34, 197, 94, .35);
}

.k-ac {
    font-family: var(--mono, ui-monospace, monospace);
    font-size: 1.5em;
    font-weight: 800;
    letter-spacing: .06em;
    color: var(--green, #4ade80);
    white-space: nowrap;
}

.k-booth { min-width: 0; }

.k-booth-no {
    font-size: 2.4em;
    font-weight: 800;
    line-height: 1;
    letter-spacing: -.02em;
}

.k-booth-name {
    font-size: 1.15em;
    color: var(--muted2, #94a3b8);
    margin-top: .15em;
    /*  One line only. A long Hindi booth name must not push the header taller and
        steal rows from the body. */
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.k-meta {
    text-align: right;
    font-family: var(--mono, monospace);
    font-size: 1.05em;
    color: var(--muted2, #94a3b8);
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}

.k-clock { font-size: 1.5em; font-weight: 700; color: var(--text, #e2e8f0); }

/*  How far through the assembly the walk is. On a screen with no controls this is
    the only thing that says the display is alive rather than frozen. */
.k-progress {
    height: .3em;
    background: rgba(255, 255, 255, .07);
}

.k-progress > i {
    display: block;
    height: 100%;
    background: var(--green, #4ade80);
    transition: width .6s linear;
}

/*  Time left on THIS screen. The bar above tracks a 16-hour walk and barely
    moves; this one crosses in one page's dwell, which is what tells a passer-by
    the display is running rather than stuck. Duration is set inline per page
    from PageDef.Dwell — the animation is the timer made visible, not a second
    copy of it, so it cannot drift from the real one. */
.k-dwell {
    height: .16em;
    background: rgba(255, 255, 255, .05);
    overflow: hidden;
}

.k-dwell > i {
    display: block;
    height: 100%;
    width: 0;
    background: linear-gradient(90deg, var(--green, #4ade80), #22d3ee);
    animation-name: k-dwell-fill;
    animation-timing-function: linear;
    animation-fill-mode: both;
}

@keyframes k-dwell-fill { from { width: 0; } to { width: 100%; } }

@media (prefers-reduced-motion: reduce) {
    .k-dwell > i { animation: none; width: 100%; opacity: .35; }
}

/* ── body ─────────────────────────────────────────────────────────────────── */

.k-body {
    min-height: 0;          /* lets the grid row actually constrain the child */
    display: grid;
    grid-template-rows: auto 1fr;
    gap: 1em;
    padding: 1.4em 1.8em 1.8em;
}



/*  gap fallback: flex gap is Chrome 84 and the TV is Chrome 66 —
    successor margins are the same geometry on every browser. */

.k-title > * + * { margin-left: 1em; }

.k-title h2 {
    margin: 0;
    font-size: 2.1em;
    font-weight: 700;
    letter-spacing: -.015em;
}

.k-title .k-of {
    font-family: var(--mono, monospace);
    font-size: 1em;
    color: var(--muted, #8e96a6);
    font-variant-numeric: tabular-nums;
}

.k-canvas { min-height: 0; display: grid; }

/* ── breakdown: a labelled row per slice ──────────────────────────────────── */

.k-rows {
    display: grid;
    gap: .5em;
    align-content: space-evenly;
    min-height: 0;
}

.k-row {
    display: grid;
    grid-template-columns: 1fr auto auto;
    align-items: center;
    gap: 1.2em;
    padding: .55em .9em;
    border-radius: .5em;
    background: rgba(255, 255, 255, .035);
    /*  The bar is the row's own background, so the number and its magnitude are
        the same object rather than a figure next to a chart. */
    background-image: linear-gradient(90deg, rgba(34, 197, 94, .22) var(--w, 0%), transparent var(--w, 0%));
}

.k-row .k-lbl { font-size: 1.35em; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.k-row .k-num { font-size: 1.5em; font-weight: 800; font-variant-numeric: tabular-nums; }
.k-row .k-pct { font-size: 1.1em; color: var(--muted2, #94a3b8); font-variant-numeric: tabular-nums; min-width: 4.5em; text-align: right; }

.k-sub { font-size: .95em; color: var(--muted, #8e96a6); font-variant-numeric: tabular-nums; }

/* ── stats: one wide row as named figures ─────────────────────────────────── */

.k-stats {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(13em, 1fr));
    gap: .9em;
    align-content: space-evenly;
    min-height: 0;
}

.k-stat {
    padding: 1em 1.1em;
    border-radius: .6em;
    background: rgba(255, 255, 255, .04);
    border-left: .22em solid var(--green, #4ade80);
}

.k-stat .k-k {
    font-size: .95em;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--muted, #8e96a6);
}

.k-stat .k-v {
    font-size: 2.4em;
    font-weight: 800;
    line-height: 1.1;
    font-variant-numeric: tabular-nums;
}

.k-stat .k-s { font-size: .95em; color: var(--muted2, #94a3b8); }

/* ── grid: sets too large to read as a list ───────────────────────────────── */

.k-grid {
    display: grid;
    /*  7.5em, measured: at 8.5em a 1080p panel fitted 9 columns, so 64 ages needed
        8 rows of 97px — 850px against 751px of canvas, and the last row was
        clipped by overflow:hidden. 10 columns brings it to 7 rows and 740px. */
    grid-template-columns: repeat(auto-fit, minmax(7.5em, 1fr));
    gap: .45em;
    align-content: space-evenly;
    min-height: 0;
}

.k-tile {
    padding: .42em .5em;
    border-radius: .45em;
    background: rgba(255, 255, 255, .04);
    text-align: center;
    /*  Tinted by share, so the dense pages read as a heat map at ten paces even
        when the individual numbers do not. */
    background-image: linear-gradient(0deg, rgba(34, 197, 94, .3) var(--w, 0%), transparent var(--w, 0%));
}

.k-tile .k-t-lbl { font-size: .92em; color: var(--muted2, #94a3b8); }
.k-tile .k-t-num { font-size: 1.35em; font-weight: 800; font-variant-numeric: tabular-nums; }

/* ── empty ────────────────────────────────────────────────────────────────── */

.k-empty {
    display: grid;
    place-content: center;
    text-align: center;
    color: var(--muted, #8e96a6);
    font-size: 1.6em;
}

/* ── part badge ───────────────────────────────────────────────────────────── */

/*  Shown only when a set needed more than one screen, so the room knows it is
    looking at half of something rather than all of it. */
.k-part {
    margin-left: .6em;
    padding: .12em .5em;
    border-radius: .3em;
    font-family: var(--mono, monospace);
    font-size: .5em;
    font-weight: 700;
    vertical-align: middle;
    letter-spacing: .04em;
    color: var(--green, #4ade80);
    background: rgba(34, 197, 94, .14);
    border: .06em solid rgba(34, 197, 94, .3);
}

/* ── a channel whose data has not arrived ─────────────────────────────────── */

.k-pending {
    grid-row: 1 / -1;
    display: grid;
    place-content: center;
    justify-items: center;
    text-align: center;
    gap: .5em;
    max-width: 24em;
    margin: 0 auto;
}

.k-pending-glyph { font-size: 5em; line-height: 1; color: rgba(148, 163, 184, .35); }
.k-pending-title { font-size: 2.2em; font-weight: 700; }
.k-pending-note  { font-size: 1.15em; line-height: 1.5; color: var(--muted, #8e96a6); }

/* ── dashboard ────────────────────────────────────────────────────────────── */

/*  The one screen a person stands in front of, so it is the one place the cursor
    and focus rings come back. */
.kiosk-home { grid-template-rows: auto 1fr auto; }
.kiosk-home, .kiosk-home * { cursor: default; }
.kiosk-home a { cursor: pointer; }

.k-menu {
    min-height: 0;
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    grid-auto-rows: 1fr;
    gap: 2em;
    /*  Padding matches the gap so the outer tiles are not visibly tighter to the
        screen edge than they are to each other, and so a scaled-up card at the
        edge has room to grow into rather than being clipped. */
    padding: 1.9em 2em;
}

/*  A channel tile.

    Each carries its own gradient, taken from the ChannelDef rather than declared
    here, so a card and the screens behind it cannot drift apart.

    The icon is the point of the card: on a wall, someone crossing the room picks
    a tile by its shape and colour long before they can read the label, so it gets
    a real block of the card rather than a decorative bullet. */
.k-card {
    position: relative;
    /*  Icon on top, text against the bottom edge. A side-by-side layout centred the
        pair and left 126px of dead space above and below in a 373px card; stacking
        them lets the tile use its own height. */
    display: grid;
    grid-template-rows: auto 1fr;
    gap: .9em;
    padding: 1.3em 1.4em;
    border-radius: .9em;
    text-decoration: none;
    color: inherit;
    overflow: hidden;
    isolation: isolate;

    background:
        radial-gradient(120% 140% at 0% 0%,
            color-mix(in srgb, var(--c) 26%, transparent) 0%,
            transparent 62%),
        linear-gradient(150deg, rgba(255,255,255,.07), rgba(255,255,255,.02));
    border: .1em solid color-mix(in srgb, var(--c) 30%, transparent);
    transition: transform .22s cubic-bezier(.22,.61,.36,1),
                border-color .22s ease, box-shadow .22s ease, opacity .22s ease;
}

/*  color-mix voids the whole declaration where it is unsupported, so a plain
    fallback is stated first and the tinted version only upgrades it. */
@supports not (color: color-mix(in srgb, red 50%, blue)) {
    .k-card { background: rgba(255,255,255,.06); border-color: rgba(255,255,255,.14); }
}

/*  A wash of the channel colour bleeding up from the bottom edge. Sits behind the
    content on its own layer so it never touches text contrast. */
/*  The icon again, oversized and barely there, bleeding off the bottom-right.
    Gives the card depth at a glance without adding anything to read. */
.k-card::before {
    content: "";
    position: absolute;
    right: -1.4em;
    bottom: -1.6em;
    width: 9em;
    height: 9em;
    background: var(--icon) center / contain no-repeat;
    opacity: .10;
    pointer-events: none;
    z-index: -1;
}

.k-card::after {
    content: "";
    position: absolute;
    top:auto; right:0; bottom:0; left:0;
    inset: auto 0 0 0;
    height: .34em;
    background: linear-gradient(90deg, var(--c), var(--c2));
    z-index: -1;
}

/* ── the icon ─────────────────────────────────────────────────────────────── */

/*  A soft tinted well rather than a saturated tile: the 3D icons carry their own
    colour and lighting, and a bright gradient underneath fights them. The channel
    hue stays present as a wash, a rim and the bottom bar. */
.k-card-icon {
    display: grid;
    place-items: center;
    width: 6.4em;
    height: 6.4em;
    border-radius: 1.4em;
    background:
        radial-gradient(80% 80% at 30% 25%, var(--cg), transparent 70%),
        rgba(255, 255, 255, .06);
    border: .08em solid var(--cg, rgba(255,255,255,.16));
    /*  The channel colour thrown as light onto the card beneath the tile: a wide
        soft pool under it, and a tight rim hugging the edge so the tile reads as
        lifted rather than printed on.

        rgba from the model rather than color-mix in this file — the television is
        a Chrome 66 WebView, which drops a color-mix declaration whole, and the
        glow would be missing on the only screen that matters. */
    box-shadow:
        0 .55em 1.6em var(--cg, rgba(255,255,255,.18)),
        0 0 0 .02em var(--cg, rgba(255,255,255,.18));
}

.k-card-icon img {
    width: 82%;
    height: 82%;
    object-fit: contain;
    /*  Grounds the icon on the tile. The 3D renders are lit from the top-left, so
        the shadow is thrown down and right to match. */
    filter: drop-shadow(.04em .06em .09em rgba(0, 0, 0, .45));
}

/* ── text ─────────────────────────────────────────────────────────────────── */

.k-card-text { display: grid; gap: .18em; min-width: 0; align-self: end; }

.k-card-title {
    font-size: 2.3em;
    font-weight: 800;
    letter-spacing: -.02em;
    line-height: 1.05;
}

.k-card-blurb {
    font-size: 1.1em;
    color: var(--muted2, #94a3b8);
    line-height: 1.25;
}

.k-card-meta {
    margin-top: .3em;
    font-family: var(--mono, monospace);
    font-size: .88em;
    color: var(--muted, #8e96a6);
    font-variant-numeric: tabular-nums;
}

.k-card-meta b {
    color: var(--c);
    font-weight: 800;
    font-size: 1.15em;
}

/* ── picked ───────────────────────────────────────────────────────────────── */

/*  The selected tile grows a little and takes a bright ring. Nothing else changes:
    the unselected cards are NOT dimmed. Dimming them was tried and dropped — it
    made the wall darker overall for a room that is mostly reading the six labels,
    and it depended on :focus-within, which matches on a far looser condition than
    :focus does. */

/*  Hover and focus are deliberately the same state: a remote moves focus and a
    mouse moves hover, and from six feet away they should look identical.

    THREE selectors, not two. tv-nav.js moves focus by calling .focus() in script,
    and :focus-visible does NOT match script-driven focus — so on a TV remote this
    highlight never fired at all and the card fell back to tv.css's generic cyan
    outline. html.tv-dpad .k-card:focus is the arrow-key path. */
/*  ONE selector per rule where :focus-visible is involved, never a shared list.
    Chrome 66 — the television's WebView — does not know :focus-visible, and an
    unknown pseudo-class invalidates the ENTIRE rule it appears in. Sharing a list
    meant the D-pad highlight vanished on precisely the device that navigates by
    D-pad. The shadow is declared twice, plain rgba first: Chrome 66 also drops
    the color-mix declaration, and keeps the fallback. */
.k-card:hover,
html.tv-dpad .k-card:focus {
    /*  Lifted and enlarged, like a selected tile on a television interface: at
        three metres a colour change alone is not enough to say which one is armed,
        but a size change is legible across the room. */
    transform: scale(1.04);
    z-index: 2;
    border-color: rgba(255, 255, 255, .85);
    box-shadow:
        0 0 0 .14em rgba(255, 255, 255, .9),
        0 .7em 2.2em var(--cg, rgba(0, 0, 0, .6));
    box-shadow:
        0 0 0 .14em rgba(255, 255, 255, .9),
        0 .7em 2.2em color-mix(in srgb, var(--c) 55%, transparent);
    /*  tv.css paints a cyan ring on any focused anchor. The card states its own
        selection, so that generic ring is suppressed here rather than layered. */
    outline: none;
}

.k-card:focus-visible {
    transform: scale(1.04);
    z-index: 2;
    border-color: rgba(255, 255, 255, .85);
    box-shadow:
        0 0 0 .14em rgba(255, 255, 255, .9),
        0 .7em 2.2em var(--cg, rgba(0, 0, 0, .6));
    box-shadow:
        0 0 0 .14em rgba(255, 255, 255, .9),
        0 .7em 2.2em color-mix(in srgb, var(--c) 55%, transparent);
    outline: none;
}

@media (prefers-reduced-motion: reduce) {
    .k-card, .k-menu .k-card { transition: none; }
    .k-card:hover, html.tv-dpad .k-card:focus { transform: none; }
    .k-card:focus-visible { transform: none; }
}

/* ── a channel with no data ───────────────────────────────────────────────── */

/*  Not disabled — it still opens and still explains itself. Just visibly holding
    no data: the gradient drains to grey while the layout stays identical, so the
    card reads as the same kind of thing, waiting. */
/*  The grey comes from ChannelDef.CardAccent, not from here — an inline custom
    property outranks any class selector, so setting --c in this rule would do
    nothing. This only softens what the model already decided. */
.k-card-off { opacity: .62; }

/*  Desaturated, not hidden: the channel is real and waiting, not broken. */
.k-card-off .k-card-icon img { filter: grayscale(1) opacity(.75); }
.k-card-off::before { filter: grayscale(1); }
.k-card-off .k-card-meta { color: #94A3B8; font-style: italic; }
.k-card-off:hover { opacity: .85; }
.k-card-off:focus-visible { opacity: .85; }
html.tv-dpad .k-card-off:focus { opacity: .85; }



/*  gap fallback: flex gap is Chrome 84 and the TV is Chrome 66 —
    successor margins are the same geometry on every browser. */
/*  RECONSTRUCTED after an editing accident deleted the original rule (the
    successor-margin sweep replaced whole blocks instead of gap lines). Layout
    intent is certain — a spaced footer strip — exact colours were lost; verify
    against the dashboard and adjust if it reads wrong. */

.k-foot > * + * { margin-left: 1em; }

.k-foot form { margin: 0; }

.k-signout {
    font: inherit;
    padding: .35em 1em;
    border-radius: .35em;
    color: var(--muted2, #94a3b8);
    background: rgba(255, 255, 255, .05);
    border: .08em solid rgba(255, 255, 255, .1);
}

.k-signout:hover,
.k-signout:focus-visible {
    color: var(--text, #e2e8f0);
    border-color: rgba(148, 163, 184, .4);
    outline: none;
}

/*  A narrow or short panel drops to two columns rather than squeezing six across
    a strip. */
@media (max-width: 1100px), (max-height: 700px) {
    .k-menu { grid-template-columns: repeat(2, 1fr); gap: .9em; padding: 1em 1.2em; }
    .k-card { padding: .9em 1em; }
}

/* ── the way back ─────────────────────────────────────────────────────────── */

/*  The only control on a channel screen. Deliberately quiet: it should be findable
    when someone walks up to the TV and invisible from across the room, so it never
    competes with the content it sits beside. */


/*  gap fallback: flex gap is Chrome 84 and the TV is Chrome 66 —
    successor margins are the same geometry on every browser. */

.k-home > * + * { margin-left: .45em; }

.k-home-i { font-size: 1.25em; line-height: 1; }

.k-home:hover,
.k-home:focus-visible {
    color: var(--text, #e2e8f0);
    background: rgba(34, 197, 94, .16);
    border-color: rgba(34, 197, 94, .5);
    outline: none;
}

/*  A panel narrow enough that the label would crowd the booth name keeps the
    glyph, which is the part that carries the meaning. */
@media (max-width: 1100px) {
    .k-home-t { display: none; }
}

/* ── who is on this booth ─────────────────────────────────────────────────── */

/*  Sits in the fixed header band, so it changes only when the walk moves to the
    next booth. Kept to three short lines: a name people recognise, a number they
    could dial, and one word for whether the account is usable. */
.k-member {
    display: grid;
    justify-items: end;
    gap: .12em;
    text-align: right;
    min-width: 0;
    max-width: 16em;
    padding-right: 1.1em;
    border-right: .08em solid rgba(255, 255, 255, .1);
}

.k-m-name {
    font-size: 1.15em;
    font-weight: 700;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 100%;
}

/*  An unassigned booth says so in the muted colour rather than shouting it. On a
    roll where 177,514 of 177,516 booths have no member yet, an alarm-coloured
    header would be an alarm nobody could act on. */
/*  Attribute on the container, not :has() — :has() is Chrome 105 and the TV is
    Chrome 66, which dropped this rule and lit every unassigned booth full-bright. */
.k-member[data-s="unassigned"] .k-m-name { color: var(--muted, #8e96a6); font-weight: 600; }



/*  gap fallback: flex gap is Chrome 84 and the TV is Chrome 66 —
    successor margins are the same geometry on every browser. */

.k-m-sub > * + * { margin-left: .6em; }

.k-m-login { color: var(--muted, #8e96a6); }

.k-m-status {
    margin-top: .1em;
    padding: .1em .55em;
    border-radius: .3em;
    font-family: var(--mono, monospace);
    font-size: .78em;
    font-weight: 700;
    letter-spacing: .05em;
    text-transform: uppercase;
    border: .07em solid transparent;
}

.k-m-status[data-s="active"]       { color:#4ADE80; background:rgba(74,222,128,.14);  border-color:rgba(74,222,128,.32); }
.k-m-status[data-s="contact-only"] { color:#38BDF8; background:rgba(56,189,248,.14);  border-color:rgba(56,189,248,.32); }
.k-m-status[data-s="locked"]       { color:#FBBF24; background:rgba(251,191,36,.14);  border-color:rgba(251,191,36,.32); }
.k-m-status[data-s="blocked"]      { color:#FB7185; background:rgba(251,113,133,.14); border-color:rgba(251,113,133,.32); }
.k-m-status[data-s="unassigned"]   { color:#8e96a6; background:rgba(100,116,139,.14); border-color:rgba(100,116,139,.28); }

/*  A narrow panel drops the login code first, then the whole block, before the
    booth name is allowed to shrink. */
@media (max-width: 1400px) { .k-m-login { display: none; } }
@media (max-width: 1100px) { .k-member { display: none; } }

/*  RELOCATED base rules.

    These eight blocks lived beside their successor-margin companions mid-file,
    where the browser's parser dropped them for a reason that resisted an hour of
    byte-level forensics: the file is valid UTF-8, braces, parens and comments all
    balance, the served bytes match disk, and each block parses cleanly in
    isolation - yet in situ the parsed sheet skipped exactly these rules. Parked
    at end-of-file, where a probe rule demonstrably parses, until the cause is
    understood. Do not inline them back without re-measuring the parsed sheet. */

.k-title {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
}

.k-foot {
    display: flex;
    align-items: center;
    justify-content: space-between;
    font-size: .85em;
    color: var(--muted2, #8e96a6);
}

.k-home {
    display: inline-flex;
    align-items: center;
    padding: .45em .8em;
    border-radius: .45em;
    text-decoration: none;
    font-size: .95em;
    color: var(--muted2, #94a3b8);
    background: rgba(255, 255, 255, .05);
    border: .08em solid rgba(255, 255, 255, .1);
    cursor: pointer;
    transition: background .18s ease, color .18s ease, border-color .18s ease;
}

.k-m-sub {
    display: flex;
    align-items: baseline;
    font-family: var(--mono, ui-monospace, monospace);
    font-size: .9em;
    color: var(--muted2, #94a3b8);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

