/*
 * WiseWTTWeb design system - one file, no dependencies.
 *
 * The brief: the old front end was criticised as dated, so this one has to
 * read as a current product at a glance. The ingredients of "current" used
 * here, all standard CSS, no framework:
 *
 *   - design tokens as custom properties, with an automatic dark scheme
 *   - one typeface (system UI stack), tabular figures on every number
 *   - soft layered shadows + 12-16 px radii instead of borders everywhere
 *   - a sticky, translucent, blurred header (the "glass" bar)
 *   - pill buttons and chips with visible focus rings and hover lift
 *   - cards with a coloured spine + tinted body, derived per school colour
 *   - reduced motion respected; print gets its own quiet theme
 */

/* ------------------------------------------------------------------ */
/* tokens                                                              */
/* ------------------------------------------------------------------ */

:root
{
    /* brand */
    --brand:        #2f6fed;   /* wise blue, used sparingly              */
    --brand-ink:    #1d4ed8;

    /* light scheme surfaces */
    --bg:           #f4f6fa;
    --surface:      #ffffff;
    --surface-2:    #eef1f6;
    --ink:          #16181d;
    --ink-soft:     #5b6472;
    --line:         #e3e7ee;
    --line-strong:  #c9d0da;

    --danger:       #d64545;
    --ok:           #2e9e5b;

    /*
     * ONE CONTROL HEIGHT, and it is here because the "?" beside a
     * field has to line up with the middle of that field. Guessing it -
     * bottom-align, then lift a few pixels - looked right beside a text
     * box and visibly wrong beside a select, which is what a guess does.
     *
     * Used as the min-height of every input and select AND as the height
     * of the box the help mark centres in, so the two cannot drift: they
     * are the same number. Change the input padding and this is the one
     * line that follows.
     */
    --control-h: 38px;

    --radius-s: 8px;
    --radius-m: 12px;
    --radius-l: 16px;

    --shadow-1: 0 1px 2px rgb(16 24 40 / .06), 0 1px 3px rgb(16 24 40 / .10);
    --shadow-2: 0 4px 12px rgb(16 24 40 / .10), 0 2px 4px rgb(16 24 40 / .06);
    --shadow-3: 0 12px 32px rgb(16 24 40 / .16);

    --font: "Inter", "Segoe UI Variable", "Segoe UI", system-ui, -apple-system, sans-serif;

    /*
     * DECLARE THE SCHEME. Without this the browser assumes the page is light
     * and paints every control IT owns in light colours - <dialog>'s default
     * text (black, on our dark panel), input and textarea backgrounds, select
     * popups, scrollbars, autofill. The tokens above would be the only dark
     * thing on the screen.
     *
     * `light dark`, not `dark`: this sheet defines both and lets the operating
     * system decide, which is what the prefers-color-scheme block below does.
     */
    color-scheme: light dark;
}

@media (prefers-color-scheme: dark)
{
    :root
    {
        --bg:          #101318;
        --surface:     #181c23;
        --surface-2:   #1f242d;
        --ink:         #e8eaf0;
        --ink-soft:    #9aa3b2;
        --line:        #262c36;
        --line-strong: #3a4250;

        --shadow-1: 0 1px 2px rgb(0 0 0 / .5);
        --shadow-2: 0 4px 12px rgb(0 0 0 / .5);
        --shadow-3: 0 12px 32px rgb(0 0 0 / .6);
    }

    /*
     * AND THE ONE THING THE BROWSER STILL PAINTS ITSELF.
     *
     * A select's open list is drawn by the operating system. It cannot be
     * styled and it does not read our tokens - it reads `color-scheme`, and
     * :root declares `light dark`, which leaves the choice to the machine.
     * On a light machine that is a white list dropping out of a dark page,
     * which is what the signage tab's design picker looked like.
     *
     * Here the page IS dark, so the popup is told so outright.
     */
    select { color-scheme: dark; }
}

/* ------------------------------------------------------------------ */
/* base                                                                */
/* ------------------------------------------------------------------ */

*, *::before, *::after
{
    box-sizing: border-box;
}

html
{
    -webkit-text-size-adjust: 100%;
}

body
{
    margin: 0;
    font-family: var(--font);
    font-size: 14px;
    line-height: 1.45;
    color: var(--ink);
    background:
        radial-gradient(1200px 400px at 20% -10%, rgb(47 111 237 / .07), transparent 60%),
        var(--bg);
    min-height: 100dvh;
}

a
{
    color: var(--brand-ink);
    text-decoration: none;
}

:focus-visible
{
    outline: 2px solid var(--brand);
    outline-offset: 2px;
    border-radius: var(--radius-s);
}

@media (prefers-reduced-motion: no-preference)
{
    .btn, .chip, .card, .picker-item, .axis
    {
        transition: transform .12s ease, box-shadow .12s ease,
                    background-color .12s ease, border-color .12s ease;
    }
}

/* ------------------------------------------------------------------ */
/* the glass top bar                                                   */
/* ------------------------------------------------------------------ */

.topbar
{
    position: sticky;
    top: 0;
    z-index: 30;
    display: flex;
    align-items: center;
    gap: 16px;
    flex-wrap: wrap;
    padding: 10px 20px;
    background: color-mix(in srgb, var(--surface) 78%, transparent);
    -webkit-backdrop-filter: blur(14px) saturate(1.4);
    backdrop-filter: blur(14px) saturate(1.4);
    border-bottom: 1px solid var(--line);
}

.brand
{
    display: flex;
    align-items: center;
    gap: 10px;
    font-size: 17px;
    letter-spacing: -.01em;
    white-space: nowrap;
}

.brand b
{
    font-weight: 700;
}

/*
 * THE MARK, AND IT IS THE PRODUCT'S OWN.
 *
 * It used to be ONE rounded square cut into quarters by a conic-gradient,
 * described here as "a nod to the desktop logo, zero assets". Cheap, and not
 * the logo. The Wise Timetable mark is FOUR SEPARATE rounded squares with a
 * gap between them, and at this size the difference is not subtle: a single
 * tile with four colours baked into it reads as a generic app icon, which is
 * exactly what it was being mistaken for.
 *
 * The geometry is measured off the mobile application's own launcher art
 * (ic_launcher_foreground, 432px): side 90, gap 20, corner radius 12.5 - a
 * gap of 22.2% of the side and a radius of 13.9%. Those proportions live in
 * the <svg>'s viewBox in _brand.php, which is why this rule only has to say
 * how big to draw it. The colours come from the same file and are literal in
 * the markup on purpose: a brand mark that changed with the colour scheme
 * would be a different mark twice.
 *
 * 20px and not 18: four squares with a hole through the middle need a little
 * more room than one solid tile to carry the same weight beside the wordmark.
 *
 * INLINE SVG AND NOT A FILE. No request, no cache to bust on a deploy, no
 * flash of a missing image before it lands, and it stays crisp on the 3x
 * phone screens this timetable is mostly read on - which a PNG at 20px does
 * not.
 *
 * The drop shadow went with the single tile it belonged to. Four small
 * squares each casting one is dirt, not depth.
 */
.brand-mark
{
    display: block;
    width: 20px;
    height: 20px;
    flex: 0 0 auto;
}

/*
 * THE COMPACT MARK, for the two places the name is set at 12px and not 17.
 *
 * Same four squares, PRESSED TOGETHER: one rounded tile with the quarters
 * touching, rounded on the outside only. It is not a second logo, it is the
 * mark at the density small type needs - at 16px the separated version's gap
 * is 1.6px, which at that size is not a gap, it is a smudge between colours
 * that no longer reads as four of anything.
 *
 * The corner keeps the separated mark: 20px beside 17px type has the room to
 * show what the shape actually is, and that is the one place on the page
 * where the product introduces itself.
 *
 * WHY A GRADIENT AND NOT FOUR RECTANGLES. Because there is nothing between
 * them to draw. A conic sweep from 0deg lands the quarters top-right,
 * bottom-right, bottom-left, top-left - which is why the colours below are in
 * that order and not in reading order, and why anyone editing them should
 * count round rather than across.
 *
 * The colours are the mark's own, the same four the corner uses, so the two
 * densities are one mark and not two.
 */
.brand-mark-tight
{
    display: block;
    width: 16px;
    height: 16px;
    flex: 0 0 auto;
    border-radius: 4px;
    background:
        conic-gradient(from 0deg,
            #aabae5 0 25%,      /* top-right    */
            #e2da66 0 50%,      /* bottom-right */
            #e84c67 0 75%,      /* bottom-left  */
            #768ed2 0 100%);    /* top-left     */
    box-shadow: var(--shadow-1);
}

/*
 * THE HEADER HAS TO BREAK BEFORE IT OVERLAPS.
 *
 * It used to be `flex: 1; min-width: 260px` with a nowrap label and no
 * overflow rule, which is a combination that does exactly the wrong thing:
 * flex-basis 0 lets the nav shrink below its contents, the label refuses to
 * wrap, and so it simply RUNS OUT of its box and underneath the buttons on
 * the right. Adding one more button (Reports) was enough to make it show.
 *
 * Three changes, each doing one job:
 *   flex: 1 1 340px  a real basis, so the row wraps the action buttons onto
 *                    their own line before anything has to overlap
 *   min-width: 0     lets the nav actually shrink, which is the prerequisite
 *                    for its children being allowed to truncate
 *   the label truncates with an ellipsis instead of overflowing
 */
.weeknav
{
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
    row-gap: 6px;
    /*
     * A bigger basis than it needed when the label beside it was three
     * hundred pixels of prose. The group is now five icon buttons, a date
     * field and a three-character chip - about 440 px - and asking for 460
     * is what keeps the chip on the same line as the arrows it belongs to
     * instead of dropping it onto a second row of its own.
     *
     * Still `1 1`, so on a phone it shrinks and wraps rather than pushing
     * the button rail off the edge.
     */
    flex: 1 1 460px;
    min-width: 0;
}

.weeklabel
{
    margin-left: 8px;
    flex: 0 1 auto;
    min-width: 0;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    color: var(--ink-soft);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.topbar-actions
{
    display: flex;
    flex-wrap: wrap;
    /*
     * Centre, not stretch. With the default a wrapped row would stretch its
     * items to the tallest one on that line, so the day the rail wraps every
     * button on the shorter row would grow. Centring is also the belt to
     * .btn's min-height braces: if anything in here ever does end up a
     * different height, it still sits on the same middle line as its
     * neighbours instead of riding two pixels high.
     */
    align-items: center;
    gap: 8px;
    row-gap: 6px;

    /*
     * MAY SHRINK, AND THEREFORE MAY WRAP.
     *
     * This used to be `flex: 0 0 auto` with a note about never shrinking, so
     * the controls stayed hard against the right edge where people aim at
     * them without looking. That is a good instinct on a wide screen and it
     * was wrong: with a fixed basis the row keeps its full content width and
     * simply overflows the window, so on a narrow browser Print was cut in
     * half and Booking, Account and Sign out were off the screen entirely -
     * unreachable, with no scrollbar that obviously led to them.
     *
     * Being aimable is worth something. Being present is worth more.
     *
     * `min-width: 0` is the part that is easy to leave out: without it a flex
     * item refuses to go below its content's minimum width and shrinking is
     * allowed in theory only.
     */
    flex: 1 1 auto;
    min-width: 0;
    justify-content: flex-end;
}

/*
 * THE ORDER NEVER CHANGES.
 *
 * There WAS a rule here that pulled the primary action and the sign-out ahead
 * of Print and Room overview below 900 px, on the theory that the most
 * important controls should survive on the first row.
 *
 * It is gone, and the reason is worth keeping. People aim at these buttons by
 * position, not by reading them: after a week of use, Reports is "third from
 * the left" and Sign out is "the last one". A rail that rearranges itself at
 * some width nobody can see the boundary of means the button under your
 * pointer depends on how wide the window happens to be - and the one that
 * moves furthest is the one that signs you out.
 *
 * Wrapping already keeps everything reachable. Reordering only made it
 * unpredictable.
 */

/* ------------------------------------------------------------------ */
/* buttons, chips, pills                                               */
/* ------------------------------------------------------------------ */

.btn
{
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    padding: 7px 14px;

    /*
     * ONE HEIGHT FOR EVERY BUTTON, and it is stated rather than inherited.
     *
     * Every button in here used to be as tall as whatever was inside it: a
     * pill with 14px text and a normal line box came out 36.3 px, an icon
     * button carrying a 16px glyph on `line-height: 1` came out 32, and a
     * <summary> holding a 15px SVG came out 31. Side by side in the header
     * that put the ? and the ☰ about two and a half pixels higher than the
     * words next to them - not enough to name, exactly enough to look
     * unfinished.
     *
     * Chasing it per control would be five fixes and a sixth the next time
     * somebody puts a new shape in a button. A minimum height is one fix: the
     * box is the button, the content sits in the middle of it, and what the
     * content happens to be stops mattering.
     *
     * 36px because that is what the pills already were, so nothing that is
     * currently right has to move.
     */
    min-height: 36px;

    border: 1px solid var(--line);
    border-radius: 999px;
    background: var(--surface);
    color: var(--ink);
    font: inherit;
    font-weight: 600;
    cursor: pointer;
    box-shadow: var(--shadow-1);
    user-select: none;
}

.btn:hover
{
    transform: translateY(-1px);
    box-shadow: var(--shadow-2);
    border-color: var(--line-strong);
}

.btn:active
{
    transform: translateY(0);
    box-shadow: var(--shadow-1);
}

.btn-icon
{
    /* Square, so the pill's 999px radius draws an actual circle. Matches
     * .btn's min-height - the two numbers are the same number and want to
     * stay that way. */
    width: 36px;
    padding: 7px 0;
    font-size: 16px;
    line-height: 1;
    flex: none;                   /* never squeezed out of the rail */
}

/* ------------------------------------------------------------------ */
/* The ? button                                                        */
/*                                                                     */
/* Borrows the language picker's pop-over wholesale - same shape, same */
/* position rules, same outside-click handling - because a second kind */
/* of pop-over in the same rail would be a second thing to learn for   */
/* no gain. Only the summary differs: a question mark in a 34px round  */
/* button rather than a word.                                          */
/* ------------------------------------------------------------------ */

.helppick
{
    position: relative;
}

.helppick-btn
{
    font-weight: 700;
    list-style: none;             /* it is a <summary> */
}

.helppick-btn::-webkit-details-marker
{
    display: none;
}

/* Wider than the language list, because each entry carries a sentence saying
 * who it is for - the whole point of offering two - and the caveat underneath
 * is three lines of prose. `min()` and not a fixed width so it still fits a
 * phone held upright. */
.helppick-pop
{
    width: min(340px, 92vw);
}

/* ------------------------------------------------------------------ */
/* The faculty's own logo, when there is one                           */
/*                                                                     */
/* Dropped into public/logo/<school code>.svg|png|webp|jpg - see       */
/* templates/_brand.php. Nothing else about the header changes.        */
/* ------------------------------------------------------------------ */

.brand-logo
{
    /*
     * HEIGHT IS FIXED, WIDTH IS WHATEVER THE LOGO IS.
     *
     * Faculty logos arrive in every shape there is - a tall crest, a wide
     * wordmark, a square seal - and the one thing the header can promise is
     * its own height. Constraining the height and letting the width follow
     * keeps the row exactly as tall as it was and never distorts anybody's
     * coat of arms.
     *
     * The max-width is the guard against the wide ones: a 5:1 wordmark at
     * 34px tall is 170px, which is fine, but somebody will send a banner.
     * At that point it shrinks below 34px rather than pushing the week
     * arrows off the screen.
     */
    height: 34px;
    max-width: 240px;
    width: auto;
    object-fit: contain;
    display: block;

    /*
     * NEVER SHRUNK BY THE ROW.
     *
     * The header is a flex row, and a flex item with `width: auto` is fair
     * game for shrinking. On a narrow window that squashed a 260x60 wordmark
     * down to a 34px square - the height held, the width did not, and the
     * logo came out as a crushed sliver of itself. `flex: none` says the
     * intrinsic size is the size; if the row runs out of space it wraps,
     * which it is already set up to do.
     */
    flex: none;
}

/* The corner is a link now, so it must not look like body text that happens
 * to be blue, and it must not pick up an underline on hover. */
.brand
{
    text-decoration: none;
    color: inherit;

    /*
     * AND IT DOES NOT SHRINK.
     *
     * It used to be `flex: 0 1 auto`, which was harmless while the corner was
     * three short words: the header squeezed it a little on a narrow screen
     * and nothing looked wrong. Put an image in it and the same squeeze
     * crushed a 250x60 wordmark into a 34px square - the height held, the
     * width did not, and a faculty's logo came out as a smear.
     *
     * The header already wraps, so refusing to shrink costs nothing: on a
     * phone the button rail drops to its own line, which is what it did
     * before and what it should do.
     */
    flex: none;
}

.brand:hover .brand-name,
.brand:hover .brand-logo
{
    opacity: .85;
}

/* ------------------------------------------------------------------ */
/* The menu, and the person menu                                       */
/*                                                                     */
/* Both borrow the language picker's pop-over - same shape, same       */
/* position rules, same outside-click handling - because a third kind  */
/* of pop-over in one rail would be a third thing to learn for no      */
/* gain. They differ only in what the summary is.                      */
/* ------------------------------------------------------------------ */

.menupick,
.whopick,
.noticepick
{
    position: relative;
}

.menupick-btn,
.whopick-btn,
.noticepick-btn
{
    list-style: none;             /* they are <summary> elements */
}

.menupick-btn::-webkit-details-marker,
.whopick-btn::-webkit-details-marker,
.noticepick-btn::-webkit-details-marker
{
    display: none;
}

.menupick-btn svg,
.noticepick-btn svg
{
    display: block;
}

/* The signed-in person's name. Capped, because a name is somebody else's
 * data and "Prof. dr. Marija Novak-Kovačević" would be the widest thing in
 * the header - which is the problem this whole rail was rebuilt to solve. */
.whopick-btn
{
    max-width: 190px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    display: block;
}

.menupick-pop
{
    width: min(280px, 92vw);
}

.whopick-pop
{
    width: min(240px, 92vw);
}

.menupick-list
{
    display: grid;
    gap: 2px;
    margin-bottom: 10px;
}

/*
 * The last block, for the entries that belong to no section - Settings.
 *
 * A rule instead of a heading. It is not a view and it is not data entry, and
 * a section named after its only member would be worse than none; a hairline
 * is enough to say "and this, separately". See _menu.php.
 */
.menupick-tail
{
    margin-top: 2px;
    padding-top: 8px;
    border-top: 1px solid var(--line);
}

/*
 * The rows are links and buttons in the same list, so they are styled as one
 * thing rather than as an anchor that happens to sit next to a button. A
 * <button> brings its own font, padding, border and background, and every one
 * of those has to be unset or the row it is in visibly disagrees with the row
 * above it.
 */
.menupick-item
{
    display: block;
    width: 100%;
    padding: 7px 8px;
    border: 0;
    border-radius: var(--radius-s);
    background: transparent;
    box-shadow: none;
    color: var(--ink);
    font: inherit;
    font-size: 13px;
    text-align: left;
    text-decoration: none;
    cursor: pointer;
}

.menupick-item:hover
{
    background: var(--surface-2);
}

/* The one row that undoes something. Not red until you are on it - a red
 * word sitting in a list of black ones pulls the eye to the exit every time
 * the menu opens. */
.menupick-danger:hover
{
    background: var(--danger-tint, #fdeceb);
    color: var(--danger, #b3261e);
}

.menupick-form
{
    margin: 0;
    display: block;
}

/* The `.menupick-lang` rules that used to sit here are gone with the block
 * they styled: the language switch was nested inside this menu and moved back
 * into the rail on 3 Sep 2026, where the plain `.langpick` rules further down
 * already position it. See templates/_menu.php. */

/* ------------------------------------------------------------------ */
/* The week number, where a sentence used to be                        */
/* ------------------------------------------------------------------ */

/*
 * "Week 32 · 3. 8. – 7. 8. 2026" was three hundred pixels restating what the
 * five day columns underneath already say. The number survives because in
 * this product it is not decoration - a reservation is stored as
 * From_Week..To_Week and the office schedules by it. The rest is the date
 * field's tooltip now.
 */
.weekchip
{
    display: inline-flex;
    align-items: center;
    padding: 4px 10px;
    border-radius: 999px;
    background: var(--surface-2);
    color: var(--ink-soft);
    font-size: 12.5px;
    font-weight: 650;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    cursor: default;

    /* Never the thing that costs the nav its second line: it is the least
     * important item on the row, so it gives way before the arrows or the
     * date field do. */
    flex: 0 0 auto;
}

/*
 * "TEDEN 49" ALMOST EVERYWHERE; "T.49" ONLY ON A PHONE.
 *
 * Both spellings are in the HTML and CSS chooses - no script, so the choice
 * follows the window as it is dragged rather than the width it had when the
 * page loaded. The full text stays in the chip's tooltip either way.
 *
 * The swap used to happen at 900px, which threw the word away on screens that
 * had plenty of room for it. Two things made that unnecessary: the chip stops
 * shrinking the rest of the row (`flex: 0 0 auto` above), and the date field
 * gave back fourteen pixels. Measured after both: the long spelling stays on
 * the same line as the date field at 1000, 900, 860, 820, 780, 700, 640 and
 * 600 - every width worth calling a window.
 *
 * At 560 the row genuinely runs out, so that is where the abbreviation lives
 * now - the same line this file uses everywhere else to decide something is a
 * handset.
 */
.weekchip-short
{
    display: none;
}

@media (max-width: 560px)
{
    .weekchip-long
    {
        display: none;
    }

    .weekchip-short
    {
        display: inline;
    }
}

/*
 * AND ON A PHONE THE CHIP GOES UP INTO THE CORNER.
 *
 * Below about 440 px the week nav can no longer hold the chip and wraps it
 * onto a LINE OF ITS OWN - a whole row of the header, on the screen with the
 * least height to spare, spent on three characters. Measured on demo in
 * Chromium: the header is 139 px tall at 460 and 171 px at 440, and the row
 * the chip lands on is empty apart from it.
 *
 * Meanwhile the FIRST row - the school's name or logo - has been running out
 * to an empty right-hand edge since 520 px: 408 free pixels at 480, 342 at
 * 414, 288 at 360. So the chip is rendered a second time there (see
 * _weekchip.php) and the two copies swap at 500 px - inside the band where
 * the first row is already free and above the width where the nav gives up,
 * so neither state is ever seen half-applied.
 *
 * `margin-left: auto` and not `position: absolute`: the corner copy stays IN
 * the flow, so on the day a faculty's logo is wide enough to need the whole
 * first row for itself, the chip wraps like everything else instead of being
 * painted on top of somebody's coat of arms.
 */
.weekchip-corner
{
    display: none;
}

@media (max-width: 500px)
{
    .weekchip-corner
    {
        display: inline-flex;
        margin-left: auto;
    }

    .weeknav .weekchip
    {
        display: none;
    }
}

/* ------------------------------------------------------------------ */
/* The refresh button, while it is refreshing                          */
/*                                                                     */
/* A reload of a page that looks identical afterwards is               */
/* indistinguishable from a button that did nothing, so the arrows     */
/* turn while the request is in flight. It is removed by the reload    */
/* itself - there is no code that takes the class off, because the     */
/* document it was set on stops existing.                              */
/* ------------------------------------------------------------------ */

.btn[data-refresh] svg
{
    display: block;
    transition: transform .15s ease;
}

.btn[data-refresh]:hover svg
{
    transform: rotate(45deg);
}

.btn[data-refresh].is-spinning svg
{
    animation: wtt-spin .7s linear infinite;
    transition: none;
}

@keyframes wtt-spin
{
    to { transform: rotate(360deg); }
}

/* Somebody who has asked not to see movement gets the button without the
 * animation rather than without the feedback - the hover tilt and the pressed
 * state still say it was pressed. */
@media (prefers-reduced-motion: reduce)
{
    .btn[data-refresh] svg,
    .btn[data-refresh].is-spinning svg
    {
        animation: none;
        transition: none;
        transform: none;
    }
}

.btn-primary
{
    background: linear-gradient(180deg, #3f7cf0, var(--brand-ink));
    border-color: transparent;
    color: #fff;
}

.btn-primary:hover
{
    filter: brightness(1.06);
}

.btn-add
{
    border-style: dashed;
    color: var(--brand-ink);
    background: transparent;
    box-shadow: none;
    list-style: none;             /* it doubles as a <summary> */
}

.btn-add::-webkit-details-marker
{
    display: none;
}

.chip
{
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 5px 8px 5px 12px;
    border-radius: 999px;
    background: color-mix(in srgb, var(--brand) 12%, var(--surface));
    border: 1px solid color-mix(in srgb, var(--brand) 30%, var(--line));
    font-weight: 600;
}

.chip-x
{
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 18px;
    height: 18px;
    border-radius: 50%;
    color: var(--ink-soft);
    font-size: 14px;
    line-height: 1;
}

.chip-x:hover
{
    background: rgb(0 0 0 / .08);
    color: var(--danger);
}

.chip-school
{
    padding: 3px 10px;
    font-size: 12px;
    color: var(--ink-soft);
    background: var(--surface-2);
    border-color: var(--line);
}

/* ------------------------------------------------------------------ */
/* filter bar                                                          */
/* ------------------------------------------------------------------ */

.filterbar
{
    display: flex;
    /* flex-start, not center: the panels are boxes of different heights now
     * (one may hold six chips, another none) and centring them makes the row
     * look broken. */
    align-items: flex-start;
    gap: 14px;
    flex-wrap: wrap;
    padding: 12px 20px;
}

/* Segmented control for the browse axis. */
.axis-switch
{
    display: inline-flex;
    padding: 3px;
    border-radius: 999px;
    background: var(--surface-2);
    border: 1px solid var(--line);
}

/*
 * A SEGMENT IS AS TALL AS A BUTTON, for the same reason .btn states its own
 * height rather than inheriting one - see the long note there.
 *
 * The arithmetic is the wrapper's: 1px border + 3px padding on each side, so
 * a 28px segment makes a 36px pill and the control lines up with every .btn
 * beside it. It was 40px - a segment of ~32px inside the same wrapper - which
 * put "Karkoli od tega / Vse skupaj" four pixels taller than the two buttons
 * on its row. Four pixels is not enough to name and exactly enough to look
 * unfinished, which is how Mišo found it on 7 Sep 2026.
 *
 * inline-flex so the text centres in that stated height instead of sitting
 * wherever its line box lands.
 */
.axis
{
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 28px;
    padding: 0 14px;
    border-radius: 999px;
    font-weight: 600;
    color: var(--ink-soft);
}

.axis:hover
{
    color: var(--ink);
}

.axis.is-active
{
    background: var(--surface);
    color: var(--ink);
    box-shadow: var(--shadow-1);
}

.chips
{
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
}

/* The <details> dropdown picker. */
.picker
{
    position: relative;
}

.picker-pop
{
    position: absolute;
    z-index: 40;
    top: calc(100% + 8px);
    left: 0;
    width: min(320px, 86vw);
    max-height: 340px;
    display: flex;
    flex-direction: column;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-l);
    box-shadow: var(--shadow-3);
    overflow: hidden;
}

/* The programme → year → branch cascade inside the group picker. */
.cascade
{
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: 10px 10px 0;
}

.cascade-sel
{
    padding: 8px 10px;
    border: 1px solid var(--line);
    border-radius: var(--radius-m);
    background: var(--surface-2);
    color: var(--ink);
    font: inherit;
    font-weight: 600;
}

.cascade-sel:disabled
{
    opacity: .45;
}

.picker-item.is-child
{
    padding-left: 28px;
    color: var(--ink-soft);
}

.picker-search
{
    margin: 10px;
    padding: 8px 12px;
    border: 1px solid var(--line);
    border-radius: var(--radius-m);
    background: var(--surface-2);
    color: var(--ink);
    font: inherit;
}

/*
 * "Select all", between the search box and the list.
 *
 * IT IS A HEADER, NOT A ROW. It sits outside the scrolling list on purpose:
 * inside it, it would scroll away after four rooms and be a control people
 * cannot find - the same argument that put Apply at the foot. The rule under
 * it is what says the list starts below, and the count is pushed to the far
 * end so the eye reads name-then-number the way it does on every row beneath.
 *
 * Smaller and quieter than the rows it commands. The rows are the answer; this
 * is a control over them, and drawing it at the same weight would make the
 * first entity in every list look like it had been given a heading.
 */
.picker-all
{
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    gap: 10px;
    margin: 0 10px 6px;
    padding: 4px 12px 8px;
    border-bottom: 1px solid var(--line);
    color: var(--ink-soft);
    font-size: 12px;
    font-weight: 600;
}

/* `display: flex` above beats the browser's own `[hidden] { display: none }`,
 * which is a one-line rule and loses to any class selector. Without this the
 * control would be on the page with scripting off - which is the one state it
 * must not be in, because there it does nothing at all. */
.picker-all[hidden]
{
    display: none;
}

.picker-all input[type="checkbox"]
{
    flex: 0 0 auto;
    width: 17px;
    height: 17px;
    margin: 0;
    accent-color: var(--brand);
}

.picker-all label
{
    cursor: pointer;
}

/* The count goes to the far end. `.picker-meta` gives it the tabular figure
 * and the quiet colour it shares with a room's seat count; only where it sits
 * is different here. */
.picker-all .picker-meta
{
    margin-left: auto;
}

.picker-list
{
    overflow: auto;
    padding: 0 6px 8px;
}

/*
 * The multi-select form fills the pop-over and does its own scrolling, so
 * Apply can sit still at the foot while four hundred rooms go past. The
 * min-height:0 is the line that matters: without it a flex child refuses to
 * shrink below its content and the list pushes the button out of the box.
 */
.picker-form
{
    display: flex;
    flex-direction: column;
    min-height: 0;
    overflow: hidden;
}

.picker-item
{
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 12px;
    border-radius: var(--radius-s);
    color: var(--ink);
    cursor: pointer;
}

/*
 * The school's own code for a subject, or a room's seat count.
 *
 * QUIETER THAN THE NAME, ON PURPOSE. The name is what somebody reads down a
 * list of four hundred; the number is what they check once they have found
 * the row, or what they typed into the search box to get here. Making the two
 * the same weight would turn the picker into a table.
 *
 * A tabular figure so the codes line up in a column of their own, which is
 * what makes a list of them scannable at all.
 */
.picker-meta
{
    margin-left: 8px;
    color: var(--ink-soft);
    font-size: 11px;
    font-weight: 500;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/* The whole row is the target - it is a <label> - but a checkbox at the
 * browser's default size looks left over beside it rather than meant. */
.picker-item input[type="checkbox"]
{
    flex: 0 0 auto;
    width: 17px;
    height: 17px;
    margin: 0;
    accent-color: var(--brand);
}

.picker-actions
{
    flex: 0 0 auto;
    display: flex;
    justify-content: flex-end;
    gap: 8px;
    padding: 8px 10px;
    border-top: 1px solid var(--line);
    background: var(--surface);
}

.picker-item:hover
{
    background: color-mix(in srgb, var(--brand) 10%, transparent);
}

.picker-empty
{
    display: block;
    padding: 12px;
    color: var(--ink-soft);
    text-align: center;
}

/* ------------------------------------------------------------------ */
/* error + empty states                                                */
/* ------------------------------------------------------------------ */

.errorbar
{
    margin: 0 20px 12px;
    padding: 10px 16px;
    border-radius: var(--radius-m);
    background: color-mix(in srgb, var(--danger) 12%, var(--surface));
    border: 1px solid color-mix(in srgb, var(--danger) 40%, var(--line));
    color: var(--danger);
    font-weight: 600;
}

.empty-state
{
    /* 18 px, not 48: with the empty filter panels now collapsed there is no
     * band of blank colour above this any more, and the old margin was
     * measured against that band. */
    margin: 18px auto 10px;
    text-align: center;
    color: var(--ink-soft);
}

/* ------------------------------------------------------------------ */
/* busy state                                                          */
/* ------------------------------------------------------------------ */

/*
 * A filter change is a full page load, and against a school with thousands
 * of groups it is not instant. Without a signal the page looks like it
 * ignored the click, so people click again - which is both slower and how
 * duplicate bookings happen.
 *
 * Set by app.js the moment a filter link or the cascade is used, cleared by
 * the new page simply existing. With JavaScript off the navigation is
 * identical, just without the guard.
 *
 * ONE class, and it paints nothing. See the long note at the top of app.js
 * for why everything visible was taken out of it: what a person needs to
 * know is that the click was heard, and what the application can uniquely
 * do about that is refuse the second one. Announcing the wait is the
 * browser's job, and the browser is better at it - it knows how long the
 * wait actually is.
 */
body.is-busy,
body.is-busy a,
body.is-busy button,
body.is-busy summary,
body.is-busy select
{
    cursor: progress;
}

/*
 * NO POINTER-EVENTS GUARD ANY MORE, and this is the third lesson this one
 * feature has taught.
 *
 * A rule stood here putting the filter bar and the week nav behind
 * pointer-events:none while busy, to refuse the second click. (Described,
 * not quoted: the selftest greps for the pattern and a verbatim quote in a
 * comment reads as the rule being back.) On an 80 ms page the refusal
 * window was invisible; on a group-filtered page the server takes
 * 170-500 ms, and a person stepping through weeks at their natural pace
 * lands every second click inside the window - which was reported,
 * accurately, as "every second click does not change the week".
 *
 * And the refusal never protected anything here. Everything in the filter
 * bar and the week nav is a GET link: clicking one twice cannot book a room
 * twice, it just replaces the navigation in flight. The double booking the
 * guard was named for is a POST, and the POST now has its own guard in
 * app.js, on the form that actually posts.
 */

/*
 * AND NOTHING ELSE. THE BUSY STATE PAINTS NOTHING AT ALL.
 *
 * There used to be two more things here: the filter bar dimmed to 55%, and a
 * thin bar ran across the top after a delay. Both are gone.
 *
 * The dimming was the loud one - a large area changing brightness and
 * changing back inside a couple of hundred milliseconds, which is exactly
 * what "the screen draws itself twice" looks like to the person using it.
 *
 * The bar needed a delay to decide whether a wait was worth announcing, and
 * that delay was a constant guessed on one machine about every other one.
 * Pages here paint in about 100 ms; a faculty at the end of a slow line is a
 * different number, and no figure in this file is right for both. Nor can a
 * threshold ever be: whatever is chosen, some page finishes just past it and
 * the bar flashes instead of informing.
 *
 * The browser's own tab spinner already says "something is happening", and
 * it is never wrong about the wait, because it is the one waiting. What this
 * file keeps is the part it alone can do: refusing the second click.
 */

/* ------------------------------------------------------------------ */
/* jump to a date                                                      */
/* ------------------------------------------------------------------ */

/*
 * The arrows walk a week or a month at a time, which is right for "next
 * week" and useless for "the Tuesday of the exam". The legacy application
 * had a calendar field in the header and people used it constantly, so the
 * week label becomes one: it already says which week is on screen, and now
 * clicking it says which week should be.
 */
.datejump
{
    display: inline-flex;
    align-items: center;
    gap: 6px;
    margin-left: 8px;
}

.datejump-input
{
    font: inherit;
    font-size: 13px;
    font-variant-numeric: tabular-nums;
    color: var(--ink);
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: 999px;
    padding: 5px 10px;
    cursor: pointer;

    /*
     * NARROWER THAN THE BROWSER WANTS IT.
     *
     * Left alone, Chromium gives `input[type=date]` 146px and puts the picker
     * icon hard against the right edge - which leaves a visible gap between
     * "18/12/2025" and the icon, about fifteen pixels of nothing in the
     * middle of the busiest row on the page.
     *
     * Measured at 13px in this font: 132 leaves a comfortable gap, 116 has
     * the icon touching the digits, and 110 clips the year to "202". So 132,
     * with sixteen pixels of headroom over the point where it starts to look
     * wrong. Every date format Chromium renders here is ten characters -
     * 18/12/2025, 18.12.2025, 2025-12-18 - so this holds per locale; if one
     * ever formats wider, this is the number to raise.
     *
     * `field-sizing: content` would be the principled way to ask for this and
     * it does nothing here: a date input's automatic width ALREADY is its
     * content width, and the gap is inside the control's own layout rather
     * than slack around it. Measured, not assumed.
     *
     * The fourteen pixels this gives back are what let the week chip keep its
     * full spelling - see .weekchip-long.
     */
    width: 132px;
}

.datejump-input:hover,
.datejump-input:focus
{
    border-color: color-mix(in srgb, var(--brand) 45%, var(--line));
    outline: none;
}

/* ------------------------------------------------------------------ */
/* the programme cascade, on the filter bar itself                     */
/* ------------------------------------------------------------------ */

/*
 * Programme -> year -> branch is how everyone at a faculty thinks about a
 * timetable, and it was hidden inside the groups panel's + popover where
 * nobody found it. It is the FIRST thing on the filter bar now, on its own
 * row above the four panels, exactly as the legacy front end had it.
 *
 * It narrows two panels at once - groups AND subjects - which is why it
 * cannot live inside either of them.
 */
.cascade-row
{
    display: flex;
    /* Explicitly a ROW. The plain .cascade rule further up sets column,
     * because that is right inside a narrow popover - which is where the
     * cascade used to live. Here it is a full-width bar and must not
     * inherit that. */
    flex-direction: row;
    align-items: center;
    flex-wrap: wrap;
    gap: 6px;
    width: 100%;
    padding: 0;
    margin: 0 0 8px;
}

.cascade-row .field-label
{
    font-size: 11.5px;
    font-weight: 650;
    color: var(--ink-soft);
    text-transform: uppercase;
    letter-spacing: .03em;
}

.cascade-row .cascade-sel
{
    font: inherit;
    font-size: 13px;
    color: var(--ink);
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: 999px;
    padding: 5px 10px;
    max-width: 260px;
}

.cascade-row .cascade-sel:disabled
{
    opacity: .5;
    cursor: not-allowed;
}

.cascade-row .cascade-sel:not(:disabled):hover
{
    border-color: color-mix(in srgb, var(--brand) 45%, var(--line));
}

.empty-art
{
    width: 72px;
    height: 72px;
    margin: 0 auto 12px;
    border-radius: 22px;
    background:
        linear-gradient(135deg,
            color-mix(in srgb, var(--brand) 30%, var(--surface)),
            color-mix(in srgb, var(--brand) 8%, var(--surface)));
    box-shadow: var(--shadow-2);
}

/* ------------------------------------------------------------------ */
/* the board                                                           */
/* ------------------------------------------------------------------ */

.board-wrap
{
    padding: 4px 20px 24px;
}

.board
{
    display: flex;
    gap: 0;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-l);
    box-shadow: var(--shadow-2);
    overflow-x: auto;               /* narrow screens scroll, wide fit    */
    scroll-snap-type: x proximity;
    padding-bottom: 12px;           /* the last ruler tick needs headroom */
}

/* time ruler ------------------------------------------------------- */

.ruler
{
    /* 64, not 56: the labels now sit to the LEFT of a tick mark rather than
     * flush against the border, and a four-character time at 11 px plus the
     * tick and its gap needs the extra eight pixels. Narrower and the labels
     * would either clip or fight the gutter's own border. */
    flex: 0 0 64px;
    border-right: 1px solid var(--line);
    /* Sticky, so the time gutter stays visible while day columns scroll
     * horizontally under it (phones swipe day by day). Also defeats the
     * initial scroll-snap pulling the first day over the gutter. */
    position: sticky;
    left: 0;
    z-index: 10;
    background: var(--surface);
}

.ruler-head
{
    height: 56px;
    border-bottom: 1px solid var(--line);
}

.ruler-canvas
{
    position: relative;
    height: var(--grid-h);
}

/*
 * A TIME LABEL AND ITS TICK.
 *
 * The label alone is not enough, and this is the one thing about the ruler
 * people notice when it is missing. A column of numbers floating in a gutter
 * does not tell you WHICH line each number belongs to - the eye has to travel
 * from "09:15" across empty space and pick the right one of four faint dashed
 * rules. A short mark that touches the gutter's border closes that gap: the
 * label, the tick and the rule in the day column are one continuous line.
 *
 * It matters most exactly where the grid is busiest. A school on 15-minute
 * lines has four labels per hour, and without ticks they read as a list
 * rather than as a scale.
 *
 * The tick echoes the rule it points at: solid and longer on the hour, short
 * and faint in between - the same distinction .rule and .rule.is-dashed make
 * three inches to the right. Two marks for one thing must not disagree.
 */
.tick
{
    position: absolute;
    right: 0;
    display: flex;
    align-items: center;
    gap: 5px;
    padding-right: 5px;
    padding-left: 4px;
    transform: translateY(-50%);
    font-size: 11px;
    color: var(--ink-soft);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;

    /*
     * OPAQUE, so nothing is ever drawn THROUGH a time.
     *
     * The header's bottom border runs the full width of the gutter at exactly
     * the height of the first tick - and the tick is centred on its own line,
     * so that border passed straight through "07:00". The label won: it now
     * paints over whatever is behind it, the header rule closes cleanly where
     * the label starts, and the tick mark to its right finishes the line.
     *
     * A label that punches a hole in the gridline behind it is the ordinary
     * treatment for an axis, and it holds for any line that ever lands there,
     * rather than only for the one that does today.
     */
    background: var(--surface);
}

.tick::after
{
    content: "";
    display: block;
    width: 5px;
    height: 0;
    border-top: 1px dashed color-mix(in srgb, var(--line-strong) 70%, transparent);
}

.tick.is-hour
{
    font-weight: 700;
    color: var(--ink);
}

.tick.is-hour::after
{
    width: 9px;
    border-top: 1px solid var(--line-strong);
}

/*
 * THE GUTTER WHEN IT CARRIES THE SCHOOL'S OWN WORDING.
 *
 * A school may ask for tbtime's own text down the left instead of the clock
 * (settings: "What the ruler down the left says"). "07:00" is four characters
 * and 64 px was measured for exactly that; UM FGPA's OPOMBA is six, and a
 * period name can be longer. TimeGrid caps the text at ten characters, and
 * 88 px is what ten characters at 11 px plus the tick and its gap need.
 *
 * BOTH NUMBERS MOVE TOGETHER. .day's scroll-margin-left exists so a snapped
 * day column does not slide under the sticky gutter; widening one without the
 * other hides the first day's name on every phone. They are written next to
 * each other here for that reason - the pair 64/64 above has the same note.
 *
 * Not tabular-nums either: that lines up digits and does nothing for words
 * except make them look mechanical.
 */
.board.is-labels .ruler
{
    flex-basis: 88px;
}

.board.is-labels .day
{
    scroll-margin-left: 88px;
}

.board.is-labels .tick
{
    font-variant-numeric: normal;
}

/* day columns ------------------------------------------------------ */

.day
{
    flex: 1 1 0;
    min-width: 168px;
    scroll-snap-align: start;
    /* Snap positions must account for the sticky time gutter, or the
     * snapped day slides underneath it and its name disappears. Same 64 as
     * .ruler's flex-basis - if one moves, so must this. */
    scroll-margin-left: 64px;
    border-right: 1px solid var(--line);
}

.day:last-child
{
    border-right: 0;
}

.day-head
{
    position: sticky;
    top: 0;
    display: flex;
    align-items: baseline;
    gap: 8px;
    height: 56px;
    padding: 10px 12px;
    border-bottom: 1px solid var(--line);
    background: var(--surface);
}

.day-name
{
    font-weight: 800;
    letter-spacing: .04em;
}

.day-date
{
    color: var(--ink-soft);
    font-variant-numeric: tabular-nums;
}

.day.is-today .day-name
{
    color: var(--brand-ink);
}

.day.is-today .day-head::after
{
    content: "";
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--brand);
    align-self: center;
}

/*
 * THE DAY THE READER IS ON, and why it is quieter than today.
 *
 * The week arrows step the ANCHOR - the date in the URL - so Wednesday to
 * Wednesday rather than Wednesday to Monday. On a phone that shows itself:
 * app.js scrolls the board to this column. On a wide screen the whole week is
 * on screen and nothing moves, so without a mark the question "which day am I
 * on" has no answer at all in a week that does not contain today.
 *
 * A THIN RING ON THE HEADING, not a second dot and not a tinted column.
 *
 *   - Today is a fact about the world; the anchor is a fact about where this
 *     reader has browsed to. Two facts of different weight must not be drawn
 *     with the same mark, or the blue dot stops meaning today.
 *   - A tinted column would compete with the holiday hatch and the weekend
 *     tint, which are facts about the SCHOOL. The heading is the only part of
 *     a column that is about navigation.
 *   - `outline` and not `border`: a border would change the heading's size and
 *     nudge every card in the column down by two pixels on the one day that is
 *     marked, which is a layout that moves for a decoration.
 *
 * When the anchor IS today the column carries both classes: the dot is drawn,
 * the ring is drawn around it, and they agree - which is the honest picture,
 * because on that day the two facts genuinely are the same one.
 */
.day.is-anchor .day-head
{
    outline: 1px solid color-mix(in srgb, var(--brand) 45%, transparent);
    outline-offset: -1px;
    border-radius: 8px;
}

.day-holiday
{
    margin-left: auto;
    font-size: 11px;
    font-weight: 700;
    color: #b3671f;
}

.day.is-holiday .day-canvas
{
    background: repeating-linear-gradient(
        -45deg,
        transparent 0 14px,
        rgb(179 103 31 / .05) 14px 28px);
}

.day-canvas
{
    position: relative;
    height: var(--grid-h);
}

/* horizontal rules from the school's bell schedule */

.rule
{
    position: absolute;
    left: 0;
    right: 0;
    height: 0;
    pointer-events: none;
}

.rule.is-solid
{
    border-top: 1px solid var(--line);
}

.rule.is-dashed
{
    border-top: 1px dashed color-mix(in srgb, var(--line) 70%, transparent);
}

/* the filter row ---------------------------------------------------- */
/*
 * The same panel row the booking screen uses, and the legacy application's
 * before it: one panel per entity kind, all open at once. The classes come
 * from booking.css, which this page does not load - so the few that the
 * timetable also needs live here. Duplication of four rules, against threading
 * a stylesheet the other 99% of visitors would download for nothing.
 */

/*
 * A WRAPPING LINE, NOT A FOUR-COLUMN GRID - changed 7 Sep 2026 so the panels
 * could become resizable.
 *
 * The grid was four equal tracks, and a track is exactly what a resized panel
 * cannot escape: an inline width on a grid item does not widen its track, it
 * makes the item overflow into its neighbour. Every way of allowing width in
 * a grid ends with either the row overflowing the page or one panel drawn on
 * top of the next.
 *
 * A flex line has the property the grid was missing: an item with a width of
 * its own KEEPS it (see .epanel.is-sized), the rest divide what is left, and
 * when they run out they wrap to a second line. Nothing can reach past the
 * page, which is the constraint that mattered.
 *
 * `1 1 200px` reproduces what the grid did for anybody who never drags: four
 * equal panels on a wide screen, two on a narrow one, one on a phone - the
 * old @media rule for 820px is gone because the basis now decides that on its
 * own, at whatever width the panels actually stop fitting rather than at a
 * number somebody guessed.
 */
.filter-row
{
    display: flex;
    flex-wrap: wrap;

    /*
     * STRETCH, NOT flex-start - changed 7 Sep 2026.
     *
     * flex-start let every panel take its own natural height, and they do not
     * have one: an empty panel is a header, a panel holding six chips is three
     * times that. On ameu with nothing chosen, three panels drew a body and
     * Rooms drew only its header, so the row ended in a stub. Mišo: "ovi
     * lijevo ajde visoki su ali neka bude kako god samo poravnano".
     *
     * Stretch is not the centring the old note warned about - it makes every
     * panel as tall as the tallest on its line, which is the alignment being
     * asked for. A panel with an inline height from the resize grip keeps it;
     * stretch only applies where the cross size is auto.
     */
    align-items: stretch;

    gap: 10px;
    flex: 1;
    min-width: 0;
}

/*
 * A PANEL YOU CAN PULL TO THE SIZE YOU NEED, from its bottom right corner.
 *
 * The legacy application had this and people used it. `resize` is the
 * browser's own, so the grip, the cursor and the drag itself are native and
 * no JavaScript runs while dragging.
 *
 * BOTH AXES. It was vertical-only for an hour, on the argument that the
 * panels were cells of a fixed four-column grid and widening one would push
 * the row past the page - which was true of a GRID. The row is a wrapping
 * flex line now (see .filter-row), so a panel that is given a width keeps it
 * and the others share what is left, wrapping to the next line when they run
 * out. Nothing can reach past the page, so the objection is gone and the
 * control does what it looks like it does.
 *
 * MINIMUMS, because without them the grip goes the other way and a panel can
 * be dragged shut - Mišo, 7 Sep 2026: "sad ga mogu potegnut previse u
 * kontrolu". 150px still fits a chip and the two header buttons; 76px is the
 * header plus a body worth having. A control that can be destroyed by using
 * it is a trap.
 *
 * `overflow: hidden` is not decoration: `resize` does nothing at all on a
 * visible-overflow box, and it is also what keeps a long chip inside the
 * panel's rounded border instead of spilling past it.
 */
.epanel
{
    min-width: 0;
    display: flex;
    flex-direction: column;
    border: 1px solid var(--line);
    border-radius: var(--radius-m);
    background: var(--surface-2);

    /*
     * EQUAL SHARES, NOT "AT LEAST 200 EACH".
     *
     * `1 1 200px` gave every panel a 200px starting width and then divided
     * only the LEFTOVER between them - so a panel whose header text is short
     * ended up wider than its neighbours for no reason a reader could see. On
     * ameu the row came out 400 / 363 / 403 / 728, with Rooms stretched
     * across the right-hand third of the window. Mišo, 7 Sep 2026: "prostori
     * su desno izduženi čudno ... samo poravnano".
     *
     * `1 1 0` divides the whole line instead, so four panels are four equal
     * quarters whatever their contents. Wrapping still works - min-width below
     * is what decides when they stop fitting, exactly as before.
     */
    flex: 1 1 0;

    /*
     * THE GRIP, AND THE CLIPPING BOX IT NEEDS.
     *
     * `resize` does nothing at all on a visible-overflow box, so these two are
     * one decision, not two. The cost is that `.picker-pop` opens from INSIDE
     * this panel, and a clipping box swallows it - which is what happened for
     * a few hours on 7 Sep 2026: press `+` and the row just reflowed, with
     * every row of the list rendered and none of it visible.
     *
     * The escape is directly below and it is per MOMENT rather than per
     * panel: while a picker is open the panel does not clip, and `resize` is
     * ignored for exactly that long. Nobody drags a panel by its corner while
     * its own pop-over is covering the row.
     *
     * min-width/min-height are what stop a panel being dragged shut - Mišo:
     * "sad ga mogu potegnut previse u kontrolu".
     */
    resize: both;
    overflow: hidden;
    min-width: 150px;
    min-height: 76px;
}

/*
 * AND IT DOES NOT CLIP WHILE THE PICKER IS OPEN.
 *
 * `overflow: hidden` above is not optional - `resize` does nothing at all on a
 * visible-overflow box, which is why it is there. But `.picker-pop` is
 * `position: absolute` inside `.picker`, which is inside this panel, so the
 * same rule that makes the grip work CLIPS THE POP-OVER TO THE PANEL.
 *
 * That shipped on 7 Sep 2026 and Mišo found it the way a customer would: press
 * `+` on Subjects and the panel just gets a little taller, press again and all
 * you can see is the search box. Nothing was broken about the list - the
 * server was rendering all 1523 of Alma Mater's subjects into that pop-over -
 * it was being cut off at the panel's edge, and `.picker-search` is simply the
 * first thing inside it.
 *
 * THE TWO CANNOT BOTH BE TRUE AT ONCE, so this picks per moment rather than
 * per panel: while the picker is open the panel does not clip, and `resize` is
 * ignored for exactly as long as that lasts. Nobody drags a panel by its
 * corner while its pop-over is covering the row, so the grip loses nothing
 * anybody was going to use. Close the picker and both come back.
 *
 * `:has()` because the state lives on a CHILD and CSS has no other way up.
 * Chrome 105+, Safari 15.4+, Firefox 121+; where it is missing the panel
 * behaves as it did before this rule, which is the bug and not something
 * worse. A `.epanel-body` guard in the self-test pins the pairing so the next
 * person to add `overflow` here finds out from a test rather than from Mišo.
 */
.epanel:has(.epanel-picker[open])
{
    overflow: visible;
}

/*
 * A panel the reader has sized KEEPS that size.
 *
 * Set by app.js the moment a drag starts. Without it the inline width the
 * browser writes is just a flex-basis suggestion and the line immediately
 * takes it back - you drag, you let go, it springs to where it was.
 */
.epanel.is-sized
{
    flex: 0 0 auto;
}

/* Nothing to size when there is nothing in it - and an empty box that can be
 * dragged to any shape is an invitation to make a mess of the row. */
.epanel.is-empty
{
    resize: none;
    min-height: 0;
}

/*
 * THE GRIP: three strokes IN THE CORNER, and no box around them.
 *
 * The browser's own is two strokes inside a frame, and that frame - a line
 * down the left of the corner and one across its top - reads as a second
 * border cutting the panel. Painting our own background over
 * ::-webkit-resizer replaces the whole thing, frame included.
 *
 * THREE SEPARATE GRADIENTS, not one repeating one. The repeating version
 * striped the WHOLE square, which is not what a grip looks like - a grip is a
 * few strokes gathered in the corner and nothing anywhere else. Each layer
 * here paints one band at a fixed distance from the bottom-right corner,
 * measured along the corner diagonal, and the square clips it: near the
 * corner that leaves a short stroke, further out a longer one. That is the
 * shape by construction rather than by drawing three lengths by hand.
 *
 * `--ink-soft` and not `--line-strong`: a border colour on a panel background
 * is almost invisible at 1.5px, and this is a control, not a rule.
 *
 * WebKit and Blink only. Firefox draws its own grip and ignores all of it,
 * which is the right degradation - the control still works and still looks
 * like a resize corner, just their version of one.
 */
.epanel::-webkit-resizer
{
    background-color: transparent;
    background-repeat: no-repeat;
    background-image:
        linear-gradient(to top left, transparent 3px,  var(--ink-soft) 3px,  var(--ink-soft) 4.5px,  transparent 4.5px),
        linear-gradient(to top left, transparent 7px,  var(--ink-soft) 7px,  var(--ink-soft) 8.5px,  transparent 8.5px),
        linear-gradient(to top left, transparent 11px, var(--ink-soft) 11px, var(--ink-soft) 12.5px, transparent 12.5px);
}

/*
 * The one line above the room board that explains why a signed-in reader is
 * still being shown every room. Quiet on purpose - it is an explanation, not
 * a warning, and it is above a board somebody came here to read.
 */
.roomnote
{
    margin: 0 0 8px;
    padding: 6px 10px;
    border-radius: var(--radius-m);
    background: var(--surface-2);
    border: 1px solid var(--line);
    color: var(--ink-soft);
    font-size: 12px;
    line-height: 1.4;
}

.roomnote a
{
    color: var(--brand-ink);
    font-weight: 600;
}

@media print
{
    /* Paper has no session and nobody to explain it to. */
    .roomnote { display: none; }
}

/*
 * AND THE SCROLLBAR STOPS SHORT OF IT.
 *
 * The body fills the panel to its bottom edge, so its scrollbar ran the whole
 * height and arrived in the same corner as the grip - two controls in one
 * 16px square, and the one you meant to grab was a guess. The margin gives
 * the grip a band of its own; the scrollbar ends above it.
 */
.epanel-body
{
    margin-bottom: 11px;
}

.epanel-head
{
    display: flex;
    align-items: center;
    gap: 4px;
    padding: 4px 6px 4px 10px;
    border-bottom: 1px solid var(--line);
    background: color-mix(in srgb, var(--brand) 7%, var(--surface));
    border-radius: var(--radius-m) var(--radius-m) 0 0;
}

.epanel-title
{
    flex: 1;
    font-size: 12px;
    font-weight: 650;
}

.epanel-btn
{
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 20px;
    height: 20px;
    border: 1px solid var(--line);
    border-radius: 6px;
    background: var(--surface);
    color: var(--ink-soft);
    font-size: 14px;
    line-height: 1;
    cursor: pointer;
    list-style: none;
    text-decoration: none;
}

.epanel-btn:hover
{
    color: var(--brand-ink);
    border-color: color-mix(in srgb, var(--brand) 40%, var(--line));
}

.epanel-btn::-webkit-details-marker
{
    display: none;
}

/*
 * The popover hangs off the PANEL, not off the little + button.
 *
 * Anchored to the button it was 320 px wide starting at the button's right
 * edge, which for the leftmost panel ran straight off the left of the
 * window - the list was there, you just could not read the half of it that
 * was outside the screen. Hung under the panel it lines up with the thing
 * it belongs to and stays on screen.
 */
.epanel
{
    position: relative;
}

.epanel-picker
{
    position: static;
}

.epanel-picker .picker-pop
{
    left: 0;
    right: auto;
    min-width: 280px;
    max-width: calc(100vw - 24px);
}

/* The last two panels are near the right edge, so theirs open leftwards -
 * otherwise a 320 px popover under a 250 px panel hangs over the edge. */
.filter-row .epanel:nth-last-child(-n+2) .picker-pop
{
    left: auto;
    right: 0;
}

/*
 * THE BODY IS NOT THE THING YOU DRAG - the panel is. See .epanel above.
 *
 * It was the body for the first hour of this feature and that was wrong in a
 * way a screenshot showed immediately: the body grew, the panel around it did
 * not, and the grip ended up drawn outside the panel's own rounded border
 * with a chip clipped through the middle. A control that can be pulled out of
 * its own frame is not a control.
 *
 * So the body only fills whatever height the panel has been given, and keeps
 * the 92 px cap until somebody actually drags.
 */
.epanel-body
{
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
    align-content: flex-start;
    padding: 7px 8px;
    min-height: 32px;
    flex: 1 1 auto;
    max-height: var(--epanel-cap, 92px);
    overflow: auto;
}

/*
 * An EMPTY panel is a header and nothing else.
 *
 * It used to keep a 40 px empty box below the header whether anything was
 * chosen or not, and with all four panels empty - which is exactly how the
 * page opens - that reserved a band of blank colour across the whole screen,
 * pushing "choose groups above" a long way down. Reserving space for content
 * that is not there is only worth it when the content arrives and leaves
 * constantly; here it arrives once and stays.
 */
.epanel.is-empty .epanel-body
{
    display: none;
}

.epanel.is-empty .epanel-head
{
    border-bottom: 0;
    border-radius: var(--radius-m);
}

.chip-sm
{
    font-size: 11.5px;
    padding: 2px 6px 2px 8px;
}

/* A picker whose list has been narrowed by the cascade says so. A list that
 * silently shows a tenth of what you expected reads as a broken list. */
.picker-note
{
    margin: 0 0 6px;
    font-size: 11.5px;
    color: var(--ink-soft);
}

.picker-note a
{
    color: var(--brand-ink);
}

/* Any of these / all together, when more than one panel is in use. */
.matchpick
{
    display: inline-flex;
    gap: 2px;
    padding: 3px;
    border-radius: 999px;
    background: var(--surface-2);
    border: 1px solid var(--line);
    align-self: flex-start;
}

/* The student-number shortcut, only rendered when the school enables it. */
/*
 * THE STUDENT NUMBER AND THE CASCADE, ON ONE LINE.
 *
 * Two routes to the same answer - which groups - and a reader takes one of
 * them. Stacked, the cascade reads as the step after the number rather than
 * the alternative to it.
 *
 * NOT THERE AT ALL WITHOUT THE FIELD. `display: contents` removes the wrapper
 * from the layout, so `.cascade-row` goes on being a direct child of the
 * filter bar and every school that never switched the lookup on is untouched.
 * The template decides which of the two it is; see the comment there for why
 * that is not `:has()`.
 */
.idrow
{
    display: contents;
}

.idrow.has-id
{
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 14px;
    width: 100%;
}

/*
 * `.cascade-row` owns `width: 100%` - that is what makes it take a line of its
 * own, and it is right when it IS the line. Here it takes what is left over
 * instead, with a basis wide enough to hold the three selects: above about
 * 700px of filter bar the two sit side by side, below it the cascade wraps
 * underneath the number, which is the two-line arrangement narrow screens
 * already had.
 */
.idrow.has-id .cascade-row
{
    width: auto;
    flex: 1 1 420px;
    margin-bottom: 0;
}

.studentid
{
    display: flex;
    align-items: center;
    gap: 6px;
    flex: none;

    /* So the "we found nothing" line below can take a row of its own inside
     * this form rather than stretching it into the cascade beside it. */
    flex-wrap: wrap;
}

/*
 * THE ANSWER TO A NUMBER THAT FOUND NOTHING.
 *
 * `flex-basis: 100%` puts it on its own line INSIDE the form, so it stays
 * attached to the field it is about and cannot push the programme cascade
 * sideways. `max-width` because the form is `flex: none` - without a ceiling
 * one long sentence in Lithuanian would set the width of the whole control.
 *
 * THE ERRORBAR'S TREATMENT, at the size of a field note. It was amber and a
 * plain line first, on the argument that a mistyped digit is not a fault and
 * the page underneath still works. Mišo's call, and he is right about who is
 * reading it: a student on a phone who has just been shown the whole school's
 * timetable instead of their own needs to know that happened, and a quiet
 * sentence beside a form they have already stopped looking at is a sentence
 * nobody reads. Same colour language as every other thing on this site that
 * says "this did not do what you asked".
 */
.studentid-note
{
    flex-basis: 100%;
    max-width: 260px;
    margin: 4px 0 0;
    padding: 6px 10px;
    border-radius: var(--radius-m);
    background: color-mix(in srgb, var(--danger) 12%, var(--surface));
    border: 1px solid color-mix(in srgb, var(--danger) 40%, var(--line));
    color: var(--danger);
    font-size: 12px;
    font-weight: 600;
    line-height: 1.35;
}

.input-sid
{
    width: 110px;
    font: inherit;
    font-size: 13px;
    padding: 5px 8px;
    border: 1px solid var(--line);
    border-radius: var(--radius-m);
    background: var(--surface);
    color: var(--ink);
}

.field-label
{
    font-size: 12px;
    font-weight: 600;
    color: var(--ink-soft);
}

.btn-small
{
    padding: 4px 10px;
    font-size: 12px;
}

/* the logo block ---------------------------------------------------- */
/*
 * The 34x34 square that used to be reserved here is gone. It was written
 * before there was a logo feature, to hold the space a faculty's mark would
 * one day need - a good instinct, and the wrong shape: almost every faculty
 * logo is a wordmark, three or four times wider than it is tall, and a square
 * slot with `max-width: 100%` on the image inside squashed one to 34px.
 *
 * It also silently beat the real rule. Two blocks in one stylesheet both
 * called `.brand-logo`, three hundred lines apart, and the later one won -
 * which is why the first faculty logo rendered as a 34px smear rather than a
 * name. The live definition is up beside .brand, next to the code that emits
 * it.
 */

/* weekend columns --------------------------------------------------- */
/*
 * A school only has these columns at all when it switched show6Day/show7Day
 * on, so their presence already says "we teach then". They are tinted anyway,
 * because a Saturday lesson is the one a person looks twice at - the legacy
 * front end colours them and so does the mobile app.
 */

.day.is-weekend .day-head
{
    background: color-mix(in srgb, #d9a441 10%, var(--surface));
}

.day.is-weekend .day-name,
.day.is-weekend .day-date
{
    color: #8a6100;
}

.day.is-weekend .day-canvas
{
    background: color-mix(in srgb, #d9a441 4%, transparent);
}

/* A quiet button - present, pressable, not competing with the primary. */
.btn-quiet
{
    background: transparent;
    box-shadow: none;
    color: var(--ink-soft);
}

/* bookable slots --------------------------------------------------- */
/*
 * One per bell line, only for a signed-in member of staff. Invisible until
 * the pointer is over it - the timetable is a reading surface first, and a
 * grid permanently covered in plus signs would be a worse timetable for the
 * thousands of students who cannot book anything.
 *
 * z-index 1 keeps them UNDER the cards (z-index 2), so a slot that already
 * has a lesson on it is not clickable.
 */

.slotlink
{
    position: absolute;
    left: 2px;
    right: 2px;
    z-index: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: var(--radius-s);
    cursor: pointer;
    opacity: 0;
    transition: opacity .12s ease, background-color .12s ease;
    background: color-mix(in srgb, var(--brand) 12%, transparent);
    border: 1px dashed color-mix(in srgb, var(--brand) 45%, transparent);
    text-decoration: none;
}

.slotlink:hover,
.slotlink:focus-visible
{
    opacity: 1;
}

.slotlink-plus
{
    font-size: 15px;
    font-weight: 600;
    line-height: 1;
    color: var(--brand-ink);
}

@media print
{
    .slotlink
    {
        display: none;
    }
}

/* event cards ------------------------------------------------------ */

.card
{
    z-index: 2;
    position: absolute;
    padding: 0 3px 1px 0;
    container-type: size;
}

.card-body
{
    height: 100%;
    overflow: hidden;
    border-radius: 10px;
    background: var(--tint);
    border-left: 4px solid var(--spine);
    box-shadow: var(--shadow-1);
    padding: 5px 8px;
    color: #1c1c1c;
}

@media (prefers-color-scheme: dark)
{
    /*
     * Tints are computed light; in dark mode dim them instead of trying to
     * recompute in CSS - readable and cheap.
     *
     * A WASH AND NOT `filter: brightness(.92)`, AND THAT IS NOT A STYLE
     * PREFERENCE. Any filter other than `none` creates a STACKING CONTEXT,
     * and everything inside it is trapped there: the badge row's own
     * `z-index: 2` - which the author put there precisely so the chips would
     * sit above `.card-open` - was being resolved against its siblings inside
     * .card-body instead of against the overlay, whose 1 then won every time.
     *
     * The result was chips that looked right, carried the correct href and
     * target="_blank", and could not be clicked at all: every click landed on
     * the transparent overlay and opened the detail panel. Mišo, 3 Sep 2026:
     * "klik na te ikone ide na podrobnosti - ne otvara mi link na zoom u
     * novom tabu". IN DARK MODE ONLY, which is why it read as random.
     *
     * An 8% black layer over the tint is the same darkening by another route
     * - `background-image` over the base rule's `background-color` - and it
     * creates no context. It dims the tint and not the text, which is if
     * anything more correct: brightness() was also dimming #1c1c1c type that
     * had no need of it.
     */
    .card-body
    {
        background-image: linear-gradient(rgb(0 0 0 / .08), rgb(0 0 0 / .08));
    }
}

.card:hover
{
    z-index: 5;
}

.card:hover .card-body
{
    box-shadow: var(--shadow-2);
}

.card-title
{
    margin: 0;
    font-size: 12.5px;
    font-weight: 700;
    line-height: 1.25;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.card-sub, .card-note, .card-time
{
    margin: 1px 0 0;
    font-size: 11px;
    line-height: 1.3;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.card-sub
{
    color: rgb(0 0 0 / .65);
}

.card-note
{
    /* The schedule note has its OWN colour everywhere it appears - the
     * same rgb(179,103,71) rule the mobile client uses. */
    color: rgb(179 103 71);
}

.card-time
{
    color: rgb(0 0 0 / .5);
    font-variant-numeric: tabular-nums;
}

.card-badges
{
    display: flex;
    gap: 4px;
    float: right;
    margin: 0 0 2px 6px;

    /* The float must never be wider than the card it floats in. Without
     * this the row keeps its content width and hangs off the LEFT edge,
     * because that is the direction a right float overflows. */
    max-width: 100%;

    /*
     * Lifted out of the flow purely for the stacking order. The card is
     * covered edge to edge by `.card-open`, and the badges are links to
     * somewhere else entirely - the meeting, the material. Without a
     * position and a higher z-index the stretched link would swallow every
     * one of them and every click would open the detail instead.
     */
    position: relative;
    z-index: 2;
}

/*
 * THE STRETCHED LINK.
 *
 * Covers the card so that clicking anywhere on it opens the detail, without
 * wrapping the card in an <a> - wrapping would nest the badge links inside
 * another link, which is invalid and behaves differently in every browser.
 *
 * The inset repeats `.card`'s own padding so the hit area stops exactly
 * where the visible body stops; a link that overhangs into the 3 px gutter
 * would steal clicks meant for the card in the next lane.
 *
 * It carries no text: the accessible name comes from aria-label, so screen
 * readers announce "Details: Systems Analysis" rather than an empty link.
 */
/*
 * `:not(.rblock)` ON EVERY RULE BELOW THAT IS ABOUT THE OVERLAY.
 *
 * `card-open` is two different things wearing one class name. On the week
 * grid it is a TRANSPARENT LINK STRETCHED INSIDE a card - the card is the
 * coloured box, this is the click target laid over it. On the room overview
 * the block IS the link: `<a class="rblock card-open">`, one element, its own
 * background.
 *
 * The class has to be on both, because app.js opens the detail dialog from a
 * single delegated listener - `ev.target.closest("a.card-open")` - and that
 * is what makes the dialog, the JavaScript-off fallback and the `?open=` deep
 * link behave identically on every screen. But everything here that positions
 * or paints the OVERLAY is wrong when applied to a card that is not one, and
 * both of those went unnoticed until 3 Sep 2026:
 *
 *   the hover background REPLACED the block's tint rather than sitting on
 *   top of it. `.card-open:hover` is two classes and beats `.rblock`'s one,
 *   so `background: var(--tint)` lost, and 16% white over the dark board
 *   turned a cream block charcoal the moment the pointer touched it. Mišo
 *   reported it as "kursor preko previse potamni karticu" and it is exactly
 *   that - the tint was gone, not dimmed.
 *
 *   `@media print { display: none }` hid EVERY BLOCK ON THE ROOM BOARD. It
 *   is there so the week grid does not print a hairline box where its
 *   invisible link is; on the room overview it printed an empty grid with a
 *   room name column and nothing in it. Nobody reported that, which is the
 *   more worrying half.
 */
.card-open:not(.rblock)
{
    position: absolute;
    z-index: 1;
    inset: 0 3px 1px 0;
    border-radius: 10px;
}

/* The one thing that IS right for both: no underline, no link colour. */
.card-open
{
    text-decoration: none;
}

.card-open:not(.rblock):hover
{
    /* A whisper of a highlight: enough to say "this is clickable", not
     * enough to fight the tint that encodes the reservation type. */
    background: rgb(255 255 255 / .16);
}

.card-open:focus-visible
{
    outline: 2px solid var(--brand);
    outline-offset: -2px;
}

.badge
{
    display: inline-flex;
    align-items: center;
    padding: 1px 6px;
    border-radius: 999px;
    font-size: 10px;
    font-weight: 700;
    background: rgb(255 255 255 / .75);
    color: #333;
}

/*
 * AND THE ONES THAT ARE LINKS HAVE TO BE ABOVE THE OVERLAY.
 *
 * `.card-open` is a transparent <a> stretched over the whole card - `inset: 0
 * 3px 1px 0; z-index: 1` - and it is the LAST child of the article, after the
 * badges. So it painted over them, and every click on the camera, the page or
 * the door landed on the overlay and opened the detail panel instead of the
 * Zoom room.
 *
 * The chips looked right, had the correct href, carried target="_blank", and
 * could not be clicked. They were unreachable from the day they shipped, and
 * nothing about the page said so - which is the worst kind of broken: it
 * looks finished. Mišo: "klik na te ikone ide na podrobnosti - ne otvara mi
 * link na zoom u novom tabu".
 *
 * `a.badge` AND NOT `.card-badges`. The row is a block and spans the whole
 * width of the card; raising the row would take that entire strip away from
 * "open the details", which is the card's main gesture. Raising only the
 * anchors lifts exactly the pixels that have somewhere else to go.
 *
 * The non-link badges - the exclamation mark, the booking tick, "cancelled" -
 * are <span>s and stay under the overlay on purpose: they say something, they
 * do nothing, and a dead spot on a card is a worse bug than this one was.
 */
a.badge
{
    position: relative;
    z-index: 2;
}

/*
 * A NAME ON THE DETAIL PANEL THAT LEADS SOMEWHERE.
 *
 * The subject's title and each room's name are links to that subject's and
 * that room's own week. They are set in the surrounding text's colour and
 * weight - the panel is a list of facts, and a row of blue underlined words
 * would read as a form rather than as a record - and they say they are links
 * by underlining on hover and carrying a focus ring.
 *
 * The lecturer's name is deliberately NOT one of these. See the note in
 * templates/event.php.
 */
.evlink
{
    color: inherit;
    text-decoration: none;
    border-bottom: 1px dotted var(--line-strong);
}

.evlink:hover
{
    color: var(--brand);
    border-bottom-color: currentColor;
}

.evlink:focus-visible
{
    outline: 2px solid var(--brand);
    outline-offset: 2px;
    border-radius: 3px;
}

.badge-exam
{
    background: #e35d6a;
    color: #fff;
}

.badge-res
{
    background: rgb(255 255 255 / .8);
    color: var(--ok);
}

.badge-link
{
    background: var(--brand);
    color: #fff;
}

/*
 * The documentation link. Deliberately quieter than the meeting link: a
 * lecture that is happening online right now is time-critical, reading
 * material is not, and two equally loud badges on a 150 px card is a card
 * that is all badge.
 *
 * The two are told apart by SHAPE as much as by colour - a camera and a
 * page - which is what makes them readable to somebody who cannot separate
 * blue from white, and what makes them survive being printed in grey.
 */
.badge-doc
{
    background: rgb(255 255 255 / .85);
    color: var(--brand);
    border: 1px solid currentColor;
}

/*
 * THE TWO LINK BADGES ARE ICONS.
 *
 * They started as words - "Join online lecture", "Documentation" - and words
 * do not fit. A lane on a five-day week is about 230 px, but a lesson that
 * overlaps two others gets a third of that, and the two labels together are
 * wider than the whole card. Floated right, they then hang off the LEFT edge
 * over the neighbouring day and take the title with them, which is exactly
 * what they were doing.
 *
 * So: a fixed 20 px chip on the card, and the words where there is room for
 * them - in the tooltip, on the detail panel, and on paper.
 *
 * The label is still the link's TEXT, merely shrunk to nothing. That is not
 * a trick for its own sake: `font-size: 0` hides the word from the eye while
 * leaving the element's accessible name a real word, so a screen reader says
 * "Documentation, link" rather than announcing an empty link, and the print
 * rule at the bottom of this file simply gives the size back.
 *
 * The icon itself is a MASK, not a background image or a font glyph. A mask
 * takes its colour from `currentColor`, so one rule serves the blue meeting
 * chip and the outlined documentation chip and would serve a dark theme
 * without a second copy of the artwork. A text glyph would have been at the
 * mercy of whichever font the machine happens to have.
 */
.badge-link, .badge-doc, .badge-room
{
    min-width: 0;
    padding: 0;
    width: 20px;
    height: 16px;
    justify-content: center;
    font-size: 0;
    text-decoration: none;
    overflow: hidden;
}

/*
 * The ROOM's link, which is a different thing from the two above: those
 * belong to the subject, this one to the place. On the school that uses it
 * most the place IS the meeting - 102 of Alma Mater's 120 rooms hold a Zoom
 * address - but elsewhere it is a floor plan, so the icon is a DOOR: the one
 * reading that is true either way. Its tooltip names the room first, because
 * a booking can hold several and two identical doors would be a puzzle.
 */
.badge-room
{
    background: rgb(255 255 255 / .85);
    color: #4a5568;
    border: 1px solid currentColor;
}

.badge-link::before, .badge-doc::before, .badge-room::before
{
    content: "";
    width: 11px;
    height: 11px;
    background: currentColor;
    -webkit-mask-repeat: no-repeat;
            mask-repeat: no-repeat;
    -webkit-mask-position: center;
            mask-position: center;
    -webkit-mask-size: contain;
            mask-size: contain;
}

/* A video camera: the lecture you can join right now. */
.badge-link::before
{
    -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M2 4.5A1.5 1.5 0 0 1 3.5 3h6A1.5 1.5 0 0 1 11 4.5v7A1.5 1.5 0 0 1 9.5 13h-6A1.5 1.5 0 0 1 2 11.5v-7Zm10 2.2 2.5-1.6a.5.5 0 0 1 .8.4v4.9a.5.5 0 0 1-.8.4L12 9.3V6.7Z'/%3E%3C/svg%3E");
            mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M2 4.5A1.5 1.5 0 0 1 3.5 3h6A1.5 1.5 0 0 1 11 4.5v7A1.5 1.5 0 0 1 9.5 13h-6A1.5 1.5 0 0 1 2 11.5v-7Zm10 2.2 2.5-1.6a.5.5 0 0 1 .8.4v4.9a.5.5 0 0 1-.8.4L12 9.3V6.7Z'/%3E%3C/svg%3E");
}

/* A written page with a folded corner: the reading material. */
.badge-doc::before
{
    -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M4 1.5h5.2L13 5.3v9.2a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1v-12a1 1 0 0 1 1-1Zm5.4 1.3V5.6h2.8L9.4 2.8ZM5.2 8h5.6v1.1H5.2Zm0 2.6h5.6v1.1H5.2Z'/%3E%3C/svg%3E");
            mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M4 1.5h5.2L13 5.3v9.2a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1v-12a1 1 0 0 1 1-1Zm5.4 1.3V5.6h2.8L9.4 2.8ZM5.2 8h5.6v1.1H5.2Zm0 2.6h5.6v1.1H5.2Z'/%3E%3C/svg%3E");
}

/* A door, ajar. True whether the room's link is a Zoom address or a plan of
 * the building - both are "here is how you get in". */
.badge-room::before
{
    -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M2.6 1.6h7.1a1 1 0 0 1 1 1v10.8a1 1 0 0 1-1 1H2.6a1 1 0 0 1-1-1V2.6a1 1 0 0 1 1-1Zm5.1 5.6a.9.9 0 1 0 0 1.8.9.9 0 0 0 0-1.8ZM12 3.4h1.5a1 1 0 0 1 1 1v9.9h-1.2V4.6H12V3.4Z'/%3E%3C/svg%3E");
            mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M2.6 1.6h7.1a1 1 0 0 1 1 1v10.8a1 1 0 0 1-1 1H2.6a1 1 0 0 1-1-1V2.6a1 1 0 0 1 1-1Zm5.1 5.6a.9.9 0 1 0 0 1.8.9.9 0 0 0 0-1.8ZM12 3.4h1.5a1 1 0 0 1 1 1v9.9h-1.2V4.6H12V3.4Z'/%3E%3C/svg%3E");
}

/* A chip you can click deserves to say so under the pointer. */
.badge-link:hover, .badge-doc:hover, .badge-room:hover
{
    filter: brightness(1.08);
    box-shadow: 0 0 0 2px rgb(255 255 255 / .5);
}

.badge-cancelled
{
    background: #444;
    color: #fff;
}

.card.is-cancelled .card-body
{
    opacity: .55;
}

.card.is-cancelled .card-title
{
    text-decoration: line-through;
}

/* ------------------------------------------------------------------ */
/* room overview - rooms down the side, the clock across the top       */
/* ------------------------------------------------------------------ */

.roomboard
{
    /* The width of the frozen room column lives in ONE place: the rows lay
     * themselves out from it, the narrowing below changes it, and the print
     * sheet puts it back. A second number somewhere else is a second number
     * that can disagree. */
    --roomname-w: 150px;

    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-l);
    box-shadow: var(--shadow-2);
    overflow-x: auto;

    /*
     * AND IT SCROLLS VERTICALLY BY ITSELF, so the clock stays put.
     *
     * `.roomrow-head` has been `position: sticky; top: 0` since the board was
     * written, and on a school with forty rooms it never once stuck. A sticky
     * element travels inside its nearest SCROLLING ancestor, and `overflow-x:
     * auto` above makes this element that ancestor for both axes - but with no
     * height limit it never scrolls, so the page scrolls instead and the header
     * goes up with everything else.
     *
     * Maja Rigler at FKKT reported exactly the symptom: scroll down to see more
     * rooms "in s tem izgubiš zgornjo vrstico z urami". Reading a lane with the
     * clock off the screen is reading a bar chart with no axis.
     *
     * THE NUMBER BELOW IS A FALLBACK AND IT IS ALWAYS A LITTLE WRONG. 210 px
     * is a guess at the header plus the footer, and the header is not a fixed
     * height: measured on the live page in a narrow window it was 547 px,
     * because the rail and the day strip wrap. Guess low and the board runs
     * off the bottom of the screen; guess high - which is the usual case on a
     * maximised window - and there is a band of empty page under the board
     * where two or three more rooms would have fitted. Mišo saw the second
     * one: "tu dole par linija neiskorištenog prostora... kad dam maximize bi
     * mogao još par prostorija vidjeti".
     *
     * So the rule underneath this one stops guessing - see `body:has(...)`
     * below, which lets the board simply end where the window does, whatever
     * the header did. This stays as what a browser without `:has()` gets, and
     * it is the behaviour that shipped this morning rather than a regression.
     *
     * `dvh` and not `vh`: on a phone `vh` is the height with the address bar
     * hidden, so a board sized in it is taller than the visible window until
     * you scroll.
     */
    max-height: max(320px, calc(100dvh - 210px));
    overflow-y: auto;

    /* The header is opaque and one row tall, so anything scrolled to sits
     * under it rather than beside it. Keyboard focus and `?open=` both land
     * this way. */
    scroll-padding-top: 30px;
}

/*
 * AND THE VERSION THAT DOES NOT GUESS.
 *
 * The room overview is the one screen in this application whose main content
 * wants to be exactly as tall as the window allows - a board of forty rooms
 * with a frozen clock at the top. So on that screen, and only there, the page
 * becomes a column: header, board, footer, with the board taking whatever is
 * left over. No constant, and it re-measures itself for free when the header
 * wraps onto a second line on a narrow window or a phone.
 *
 * `body:has(.roomboard)` SCOPES IT WITHOUT A BODY CLASS. The alternative was
 * threading a class through _head.php and every template that renders it, to
 * say something the markup already says. Where `:has()` is not supported the
 * whole block is skipped and the fallback above applies, which is a board
 * that is slightly too short rather than a broken page.
 *
 * `min-height: 0` on the main is the load-bearing line and the one everybody
 * forgets: a flex item's default `min-height: auto` refuses to shrink below
 * its content, so the board would size itself to all forty rooms and overflow
 * the column instead of scrolling inside it.
 *
 * `height` AND NOT `min-height`, AND THAT IS THE WHOLE RULE.
 *
 * It shipped as `min-height: 100dvh` on 3 Sep 2026 and did nothing at all -
 * the board still ran to its full 2158 px, the page still scrolled, and the
 * clock still travelled off the top. `min-height` is a FLOOR: it says the
 * column may not be shorter than the window, and says nothing about longer.
 * So the column grew to its content, `flex: 1 1 auto` on the board had a
 * container with room to spare and never had to shrink anything, and
 * `overflow-y: auto` on a box that is already tall enough for its content
 * scrolls nothing - which is the same shape as the original bug it was
 * written to fix.
 *
 * `height: 100dvh` makes the column exactly the window. Now the board's
 * flex-basis (its content, all forty rooms) exceeds what is left after the
 * header and the footer, `flex-shrink: 1` takes it down to the remainder,
 * and the box that has `overflow-y: auto` finally has something to scroll -
 * so `.roomrow-head` sticks, because a sticky element travels in the nearest
 * ancestor that ACTUALLY scrolls, not in the nearest one that is allowed to.
 *
 * Deliberately NOT `overflow: hidden` on the body. On a window too short for
 * the header, the footer and the board's 240 px floor, the column overflows
 * and the page scrolls - the clock stops sticking, which is bad, but every
 * room is still reachable, which matters more than the clock.
 *
 * The footer ends up on screen, directly under the board, and that is
 * correct rather than a cost: the band of empty page it used to sit below -
 * "tu dole par linija neiskorištenog prostora... kad dam maximize bi mogao
 * još par prostorija vidjeti" - was the same guessed 210 px, and it is now
 * rooms.
 */
@supports selector(:has(*))
{
    body:has(.roomboard)
    {
        height: 100dvh;
        display: flex;
        flex-direction: column;
    }

    /* The header and the footer are their own size and are never squeezed to
     * make the board fit. Without this a flex item's default shrink factor
     * lets the browser take pixels out of the day strip and the buttons
     * instead of out of the one box that was built to scroll. */
    body:has(.roomboard) > header,
    body:has(.roomboard) > footer
    {
        flex: none;
    }

    body:has(.roomboard) main.board-wrap
    {
        flex: 1 1 auto;
        min-height: 0;
        display: flex;
        flex-direction: column;
    }

    /*
     * AND THE FOOTER GIVES ITS 122 PX BACK TO THE ROOMS.
     *
     * Measured on a 1990x1100 window: 24 px of padding under the board plus a
     * 98 px footer, of which 66 px is the two store badges stacked under the
     * school code. That is five rooms, on the one screen where a room is what
     * somebody came for - "kad dam maximize bi mogao još par prostorija
     * vidjeti".
     *
     * So here, and nowhere else, the footer is one line: our mark, the code
     * to type into the phone app, and the two stores as plain word links.
     * Nothing is removed - the code is still there, both links still work,
     * and every other screen keeps the official artwork at full size. The
     * artwork is not shrunk or redrawn, which is the thing the guidelines
     * actually forbid; it is simply not the element used here.
     */
    body:has(.roomboard) main.board-wrap
    {
        padding-bottom: 8px;
    }

    body:has(.roomboard) .foot
    {
        align-items: center;
        padding: 6px 18px 8px;
    }

    body:has(.roomboard) .foot-apps
    {
        flex-direction: row;
        align-items: center;
        gap: 14px;
    }

    body:has(.roomboard) .storebadge-img
    {
        display: none;
    }

    body:has(.roomboard) .storebadge-word
    {
        display: inline;
    }

    body:has(.roomboard) .roomboard
    {
        flex: 1 1 auto;
        /* A floor, so a short window or a very tall header leaves a board
         * somebody can still read rather than a two-room slot. Past that the
         * page scrolls, which is the honest answer to "there is no room". */
        max-height: none;
        min-height: 240px;
    }
}

/* A board that has to fit a sheet of paper cannot be a scroll box: everything
 * below the fold would simply not print - and neither the flex column above
 * nor the fallback cap has any business on paper. */
@media print
{
    .roomboard,
    body:has(.roomboard) .roomboard
    {
        max-height: none;
        min-height: 0;
        overflow: visible;
        flex: none;
    }

    /* `height: 100dvh` is a window measurement and paper is not a window: it
     * would cut the sheet off at one screenful and print nothing below it. */
    body:has(.roomboard)
    {
        display: block;
        height: auto;
    }
}

/*
 * THE ROW MUST BE AS WIDE AS THE DAY, OR THE FROZEN COLUMN IS NOT FROZEN.
 *
 * `position: sticky` can only travel inside its own containing block, and
 * that block is this row. A block-level row is as wide as the board's
 * VISIBLE area - not as wide as its own overflowing contents - so the room
 * name stays put only until the scroll passes (visible width - column
 * width), and is then dragged off the left edge for the rest of the day.
 *
 * That is why it read as "sometimes": the break-even is a window width.
 * Measured in Chromium against this exact markup, 07:00-21:30 at 1.3 px per
 * minute - the names let go at ~11:08 on a 360 px window, ~12:40 on 480 px,
 * ~15:29 on 700 px, and never above ~760 px. Anyone testing on a maximised
 * monitor cannot see it at all.
 *
 * `max-content` gives the row the width of the whole day, so the sticky
 * column has the whole scroll to travel through. `min-width: 100%` keeps the
 * rows spanning the board when the day is NARROWER than the screen, so the
 * row rules still reach the right-hand edge instead of stopping short.
 */
.roomrow
{
    display: flex;
    width: max-content;
    min-width: 100%;

    /*
     * A THINNER LINE, not a thinner border.
     *
     * A hairline cannot be asked for in pixels - 1px is the floor, and 0.5px
     * rounds to 1 on most displays and to 0 on some, which is a rule that
     * draws a line on one machine and nothing on another. What actually makes
     * a rule read as thin is CONTRAST, so the border keeps its pixel and
     * loses most of its ink.
     *
     * It matters more at 25 px than it did at 44: forty rooms means forty
     * rules, and at full strength they stop separating the rows and start
     * being a pattern of their own - which is what "još kompaktnije, tanje
     * linije" was asking about on 3 Sep 2026. The alternating row tint below
     * does most of the separating anyway.
     */
    border-top: 1px solid color-mix(in srgb, var(--line) 45%, transparent);
}

.roomrow:first-child
{
    border-top: 0;
}

.roomrow:nth-child(even) .roomlane
{
    background: color-mix(in srgb, var(--surface-2) 45%, transparent);
}

.roomrow:hover .roomlane
{
    background: color-mix(in srgb, var(--brand) 5%, transparent);
}

.roomrow-head
{
    position: sticky;
    top: 0;
    z-index: 12;
    background: var(--surface);
    border-bottom: 1px solid var(--line);
}

.roomname
{
    flex: 0 0 var(--roomname-w);
    position: sticky;
    left: 0;
    z-index: 10;
    display: flex;
    align-items: center;
    padding: 0 14px;
    /* 44, then 30, now 25 - see the geometry note in templates/rooms.php for
     * why the row shrank twice in one day. It has to match `.roomlane`
     * exactly or the name and its lane disagree about where the row ends. */
    min-height: 25px;
    font-size: 12px;
    background: var(--surface);
    border-right: 1px solid var(--line);
    font-weight: 700;
    color: var(--ink);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

a.roomname:hover
{
    color: var(--brand-ink);
}

/*
 * ONCE THE BOARD IS SCROLLED, THE COLUMN IS A REMINDER - NOT A DIRECTORY.
 *
 * At rest the room column is 150 px because that is a screen you are
 * READING: full names, nothing clipped. The moment you scroll into the
 * afternoon it stops being a list you read and becomes the label that tells
 * you which lane you are on - and the first five or six characters do that
 * as well as the whole name does ("P-001", "A-004", "FZV-1"). The 78 px it
 * gives back is an hour of the day, on the screen, where it is wanted.
 *
 * The full name is still one hover away: the template puts it in `title`
 * precisely because this column now clips.
 *
 * NO TRANSITION, deliberately. app.js compensates the scroll position by
 * exactly the width that was taken away, so the minute under the pointer
 * does not move. Animating the width would slide the whole day under a
 * stationary compensation for a tenth of a second - the board would smear,
 * which is worse than the honest snap.
 */
.roomboard.is-narrow
{
    --roomname-w: 72px;
}

.roomboard.is-narrow .roomname
{
    padding: 0 8px;
}

.roomlane
{
    position: relative;
    /* pad 3 + pitch 22. Kept in step with $lanePad/$lanePitch in
     * templates/rooms.php, which writes each block's top and height from the
     * same two numbers. */
    min-height: 25px;
    flex: 0 0 auto;
}

/*
 * THE COMPACT BOARD - the same board, drawn as tight as it goes.
 *
 * A box on the settings screen beside "hide empty rooms", remembered in the
 * reader's own browser. 22 stays the default because it is the density most
 * people read comfortably; this is for somebody who wants the whole building
 * at once and knows their rooms by name. "jos i compact rooms preview i skroz
 * smanjimo visinu linija... sirina i sve ostalo je ok", 3 Sep 2026 - so
 * HEIGHT ONLY. The name column, the hour ruler and every horizontal
 * measurement are untouched.
 *
 * pad 3 + pitch 16 = 19 px per single-lane row, against 25. FKKT's forty
 * rooms: about thirty-five on a laptop instead of twenty-six.
 *
 * THESE NUMBERS ARE THE OTHER HALF OF $lanePitch/$laneHeight IN
 * templates/rooms.php, which writes each block's `top` and `height` inline.
 * One decision in two files: change either and the blocks leave their strips.
 *
 * 13 px of block still holds the 10 px line with a pixel above and below it.
 * That is the floor - past it the descenders on g, j and p go, which is not
 * compact, it is broken.
 */
.roomboard.is-compact .roomlane,
.roomboard.is-compact .roomname
{
    min-height: 19px;
}

.roomboard.is-compact .rblock
{
    height: 13px;
    padding: 0 4px;
    border-radius: 4px;
    border-left-width: 2px;
    font-size: 10px;
}

.roomboard.is-compact .roomname
{
    padding: 0 10px;
    font-size: 11px;
}

.roomrow-head .roomlane
{
    min-height: 22px;
}

.rtick
{
    position: absolute;
    top: 4px;
    transform: translateX(-50%);
    font-size: 11px;
    font-weight: 700;
    color: var(--ink-soft);
    font-variant-numeric: tabular-nums;
}

.rtick.is-first
{
    transform: none;
    padding-left: 4px;
}

.rrule
{
    position: absolute;
    top: 0;
    bottom: 0;
    /* 80% until 3 Sep 2026. The hour rules run the full height of a board
     * that is now half as tall per room, so there are as many of them in far
     * less space; at 80% they read as a grid drawn over the events rather
     * than as a ruler behind them. */
    border-left: 1px dashed color-mix(in srgb, var(--line) 45%, transparent);
    pointer-events: none;
}

/* ------------------------------------------------------------------ */
/* the week, as a row of days, on the room overview                    */
/* ------------------------------------------------------------------ */
/*
 * See templates/_daystrip.php. The row is short because each button is a
 * three-letter name over a number, and it sits in the same nav as the arrows
 * and the date field - the third and most-used way of asking the same
 * question on a screen that shows one day at a time.
 */
.daystrip
{
    display: flex;
    gap: 2px;
    margin-left: 6px;
}

.daystrip-day
{
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    min-width: 34px;
    padding: 2px 5px;
    border: 1px solid transparent;
    border-radius: var(--radius-s);
    color: var(--ink-soft);
    text-decoration: none;
    line-height: 1.15;
}

.daystrip-day:hover
{
    background: var(--surface-2);
    color: var(--ink);
}

/*
 * THE DAYS BELONG BESIDE THE DATE, NOT UNDER IT.
 *
 * `.weeknav` wraps, and the day strip is the last thing in it, so the strip
 * is what drops when the row runs out of width. On Alma Mater - a wide school
 * logo in the corner, a signed-in registrar with two more buttons on the
 * right, and a laptop at 125% scaling, so about 1600 CSS px - it dropped, and
 * the header became two rows: arrows and date on one, the days orphaned
 * underneath. Mišo asked for them back on the same line: "tu bi dani mogli i
 * desno od datuma".
 *
 * WHAT ACTUALLY CAUSED IT was not the strip's width. `.topbar-actions` is
 * `flex: 1 1 auto` and `.weeknav` `flex: 1 1 460px`, so the two grow against
 * each other and the actions - whose content is right-aligned and mostly
 * empty space - took 809 px of a 1600 px row while the nav was squeezed to
 * 566 and had to wrap inside itself. The nav was starved by a box that did
 * not need the room.
 *
 * So above 880 px:
 *   .weeknav        stops growing and stops wrapping. It takes exactly what
 *                   its seven children need and the days stay put.
 *   .topbar-actions takes the remainder and right-aligns inside it, which is
 *                   what it was doing visually anyway.
 *
 * The strip itself is tightened a little as well - 34 px per day to 30 - so
 * there is slack before anything has to give at all. Only the box: the type
 * in it is already 10 px and 12 px and there is nothing left to take.
 *
 * WHAT GIVES WHEN THERE IS GENUINELY NOT ENOUGH ROOM: the topbar's own wrap,
 * which puts the button rail on a second row and keeps the days with the
 * date. That is the right order - somebody on the room overview is moving
 * between days far more often than they are opening the menu.
 *
 * Below 880 px none of this applies. A phone wants the old behaviour: one
 * thing per line, wrapping freely.
 */
@media (min-width: 880px)
{
    .topbar
    {
        gap: 12px;
        padding-left: 14px;
        padding-right: 14px;
    }

    .weeknav
    {
        flex: 0 0 auto;
        flex-wrap: nowrap;
        column-gap: 6px;
    }

    .topbar-actions
    {
        flex: 1 1 auto;
        min-width: 0;
        justify-content: flex-end;
    }

    .daystrip
    {
        flex: 0 0 auto;
    }

    /* The type is already 10 px and 12 px; only the box around it gives. */
    .daystrip-day
    {
        min-width: 30px;
        padding: 2px 3px;
    }
}

.daystrip-name
{
    font-size: 10px;
    font-weight: 700;
    letter-spacing: .04em;
}

.daystrip-num
{
    font-size: 12px;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}

/* TODAY IS MARKED EVEN WHEN IT IS NOT THE DAY ON SCREEN - a dot, not a fill,
 * so it cannot be mistaken for the selection. Somebody who has paged into
 * next month needs to see where they came from. */
.daystrip-day.is-today .daystrip-num::after
{
    content: "";
    display: block;
    width: 3px;
    height: 3px;
    margin: 1px auto 0;
    border-radius: 50%;
    background: currentColor;
}

/* And the day actually being drawn. Filled, because this one is a statement
 * about the board below it rather than about the calendar. */
.daystrip-day.is-now
{
    background: var(--brand);
    border-color: var(--brand);
    color: #fff;
}

.daystrip-day.is-now:hover
{
    color: #fff;
}

/* Paper has no navigation. */
@media print
{
    .daystrip
    {
        display: none;
    }
}

/* bookable slots on a room's lane ----------------------------------- */
/*
 * The week grid's slot link, turned on its side. Same three rules: invisible
 * until hovered, only for signed-in staff, and UNDER the blocks so a hall
 * that is already taken is not clickable.
 *
 * This is the screen where booking-by-pointing matters most. People open the
 * room overview to find a free hall; once they have found it, making them
 * remember the name and go and type it in somewhere else was asking them to
 * carry the answer by hand.
 */
.rslot
{
    position: absolute;
    top: 6px;
    bottom: 6px;
    z-index: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 8px;
    cursor: pointer;
    opacity: 0;
    transition: opacity .12s ease;
    background: color-mix(in srgb, var(--brand) 12%, transparent);
    border: 1px dashed color-mix(in srgb, var(--brand) 45%, transparent);
    text-decoration: none;
}

.rslot:hover,
.rslot:focus-visible
{
    opacity: 1;
}

.rslot-plus
{
    font-size: 13px;
    font-weight: 600;
    line-height: 1;
    color: var(--brand-ink);
}

@media print
{
    .rslot
    {
        display: none;
    }
}

.rblock
{
    /* Above .rslot, so an occupied stretch of the lane cannot be clicked
     * through to the booking form. */
    z-index: 2;
    position: absolute;

    /* TOP AND HEIGHT COME FROM THE TEMPLATE, not from here.
     *
     * This used to be `top: 6px; bottom: 6px`, which stretched every block
     * over the whole row - fine while a row could only hold one block, and
     * exactly what made two events sharing a room land on identical pixels
     * with the later one hiding the earlier one completely. rooms.php now
     * gives each block a lane, and writes `top` and `height` inline from it.
     *
     * `bottom: auto` is the load-bearing line: with a `bottom` still set here
     * it would win over the inline height and every block would stretch again,
     * which is the same bug wearing a different hat. The fallbacks keep a
     * block sane if it is ever rendered without the inline pair - 3/18 is the
     * single-lane geometry, so nothing moves. */
    top: 3px;
    height: 18px;
    bottom: auto;

    display: flex;
    align-items: center;
    padding: 0 5px;
    border-radius: 5px;
    background: var(--tint);
    border-left: 3px solid var(--spine);
    box-shadow: var(--shadow-1);
    font-size: 11px;
    font-weight: 600;
    color: #1c1c1c;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    /* It is an <a> now - see rooms.php. The colour above already wins over
     * the link colour; only the underline has to go. .is-cancelled comes
     * after this rule and keeps its line-through. */
    text-decoration: none;
}

.rblock:hover,
.rblock:focus-visible
{
    z-index: 5;

    /*
     * A LIFT AND A WHISPER OF LIGHT, and the light is painted INSIDE rather
     * than set as a background.
     *
     * `background:` on hover would replace `var(--tint)`, and the tint is not
     * decoration - it is what says which kind of event this is. That is
     * precisely how the overlay rule broke this: a block went charcoal on
     * hover because its tint had been overwritten, not shaded.
     *
     * A large inset shadow fills the box over whatever background is already
     * there, so the colour is still the colour and it is simply a little
     * brighter under the pointer. It also stacks with the drop shadow in the
     * same declaration, which a filter could not do - `filter` does not
     * accumulate across rules, so a brightness here would have cancelled the
     * dark-scheme correction at the bottom of this file.
     */
    box-shadow: var(--shadow-2), inset 0 0 0 999px rgb(255 255 255 / .14);
}

/* The block was a <span> and could not be reached from the keyboard at all.
 * Now that it is a link it can be, so it has to SHOW that it has been - and
 * inside a lane of 44 px an outline drawn outside the box would be clipped. */
.rblock:focus-visible
{
    outline: 2px solid var(--brand-ink);
    outline-offset: -2px;
}

.rblock.is-cancelled
{
    opacity: .5;
    text-decoration: line-through;
}

@media (prefers-color-scheme: dark)
{
    .rblock
    {
        filter: brightness(.92);
    }
}

/* ------------------------------------------------------------------ */
/* footer, landing                                                     */
/* ------------------------------------------------------------------ */

.foot
{
    display: flex;
    justify-content: space-between;
    padding: 0 24px 20px;
    color: var(--ink-soft);
    font-size: 12px;
}

.landing
{
    max-width: 520px;
    margin: 18vh auto;
    padding: 32px;
    text-align: center;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-l);
    box-shadow: var(--shadow-2);
}

/* ------------------------------------------------------------------ */
/* small screens                                                       */
/* ------------------------------------------------------------------ */

@media (max-width: 900px)
{
    .topbar
    {
        padding: 8px 12px;
        gap: 8px;
    }

    .weeklabel
    {
        display: none;      /* the day headers already carry the dates */
    }

    .board-wrap
    {
        padding: 4px 8px 16px;
    }

    /*
     * BETWEEN A LAPTOP AND A PHONE, THE WEEK STAYS A WEEK.
     *
     * The floor drops from 168 to 116 so five days keep fitting as the window
     * narrows - 5 x 116 + 64 for the ruler is 644, which still shows a whole
     * week on a small laptop or a tablet held landscape. Tighter than
     * comfortable, but a tight week beats a wide Monday: the question people
     * open this page with is "when am I free on Thursday", and that is not a
     * question you can answer by swiping.
     */
    .day
    {
        min-width: 116px;
    }
}

/*
 * ONE DAY AT A TIME, AND ONLY ON SOMETHING PHONE-SIZED.
 *
 * This used to fire at 900px, which is not a phone - it is a laptop window
 * that has been made a bit narrower, and, more to the point, it is any
 * ordinary desktop once Windows display scaling is turned up: at 175% a
 * 1500-pixel window reports about 860 CSS pixels and lands here. The result
 * was a Monday 78% of the screen wide and the rest of the week off the edge,
 * on a machine with room for all five days.
 *
 * The swipe layout is right for a phone and wrong for everything else, so it
 * now waits for one: 560px is the same breakpoint the rest of this file uses
 * to decide something is a handset.
 */
@media (max-width: 560px)
{
    .day
    {
        min-width: 78vw;    /* one day fills the view; swipe for the next */
    }
}

/* ------------------------------------------------------------------ */
/* reports                                                             */
/* ------------------------------------------------------------------ */

/*
 * A report is a document, not a screen: one column, generous line height,
 * ruled rows, and a heading that names the institution and the range. Most of
 * the design work here is for the PRINTED version - a report only really
 * exists once somebody has put it in front of a dean.
 */

.reportbar
{
    flex-direction: column;
    align-items: stretch;
    gap: 10px;
}

.reportform
{
    margin: 0;
}

/*
 * ONE BASELINE FOR THE WHOLE CONTROL ROW.
 *
 * It was `align-items: flex-end` on the row with `align-self: center` on the
 * labels - two different alignments in one line, so the uppercase labels
 * floated a couple of pixels above the fields they belonged to and the Show
 * button, being taller, sat lower than both. Everything centres on one axis
 * now, and each label is glued to its own control rather than being a free
 * item in the row.
 */
.reportrow
{
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 10px;
    row-gap: 8px;
}

.reportrow .field
{
    display: inline-flex;
    align-items: center;
    gap: 8px;
    margin: 0;
}

.reportrow .field-label
{
    font-size: 11.5px;
    font-weight: 650;
    color: var(--ink-soft);
    text-transform: uppercase;
    letter-spacing: .03em;
    white-space: nowrap;
}

/* The two rows line their labels up with each other, so REPORT and QUICK
 * RANGE start at the same x - the thing the eye actually checks. */
.reportrow > .field-label:first-child
{
    flex: 0 0 auto;
    min-width: 86px;
}

.reportmain
{
    padding: 0 20px 40px;
}

.reporthead
{
    margin: 18px 0 10px;
}

.reporttitle
{
    margin: 0;
    font-size: 20px;
    font-weight: 750;
    letter-spacing: -.01em;
}

.reportsub
{
    margin: 2px 0 0;
    font-size: 13px;
    color: var(--ink-soft);
    font-variant-numeric: tabular-nums;
}

.reportactions
{
    display: flex;
    align-items: center;
    gap: 10px;
    margin: 0 0 12px;
}

.reportcount
{
    margin-left: auto;
    font-size: 12.5px;
    font-weight: 650;
    color: var(--ink-soft);
    font-variant-numeric: tabular-nums;
}

.reporttable
{
    width: 100%;
    border-collapse: collapse;
    font-size: 13px;
    background: var(--surface);
    border-radius: var(--radius-l);
    overflow: hidden;
    box-shadow: var(--shadow-1);
}

.reporttable thead th
{
    position: sticky;
    top: 0;
    z-index: 2;
    text-align: left;
    padding: 9px 12px;
    background: var(--brand);
    color: #fff;
    font-size: 11.5px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .03em;
    white-space: nowrap;
}

.reporttable td
{
    padding: 7px 12px;
    border-top: 1px solid var(--line);
    vertical-align: top;
}

.reporttable tbody tr:hover td
{
    background: color-mix(in srgb, var(--brand) 4%, transparent);
}

/* Dates and times: tabular figures so a column of them lines up, which is
 * the whole reason anybody scans a report column. */
.reporttable .num
{
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.reportnote
{
    color: var(--ink-soft);
    max-width: 320px;
}

/* A cancelled lesson still happened in the plan and still has to appear -
 * marked, not dropped. Dropping it would make the report disagree with the
 * timetable people were shown. */
.reporttable tr.is-cancelled td
{
    text-decoration: line-through;
    color: var(--ink-soft);
}

.reportsection th
{
    text-align: left;
    padding: 9px 12px;
    background: color-mix(in srgb, var(--brand) 9%, var(--surface));
    border-top: 1px solid var(--line);
    font-size: 13px;
    font-weight: 750;
}

.reportsection-sum
{
    float: right;
    font-weight: 600;
    color: var(--ink-soft);
    font-variant-numeric: tabular-nums;
}

.reporttotal
{
    margin: 12px 0 0;
    text-align: right;
    font-size: 13px;
    color: var(--ink-soft);
    font-variant-numeric: tabular-nums;
}

/* the two signed evidence sheets -------------------------------------- */

/*
 * These are DOCUMENTS, not tables: an institution's heading, a named
 * lecturer, totals, a date and a ruled line to sign on. Everything here is
 * really designed for the printed page - a signed sheet only exists once it
 * is on paper - and the screen is the proof-read before it gets there.
 */
.evsheet
{
    margin: 0 0 44px;
}

.evmeta
{
    display: grid;
    grid-template-columns: max-content 1fr;
    gap: 2px 14px;
    margin: 0 0 12px;
    font-size: 13px;
}

.evmeta dt
{
    font-size: 11px;
    font-weight: 700;
    color: var(--ink-soft);
    text-transform: uppercase;
    letter-spacing: .03em;
    align-self: center;
}

.evmeta dd
{
    margin: 0;
}

.evtable .num
{
    text-align: center;
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}

.evtable thead th.num
{
    /* One letter per column, and the legend under the table says what it
     * means. Full type names across a dozen columns would not fit a page,
     * which is exactly why the legacy codes them. */
    min-width: 34px;
    text-align: center;
}

/* The date reads as a date, not as a figure - it is tabular so a column of
 * them lines up, but left-aligned like the text it is. Centring it made the
 * first column look like a measurement. */
.evtable .evwhen
{
    text-align: left;
}

/* The description column is empty on purpose - it is written in by hand,
 * per session. Given real width so there is somewhere to write. */
.evtable .evdesc
{
    min-width: 200px;
}

.evsub th,
.evsub td
{
    background: color-mix(in srgb, var(--brand) 7%, var(--surface));
    font-weight: 650;
}

.evlegend
{
    margin: 8px 0 0;
    font-size: 11.5px;
    color: var(--ink-soft);
}

.evdate
{
    margin: 18px 0 0;
    font-size: 12.5px;
}

/*
 * The signature block. A ruled line with its caption UNDER it, which is how
 * every form in a faculty office is laid out - you sign on the rule, and the
 * small print below says whose signature it was.
 */
.evsign
{
    display: flex;
    gap: 60px;
    margin: 34px 0 0;
}

.evsign-line
{
    min-width: 220px;
    font-size: 11.5px;
    color: var(--ink-soft);
}

.evsign-rule
{
    padding-top: 6px;
    border-top: 1px solid var(--ink);
    text-align: center;
}

/* evidence by month --------------------------------------------------- */

.montable
{
    font-size: 11px;
    width: auto;
    min-width: 100%;
    table-layout: fixed;
}

.montable th,
.montable td
{
    padding: 4px 5px;
}

.montable .moncourse
{
    width: 190px;
    text-align: left;
    font-weight: 650;
    vertical-align: top;
    background: var(--surface);
}

.montable .moncode,
.montable .monlabel
{
    width: 74px;
    white-space: nowrap;
}

.montable .monday
{
    width: 26px;
    text-align: center;
    font-variant-numeric: tabular-nums;
}

/*
 * Days past the end of the month. Greyed rather than removed: the grid stays
 * 31 columns wide every month, so February's sheet can be filed next to
 * January's and read the same way - which is the legacy's rule and the right
 * one for a form that gets archived.
 */
.montable .is-void
{
    background: color-mix(in srgb, var(--ink) 6%, transparent);
}

.montable td.is-on
{
    background: color-mix(in srgb, var(--brand) 16%, transparent);
    font-weight: 650;
}

/* The hand-filled rows: consultation, colloquium, exams. Empty boxes, on
 * purpose - the timetable does not know about any of them. */
.monother td
{
    background: color-mix(in srgb, var(--ok) 5%, transparent);
}

.montotals
{
    margin: 12px 0 0;
    border-collapse: collapse;
    font-size: 12.5px;
}

.montotals th
{
    text-align: left;
    font-weight: 500;
    padding: 3px 16px 3px 0;
}

.montotals td
{
    padding: 3px 0;
    font-variant-numeric: tabular-nums;
    font-weight: 650;
    text-align: right;
    min-width: 90px;
}

.montotals-grand th,
.montotals-grand td
{
    border-top: 1px solid var(--line);
    font-weight: 800;
}

/* who teaches what ---------------------------------------------------- */

.staffprog
{
    margin: 20px 0 4px;
    font-size: 17px;
    font-weight: 750;
}

.staffyear
{
    margin: 22px 0 6px;
    font-size: 14px;
    font-weight: 700;
    color: var(--brand-ink);
}

.staffbranch
{
    margin: 0 0 18px;
    break-inside: avoid;
}

.staffbranch-name
{
    display: flex;
    align-items: baseline;
    gap: 10px;
    margin: 0 0 6px;
    font-size: 13px;
    font-weight: 650;
}

/*
 * The count of unstaffed subjects, in the branch heading.
 *
 * This is the number the report exists to surface, so it is loud: a
 * programme leader should see there is a hole before they start reading
 * rows, not after. Red, because it is a problem and not a statistic.
 */
.staffgap-count
{
    font-size: 11px;
    font-weight: 700;
    padding: 2px 8px;
    border-radius: 999px;
    color: var(--danger);
    background: color-mix(in srgb, var(--danger) 12%, var(--surface));
    border: 1px solid color-mix(in srgb, var(--danger) 35%, var(--line));
}

.stafftable
{
    font-size: 12.5px;
}

.stafftable th[scope="rowgroup"],
.stafftable th[scope="row"]
{
    text-align: left;
    font-weight: 650;
    vertical-align: top;
    width: 44%;
}

.stafftable .staffcode
{
    width: 70px;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/* A subject nobody is down to teach - Branko's "Predmet 2 | --- |". Marked,
 * because finding these is the whole job of the report. */
.stafftable .staffgap th,
.stafftable .staffgap td
{
    background: color-mix(in srgb, var(--danger) 7%, transparent);
    color: var(--danger);
}

/* A rule between subjects, so a subject with four lecturers still reads as
 * one block rather than four rows. */
.stafftable tr.stafffirst > *
{
    border-top: 1px solid var(--line);
}

/* the sheet heading editor -------------------------------------------- */

.headedit
{
    width: 100%;
    border: 1px solid var(--line);
    border-radius: var(--radius-m);
    background: var(--surface);
    padding: 8px 12px;
}

.headedit > summary
{
    cursor: pointer;
    font-size: 11.5px;
    font-weight: 650;
    color: var(--ink-soft);
    text-transform: uppercase;
    letter-spacing: .03em;
}

.headedit-row
{
    margin-top: 8px;
}

.headedit-wide
{
    flex: 1 1 220px;
    min-width: 0;
}

/* room utilisation ---------------------------------------------------- */

/*
 * The utilisation matrix. Narrow numeric columns, the room name held on the
 * left, and heat shading - which is the whole point. A wall of numbers hides
 * the pattern that a wall of colour shows at a glance: the hall nobody books,
 * the Friday everybody avoids, the two rooms carrying the whole department.
 *
 * FIVE STEPS, not a continuous gradient. Five is enough to read a pattern off
 * and few enough to survive a black-and-white printer, where they land as five
 * distinguishable greys.
 */
.roomtable
{
    font-size: 12px;
    width: auto;
    min-width: 100%;
}

.roomtable th,
.roomtable td
{
    padding: 5px 8px;
}

.roomtable thead th
{
    text-align: center;
    line-height: 1.25;
}

.roomtable thead th:first-child
{
    text-align: left;
}

.roomtable .colsub
{
    font-weight: 500;
    opacity: .75;
    text-transform: none;
    letter-spacing: 0;
}

.roomtable .roomcell
{
    position: sticky;
    left: 0;
    z-index: 1;
    background: var(--surface);
    font-weight: 650;
    white-space: nowrap;
}

.roomtable td.num
{
    text-align: center;
    min-width: 44px;
}

.roomtable .strong
{
    font-weight: 700;
    white-space: nowrap;
}

.roomtable tfoot th
{
    padding: 7px 8px;
    border-top: 2px solid var(--line);
    background: var(--surface-2);
    font-weight: 700;
    text-align: center;
    font-variant-numeric: tabular-nums;
}

.roomtable tfoot th:first-child
{
    text-align: left;
}

/*
 * A weekend column stays visibly a weekend - a Saturday at 80 % is a
 * different fact from a Tuesday at 80 % - but ONLY WHERE IT IS EMPTY.
 *
 * The first attempt tinted every weekend cell, and because `.roomtable
 * .is-weekend` outranks a bare `.heat-3`, the tint won: a busy Saturday came
 * out pale pink like an empty one, and the shading - the whole point of the
 * table - silently stopped working in two columns out of seven. The heat is
 * the DATA; the weekend is context, and context must never overpaint data.
 *
 * So: empty weekend cells get the tint, used ones keep their heat, and the
 * column header says which day it is in words anyway.
 */
.roomtable td.is-weekend.heat-0
{
    background: color-mix(in srgb, var(--danger) 5%, transparent);
}

/* In the header the tint over the solid brand fill just washed the day name
 * out to an unreadable grey. A tinted LABEL says the same thing and stays
 * legible. */
.roomtable thead th.is-weekend
{
    color: color-mix(in srgb, #fff 72%, var(--danger));
}

.heat-1 { background: color-mix(in srgb, var(--brand)  8%, transparent); }
.heat-2 { background: color-mix(in srgb, var(--brand) 18%, transparent); }
.heat-3 { background: color-mix(in srgb, var(--brand) 30%, transparent); }
.heat-4 { background: color-mix(in srgb, var(--brand) 44%, transparent); }
.heat-5
{
    background: color-mix(in srgb, var(--brand) 62%, transparent);
    color: #fff;
    font-weight: 700;
}

@media print
{
    /* Keep the shading on paper - it IS the report. Browsers strip
     * backgrounds by default when printing, which would leave a grid of bare
     * numbers and throw away the only thing that made it readable. */
    .heat-1, .heat-2, .heat-3, .heat-4, .heat-5,
    .roomtable tfoot th,
    .roomtable td.is-weekend.heat-0
    {
        -webkit-print-color-adjust: exact;
        print-color-adjust: exact;
    }

    .roomtable .roomcell
    {
        position: static;   /* sticky means nothing on paper */
    }
}

@media print
{
    /*
     * The printed report. This is the PDF: the browser's own print-to-PDF
     * renders the page's real fonts, so Slovenian, Croatian and Lithuanian
     * diacritics come out right on every machine in the building - which a
     * server-side PDF only manages once somebody has embedded a Latin
     * Extended-A font in it.
     */
    .reportbar,
    .reportactions
    {
        display: none !important;
    }

    .reportmain
    {
        padding: 0;
    }

    .reporttable
    {
        box-shadow: none;
        border-radius: 0;
        font-size: 10pt;
    }

    .reporttable thead th
    {
        /* Repeated at the top of every page by the browser, because a table
         * head only repeats if it is a real <thead> - which is why the markup
         * uses one. */
        background: #e8edfb !important;
        color: #000 !important;
        -webkit-print-color-adjust: exact;
        print-color-adjust: exact;
    }

    .reportsection th
    {
        background: #f2f5fd !important;
        -webkit-print-color-adjust: exact;
        print-color-adjust: exact;
    }

    /* Never break a row across two pages, and keep a section heading with at
     * least the first rows under it - a heading stranded at the foot of a
     * page is the classic printed-report defect. */
    .reporttable tr
    {
        break-inside: avoid;
    }

    .reportsection
    {
        break-after: avoid;
    }
}

/* ------------------------------------------------------------------ */
/* print - schools pin these on doors                                  */
/* ------------------------------------------------------------------ */

@media print
{
    /*
     * THE WHOLE HEADER GOES, not just its buttons.
     *
     * Hiding `.topbar-actions` alone left the logo, the arrows and a Today
     * button printed across the top of every timetable pinned to a door -
     * controls nobody can press on paper, eating the space the timetable
     * needed. What belongs on the sheet is the timetable and the date, and
     * the date is already in the day headings.
     */
    .topbar, .filterbar, .foot, .errorbar, .datejump, .weeknav
    {
        display: none !important;
    }

    body
    {
        background: #fff;
    }

    /*
     * NO SCROLLERS ON PAPER. Every one of these is a scrolling box on screen,
     * and a scrolling box printed is a box CLIPPED at whatever the scroll
     * position happened to be - which is how half a room overview ends up on
     * the sheet and nobody notices until it is on the wall.
     */
    .board-wrap, .board, .roomboard, .fbwrap, .picker-list, .epanel-body
    {
        overflow: visible !important;
        max-height: none !important;
    }

    /* Paper does not scroll, so the room column has nothing to get out of
     * the way OF - and --print-scale was computed in the template from a
     * 150 px column. A board that happened to be scrolled when Print was
     * pressed would otherwise print narrower than the scale it was given,
     * which is a sheet with a margin nobody asked for. */
    .roomboard
    {
        --roomname-w: 150px !important;
    }

    .board
    {
        box-shadow: none;
        border-color: #999;
    }

    /*
     * THE WEEK MUST FIT THE SHEET, WHATEVER THE SHEET IS.
     *
     * `.day` carries `min-width: 168px` on screen so a column never becomes
     * an unreadable ribbon. Five of those plus the 64 px time gutter is 904
     * px, which fits a landscape A4 (1047 px at 96 dpi with 10 mm margins)
     * and does NOT fit a portrait one (718 px). The stylesheet asks for
     * landscape below, but a browser that remembers the reader choosing
     * portrait for this site last time will print portrait anyway - and then
     * the week is guillotined after Tuesday, which is what the reader
     * actually sees.
     *
     * So on paper the minimum goes and the columns share whatever width the
     * page has. Nothing is lost by it: the cards are positioned in PER CENT
     * across their column, so they shrink with it, and only the time axis is
     * in pixels - which is vertical and unaffected.
     */
    .day
    {
        min-width: 0;
    }

    .board
    {
        width: 100%;
    }

    /*
     * AND IT MUST COME OUT IN COLOUR.
     *
     * The tint is not decoration on this page - it is how a reservation type
     * is told apart from a lecture at a glance, and it is the whole reason
     * the legend exists. Browsers drop background colours when printing
     * unless a page says otherwise, so the sheets pinned to doors were
     * coming out as identical white rectangles with grey borders.
     *
     * `print-color-adjust: exact` is the instruction to keep them. A reader
     * who wants to save ink can still say so in the print dialogue; this
     * only stops the browser deciding for them.
     */
    .card-body
    {
        box-shadow: none;
        border: 1px solid #999;
        border-left-width: 4px;
        -webkit-print-color-adjust: exact;
        print-color-adjust: exact;
    }

    .card-body, .badge, .swatch,
    .day.is-weekend .day-head, .day.is-holiday .day-canvas
    {
        -webkit-print-color-adjust: exact;
        print-color-adjust: exact;
    }

    /* A card that lands on a page break comes out cut in half through its
     * own title. Better to push the whole thing to the next sheet. */
    .card
    {
        break-inside: avoid;
    }

    /* Sticky is meaningless on paper and actively harmful: a sticky cell can
     * be painted at the wrong offset once the page stops scrolling. */
    .roomrow-head, .roomname, .roomtable .roomcell, .reporttable thead th
    {
        position: static !important;
    }

    .roomboard
    {
        box-shadow: none;
        border-radius: 0;
    }

    /*
     * FIT THE BOARD TO THE PAGE.
     *
     * These boards are laid out in absolute pixels - a lane is `width: 940px`
     * because a minute is 1.3 px - so they cannot reflow to a sheet of paper
     * the way ordinary content does. `--print-scale` is computed by the page
     * from the board's real width against the printable width of a landscape
     * A4, and `zoom` applies it to the whole thing, absolute positions and
     * all. Defaults to 1, so a board that already fits is untouched.
     */
    .roomboard
    {
        zoom: var(--print-scale, 1);
    }

    @page
    {
        size: landscape;
        margin: 10mm;
    }

    /* Paper has no controls. The view-mode picker joined the header after
     * this rule was written and was printing on every sheet. */
    .langpick, .viewpick
    {
        display: none;
    }
}

/* ------------------------------------------------------------------ */
/* The language control                                                */
/* ------------------------------------------------------------------ */
/*
 * Two languages live behind one button - the INTERFACE, which is ours and has
 * thirteen, and the school's own NAMES, which are theirs and have exactly two.
 * See templates/_langswitch.php for why they are separate controls.
 *
 * It sits at the head of the right-aligned button rail, so its pop-over hangs
 * from its RIGHT edge: opening rightwards from there puts it off the screen,
 * which is the same mistake the entity pickers made in an earlier round.
 */
.langpick
{
    position: relative;
}

/* A <details> summary is a disclosure widget by default, complete with a
 * triangle. Here it is a button, so the marker goes - in both dialects,
 * because Safari still wants the -webkit- pseudo-element. */
.langpick > summary
{
    list-style: none;
    cursor: pointer;
}

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

/*
 * A GLOBE AND A TWO-LETTER CODE.
 *
 * The button carried the language's own name - "Slovenščina", "Português",
 * "Lietuvių" - which is between 2 and 12 characters depending on which
 * language the page is in. Everything to its LEFT in a right-aligned rail
 * therefore moved when the language did, which is the reason the whole rail
 * was rebuilt once already.
 *
 * The gap is what makes the pair read as one label rather than as an icon
 * button that has picked up a stray word.
 */
.langpick-btn
{
    display: inline-flex;
    align-items: center;
    gap: 6px;
    white-space: nowrap;
}

.langpick-btn svg
{
    display: block;
    flex: none;
    opacity: .75;
}

/*
 * The code, on the button and again at the head of every row in the list.
 *
 * Tabular figures and a floor under the width so that SL, EN and IT all
 * occupy the same space: without it the button is two pixels wider in one
 * language than another, and a rail that shifts when you change the language
 * is exactly what this control is being blamed for elsewhere.
 */
.langpick-code
{
    display: inline-block;
    min-width: 1.6em;
    font-size: 12px;
    font-weight: 700;
    letter-spacing: .04em;
    font-variant-numeric: tabular-nums;
}

/* Inside a row of the list the code is a marker in front of the name, not the
 * name itself, so it steps back. */
.langpick-item .langpick-code
{
    margin-right: 8px;
    font-weight: 600;
    color: var(--ink-soft);
}

.langpick-item.is-active .langpick-code
{
    color: inherit;
}

/* "The names are not the school's own." A mark, not a word - see
 * templates/_langswitch.php. */
.langpick-star
{
    margin-left: 1px;
    font-weight: 700;
    line-height: 1;
}

.langpick-pop
{
    position: absolute;
    z-index: 60;
    top: calc(100% + 8px);
    right: 0;
    left: auto;
    /* 260 until the rows grew a two-letter code in front of the name on
     * 3 Sep 2026. Two columns of 121px could not hold "SL Slovenščina" and
     * the longest half-dozen wrapped onto two lines each. Every other pop-over
     * that shares this frame sets its own width, so this widens one control.
     * Still the same frame, still capped by the viewport below it. */
    width: min(320px, 92vw);
    max-height: 70vh;
    overflow-y: auto;
    padding: 8px;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-l);
    box-shadow: var(--shadow-3);
}

.langpick-head
{
    margin: 4px 6px 6px;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--ink-soft);
}

/*
 * Two columns for thirteen languages. One column would be a thirteen-row list
 * that scrolls on a laptop, and a language picker you have to scroll is a
 * language picker where people miss their own language.
 */
.langpick-list
{
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 2px;
    margin-bottom: 10px;
}

/* The content pair are short sentences, not single words, so they get the
 * full width rather than being squeezed into half of it and wrapping. */
.langpick-list-wide
{
    grid-template-columns: 1fr;
}

.langpick-item
{
    padding: 6px 8px;
    border-radius: var(--radius-s);
    font-size: 13px;
    color: var(--ink);
    text-decoration: none;
}

.langpick-item:hover
{
    background: var(--surface-2);
}

.langpick-item.is-active
{
    font-weight: 700;
    background: var(--surface-2);
}

.langpick-note
{
    margin: 0 6px;
    font-size: 11px;
    line-height: 1.45;
    color: var(--ink-soft);
}

/* ------------------------------------------------------------------ */
/* the event detail panel                                              */
/*                                                                     */
/* One block of rules, two containers. Rendered as a page it sits in   */
/* `.board-wrap` like any other page; fetched with panel=1 it is       */
/* dropped into a <dialog>. The panel itself does not know or care -   */
/* it is a column of text with a maximum width, and both containers    */
/* just give it somewhere to sit.                                      */
/* ------------------------------------------------------------------ */

.evpanel
{
    width: min(720px, 100%);
    margin: 0 auto;
    padding: 20px 22px 24px;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-l);
    box-shadow: var(--shadow-1);
}

.evpanel-head
{
    margin-bottom: 12px;
    padding-bottom: 12px;
    border-bottom: 1px solid var(--line);
}

.evpanel-title
{
    margin: 0;
    font-size: 20px;
    font-weight: 700;
    line-height: 1.25;
}

.evpanel-when
{
    margin: 4px 0 0;
    font-size: 13px;
    color: var(--ink-soft);
    font-variant-numeric: tabular-nums;
}

/*
 * The note keeps the line breaks the scheduler typed. It is prose written
 * by a person for people - "cancelled, replacement 14 May, bring the lab
 * handout" - and collapsing that into one paragraph loses the shape they
 * gave it. The left rule and the schedule-note colour are the same ones
 * the card and the mobile client use, so the field is recognisable.
 */
.evpanel-note
{
    margin: 0 0 14px;
    padding: 10px 12px;
    border-left: 3px solid rgb(179 103 71);
    border-radius: 0 var(--radius-s) var(--radius-s) 0;
    background: var(--surface-2);
    color: rgb(179 103 71);
    font-size: 14px;
    line-height: 1.5;
    white-space: pre-line;
}

/*
 * The word above a field, so a field is recognisable as one.
 *
 * The description used to be an unlabelled paragraph under the title, and it
 * was reported as MISSING by somebody looking straight at it - which settles
 * the argument about whether a label is decoration. Small, quiet, uppercase:
 * enough to name the block, not enough to compete with it.
 */
.evpanel-label
{
    margin: 0 0 4px;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: .04em;
    text-transform: uppercase;
    color: var(--ink-soft);
}

/*
 * The internal comment, in the panel's ordinary ink rather than the note's
 * amber.
 *
 * The amber rule means "this changes your day" - cancelled, moved, bring the
 * handout. An internal remark is not that: it is one member of staff writing
 * to another, and giving it the same alarm colour would put a note about who
 * has the key at the same volume as a cancellation. Same shape, calmer.
 *
 * It is drawn for signed-in staff only; see event.php, where that decision is
 * made and explained.
 */
.evpanel-internal
{
    border-left-color: var(--line-strong);
    color: var(--ink-soft);
}

.evlinks
{
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-bottom: 16px;
}

/*
 * Facts as a two-column definition list: label in a fixed narrow column,
 * value taking the rest. Fixed rather than auto, so "Rooms", "Lecturers"
 * and "Course type" line up down the page in every one of the thirteen
 * interface languages instead of the column resizing per row.
 */
.evfacts
{
    display: grid;
    grid-template-columns: minmax(96px, max-content) 1fr;
    gap: 6px 14px;
    margin: 0;
    font-size: 14px;
}

.evfacts dt
{
    font-size: 11px;
    font-weight: 700;
    letter-spacing: .06em;
    text-transform: uppercase;
    color: var(--ink-soft);
    padding-top: 2px;
}

.evfacts dd
{
    margin: 0;
    line-height: 1.45;
}

/* German and Slovenian labels are long enough to leave the value a sliver
 * on a phone, so below the fold the list stacks. */
@media (max-width: 560px)
{
    .evfacts
    {
        grid-template-columns: 1fr;
        gap: 2px;
    }

    .evfacts dd
    {
        margin-bottom: 8px;
    }
}

.evgroups
{
    margin-top: 18px;
    padding-top: 14px;
    border-top: 1px solid var(--line);
}

.evpanel-sub
{
    margin: 0 0 10px;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--ink-soft);
}

.evgroup-list
{
    margin: 2px 0 0;
    font-size: 13.5px;
    line-height: 1.5;
    color: var(--ink-soft);
}

/*  The programme, the branch and the year moved off the screen and into this
    row's `title` (see templates/event.php). A tooltip nobody can tell is
    there is a tooltip nobody opens, so the row wears the browser's own sign
    for "there is more behind this": the dotted underline and the help
    cursor, the same pair <abbr> has used since the beginning.

    Selected on [title] rather than on the class, so the row on a school
    whose groups carry no programme at all - where shape() leaves the title
    empty - is plain text and promises nothing.  */
.evgroup-list[title]
{
    cursor: help;
    text-decoration: underline dotted var(--line);
    text-underline-offset: 3px;
}

.evpanel-foot
{
    margin: 18px 0 0;
}

/*
 * THE DIALOG.
 *
 * Anchored to the bottom on a phone, because that is where the thumb is and
 * because the mobile client already trained everybody that tapping a lesson
 * slides a sheet up. Centred on a desktop, where there is no thumb and a
 * sheet stuck to the bottom edge of a 27-inch screen is just far away.
 *
 * `.evpanel` inside the dialog drops its own frame - the dialog is already
 * a raised surface and two borders read as a box inside a box.
 */
.evdialog
{
    padding: 0;
    border: 0;
    background: transparent;
    max-width: min(720px, 96vw);
    max-height: 92vh;
    overflow: auto;
    overscroll-behavior: contain;
}

.evdialog::backdrop
{
    background: rgb(0 0 0 / .42);
}

.evdialog .evpanel
{
    border: 0;
    box-shadow: var(--shadow-3);
}

/* Room for the close control, which only exists in the dialog. Subject
 * names run long - "International Business Economics (lecture)" already
 * reaches the corner - and a heading with a cross sitting on its last word
 * looks like a rendering fault rather than a button. */
.evdialog .evpanel-head
{
    padding-right: 34px;
}

@media (max-width: 560px)
{
    .evdialog
    {
        /* Bottom sheet. `margin-top: auto` in a dialog's centring box is
         * what pins it to the bottom without position tricks. */
        margin: auto auto 0;
        max-width: 100vw;
        width: 100vw;
    }

    .evdialog .evpanel
    {
        border-radius: var(--radius-l) var(--radius-l) 0 0;
        padding-bottom: max(24px, env(safe-area-inset-bottom));
    }
}

/*
 * The close control the dialog adds. It does not exist in the page
 * rendering - there the header's Close button and the back button do the
 * job - so app.js injects it and this styles it.
 */
.evdialog-close
{
    position: absolute;
    top: 10px;
    right: 12px;
    z-index: 2;
    width: 30px;
    height: 30px;
    display: grid;
    place-items: center;
    border: 1px solid var(--line);
    border-radius: 999px;
    background: var(--surface);
    color: var(--ink-soft);
    font-size: 16px;
    line-height: 1;
    cursor: pointer;
}

.evdialog-close:hover
{
    background: var(--surface-2);
    color: var(--ink);
}

/* While the fetch is in flight. One line, no spinner: the request is a
 * local round trip and a spinner that flashes for 80 ms is noise. */
.evdialog-wait
{
    padding: 28px 22px;
    background: var(--surface);
    border-radius: var(--radius-l);
    color: var(--ink-soft);
    font-size: 14px;
}

@media print
{
    /* Printing the timetable must not print a hairline box round every
     * card where the stretched link is.
     *
     * NOT the room board's blocks, which carry the same class and ARE the
     * event - see the note beside .card-open. Without the :not() this rule
     * printed the room overview as a grid of hour lines, a column of room
     * names and no events at all. */
    .card-open:not(.rblock)
    {
        display: none;
    }

    /*
     * ON PAPER THE ICONS BECOME WORDS AGAIN.
     *
     * A masked icon is drawn with a background colour, and browsers do not
     * print backgrounds unless the reader has gone looking for the setting -
     * so the chip would come out of the printer as a blank rounded box. The
     * word was in the markup the whole time; print simply gives it its size
     * back. Nobody can click a printed page anyway, and "Documentation"
     * tells a person there is something to go and find.
     */
    .badge-link, .badge-doc, .badge-room
    {
        width: auto;
        height: auto;
        padding: 1px 6px;
        font-size: 9px;
        border: 1px solid #666;
        background: none;
        color: #000;
    }

    .badge-link::before, .badge-doc::before, .badge-room::before
    {
        display: none;
    }

    .evpanel
    {
        border: 0;
        box-shadow: none;
        padding: 0;
    }

    .evpanel-foot
    {
        display: none;
    }

    /* A printed button is a dead rectangle. On paper the link becomes its
     * own address, which is the only form of it anybody can use there. */
    .evlinks a
    {
        background: none;
        border: 0;
        padding: 0;
        color: #000;
        font-weight: 700;
    }

    .evlinks a::after
    {
        content: " " attr(href);
        font-weight: 400;
        word-break: break-all;
    }
}

/* ------------------------------------------------------------------ */
/* the view-mode control                                               */
/*                                                                     */
/* Shares the language pop-over's frame (.langpick-pop) on purpose -   */
/* two pop-overs in the same header rail that were shaped differently  */
/* would read as two unrelated mechanisms. Only the list inside is its */
/* own, because these five items each carry a line of explanation and  */
/* the language list does not.                                         */
/* ------------------------------------------------------------------ */

.viewpick
{
    position: relative;
}

.viewpick-btn
{
    white-space: nowrap;
}

.viewpick-pop
{
    /* Wider than the language pop-over: each row is a name and a sentence,
     * and a sentence that wraps to four lines stops being a hint. */
    width: min(320px, 92vw);
}

/* The ? pop-over draws the same kind of list - a name and a line saying what
 * it is - so it shares the styling. It does NOT share the class names: those
 * double as markers the tests search for, and two elements answering to one
 * name makes every such search find whichever is first in the document. */
.viewpick-list,
.helppick-list
{
    display: grid;
    gap: 2px;
}

.viewpick-item,
.helppick-item
{
    display: block;
    padding: 7px 8px;
    border-radius: var(--radius-s);
    text-decoration: none;
    color: var(--ink);
}

.viewpick-item:hover,
.helppick-item:hover
{
    background: var(--surface-2);
}

.viewpick-item.is-active
{
    background: var(--surface-2);
}

.viewpick-name
{
    display: block;
    font-size: 13px;
}

.viewpick-item.is-active .viewpick-name
{
    font-weight: 700;
}

/*
 * The hint is the reason this control is usable. "Optimised height" and
 * "Optimise everything" are near-synonyms as labels; the difference only
 * becomes a choice once each says which of the two things it does.
 */
.viewpick-hint
{
    display: block;
    margin-top: 1px;
    font-size: 11px;
    line-height: 1.4;
    color: var(--ink-soft);
}

/*
 * A COMPRESSED GRID KEEPS ITS TICKS AND DROPS THE SUB-HOUR NUMBERS.
 *
 * Some schools run fifteen-minute bell lines. At full height those labels sit
 * 16 px apart and read fine; compressed they are 10 px apart and turn into a
 * grey smear down the left edge - which is worse than no ruler, because it
 * looks like a rendering fault rather than a deliberate density.
 *
 * `text-indent` and not `display: none`: the element still lays out, so the
 * tick mark drawn by ::after stays exactly where it was. Only the digits
 * leave, and the line they were labelling is still there to measure against.
 */
.board.is-dense .tick:not(.is-hour)
{
    text-indent: -9999px;
    overflow: hidden;
    background: none;
}

/* The hours themselves are shorter too, so they need less air around them
 * or the ruler ends up taller than the day it measures. */
.board.is-dense .tick.is-hour
{
    font-size: 10px;
}

/* ------------------------------------------------------------------ */
/* the account screen                                                  */
/*                                                                     */
/* Borrows the event panel's frame, because it is the same shape: one  */
/* column of text with a maximum width, sitting in the middle of the   */
/* board area. A second nearly-identical panel style would be two      */
/* places to keep the padding in step.                                 */
/* ------------------------------------------------------------------ */

.acctpanel
{
    width: min(520px, 100%);
}

.acctform
{
    display: grid;
    gap: 12px;
    margin-top: 4px;
}

/*
 * The fields are STACKED, one per row, deliberately. Three password boxes
 * side by side is how somebody types their new password into the confirmation
 * and their confirmation into the current - and then reads "the current
 * password is not right" and doubts their own memory.
 */
.acctform .field
{
    display: grid;
    gap: 4px;
}

.acctform .field-label
{
    font-size: 12px;
    font-weight: 600;
    color: var(--ink-soft);
}

.acctform .input
{
    width: 100%;
    padding: 9px 11px;
    font: inherit;
    font-size: 14px;
    color: var(--ink);
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-s);
}

.acctform .input:focus-visible
{
    outline: 2px solid var(--brand);
    outline-offset: 1px;
    border-color: transparent;
}

.acctform-hint
{
    margin: 0;
    font-size: 12px;
    line-height: 1.5;
    color: var(--ink-soft);
}

.acctform-actions
{
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-top: 4px;
}

/* Quieter than the hint: this one is a fact about sessions, not advice about
 * the password, and it should not compete with the form. */
.acctform-note
{
    margin: 18px 0 0;
    padding-top: 12px;
    border-top: 1px solid var(--line);
    font-size: 11.5px;
    line-height: 1.5;
    color: var(--ink-soft);
}

/* The name in the header rail. Long names are cut rather than allowed to
 * push the sign-out button off the edge - the point of the chip is
 * recognition, and the first few characters carry that. */
.chip-who
{
    max-width: 18ch;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
}

/* ------------------------------------------------------------------ */
/* confirmations                                                       */
/*                                                                     */
/* Two actions in this application are not undoable from inside it:    */
/* deleting a booking, and signing out of a half-filled form. Both ask */
/* first, and both ask the same way.                                   */
/*                                                                     */
/* A <details> rather than window.confirm(): it works with scripting   */
/* off, it can be styled, we translate it ourselves, and - the one     */
/* that decides it - it can name the thing that is about to go.        */
/* ------------------------------------------------------------------ */

.confirm
{
    display: inline-block;
}

.confirm > summary
{
    /* The disclosure triangle would read as "expand" beside a word that
     * means "destroy". This is a button that asks a question. */
    list-style: none;
    cursor: pointer;
}

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

.confirm-pop
{
    padding: 10px 12px;
    background: var(--surface);
    border: 1px solid #e35d6a;
    border-radius: var(--radius-s);
}

.confirm-q
{
    margin: 0;
    font-size: 13px;
    font-weight: 600;
    line-height: 1.35;
    color: var(--ink);
}

/* WHICH booking. Clipped to two lines: a description can be a paragraph,
 * and a confirmation that is itself a wall of text is not read either. */
.confirm-what
{
    margin: 4px 0 0;
    font-size: 12px;
    line-height: 1.4;
    color: var(--ink-soft);
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.confirm-actions
{
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin-top: 10px;
}

/*
 * IN A LIST, THE ROW GROWS. IN THE HEADER, THE QUESTION HANGS.
 *
 * The two places need opposite behaviour and the reason is the same in both:
 * what the ancestor does with overflow.
 *
 * A booking row lives inside `.booklist`, which SCROLLS - and an
 * `overflow: auto` ancestor clips an absolutely positioned child. The first
 * version floated the question above the button and it came out sliced in
 * half against the top edge of the panel, with the Yes button outside it. So
 * in a list nothing floats: the row takes the full width and grows.
 */
.confirm[open]
{
    flex: 1 1 100%;
}

.confirm-pop
{
    margin-top: 8px;
}

/*
 * The header rail does not scroll and must not grow - a sticky header that
 * doubles in height pushes the whole timetable down and then springs back.
 * So there the question hangs UNDER the button, anchored to its right edge
 * because that rail is right-aligned and opening leftwards is the only
 * direction that stays on the screen.
 */
.confirm-out
{
    position: relative;
}

.confirm-out[open]
{
    flex: 0 0 auto;
}

.confirm-out .confirm-pop
{
    position: absolute;
    z-index: 60;
    top: calc(100% + 8px);
    right: 0;
    width: max-content;
    max-width: min(280px, 86vw);
    margin-top: 0;
    box-shadow: var(--shadow-3);
}

/* Paper has no buttons to press, and an open confirmation would print as a
 * red box asking a question nobody can answer. */
@media print
{
    .confirm
    {
        display: none;
    }
}

/* ------------------------------------------------------------------ */
/* The consolidated view                                               */
/*                                                                     */
/* A whole term folded onto one week - see _consolidated.php and       */
/* ReportsPage::shapeConsolidated(). A plain table, because what is    */
/* worth reading here is WHICH WEEKS each entry runs, and that is      */
/* text; the board's job of showing duration as height does not apply  */
/* when the same lesson happens in eleven different weeks.             */
/* ------------------------------------------------------------------ */

.conskey
{
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin: 0 0 14px;
}

/* The key is the grouping, so it is chips in the same colours the rows
 * wear - not a list of words next to swatches. */
.conskey-item
{
    padding: 3px 10px;
    border: 1px solid;
    border-radius: var(--radius-s);
    font-size: 12px;
    font-weight: 650;
}

.constable
{
    width: 100%;
    border-collapse: collapse;
    table-layout: fixed;
}

.constable th
{
    padding: 6px 8px;
    text-align: left;
    font-size: 12.5px;
    color: var(--ink-soft);
    border-bottom: 1px solid var(--line);
}

.constable td
{
    padding: 0;
    vertical-align: top;
    border: 1px solid var(--line);
}

/* The hour column is narrow and fixed: it holds "09:00" and nothing else,
 * and every pixel it takes is a pixel a course name does not get. */
.constable-hour
{
    width: 58px;
    padding: 6px 8px !important;
    font-size: 12.5px;
    font-weight: 650;
    color: var(--ink-soft);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    border-left: 0 !important;
}

/*
 * THE RULE THAT SAYS "NEW BLOCK".
 *
 * Rows are grouped by hour and then by type, and within one group there is a
 * row per overlapping lesson. Without a heavier line at the top of each group
 * the table is fifty undifferentiated rows; with it, the eye finds 09:00's
 * lectures, then 09:00's practicals, without reading a word.
 */
.constable tr.is-blockstart > td
{
    border-top: 2px solid var(--ink-soft);
}

.conscell
{
    padding: 6px 8px !important;
    border-left-width: 4px !important;
    border-left-style: solid !important;
    font-size: 12px;
    line-height: 1.35;
}

.conscell-course
{
    font-weight: 700;
    font-size: 12.5px;
}

/*
 * EVERY LINE IN A CELL SHADES ITSELF FROM currentColor.
 *
 * The cell sets its own `color` from the school's palette, because it also
 * sets its own background from it (see templates/_consolidated.php). A child
 * that asks for `var(--ink)` or `var(--ink-soft)` instead pulls the READER'S
 * THEME back in underneath a background that never changes with the theme -
 * which in dark mode is near-white text on a pale tint, invisible until you
 * happen to drag a selection across it. That is exactly how this was found.
 *
 * So the quiet lines are mixes of the inherited colour rather than palette
 * tokens: the hierarchy is preserved, and it stays correct on any tint and in
 * either theme, with nothing to keep in step.
 */

/*
 * How it is taught, under the subject.
 *
 * Quiet and small: the colour band down the left of the cell already groups
 * by this, so the word is here for the cases colour cannot serve - a printed
 * black-and-white copy, a reader who cannot tell the two tints apart, a
 * screen scrolled past the key. Setting it at the weight of the subject would
 * make every cell read as two headings.
 */
.conscell-type
{
    color: color-mix(in srgb, currentColor 68%, transparent);
    font-size: 11px;
    font-weight: 600;
}

.conscell-when
{
    color: inherit;
}

/*
 * THE LINE THE REPORT EXISTS FOR.
 *
 * Everything else in the cell is on the ordinary week grid as well. This is
 * what replaced the other forty-nine grids, so it is the one line in the cell
 * that is not grey.
 */
.conscell-weeks
{
    font-weight: 650;
    font-variant-numeric: tabular-nums;
}

.conscell-where
{
    color: color-mix(in srgb, currentColor 68%, transparent);
}

/* ------------------------------------------------------------------ */
/* printing it                                                         */
/*                                                                     */
/* This report exists to be pinned to a noticeboard and handed to a     */
/* first-year, so print is not an afterthought. Landscape, because it   */
/* is five or six columns wide; colours forced, because the colour IS   */
/* the type; and no row split across a page break, because half a      */
/* lesson at the foot of a sheet is worse than a shorter sheet.         */
/* ------------------------------------------------------------------ */

@media print
{
    .constable
    {
        font-size: 9pt;
    }

    .constable tr
    {
        break-inside: avoid;
        page-break-inside: avoid;
    }

    .constable thead
    {
        display: table-header-group;   /* repeat the weekdays on every page */
    }

    .conskey,
    .conscell
    {
        print-color-adjust: exact;
        -webkit-print-color-adjust: exact;
    }
}

/* ------------------------------------------------------------------ */
/* settings editor                                                     */
/*                                                                     */
/* Ninety-odd keys, admins only, on their own screen. Two problems to   */
/* solve and they pull against each other: the list has to be COMPACT   */
/* enough to scan - the first version was one long column and unusable  */
/* - and each row has to say what it does and whether it does anything  */
/* here at all.                                                        */
/*                                                                     */
/* The answers are tabs (the legacy dialog paged its settings too, and  */
/* it was right), a responsive grid so a wide screen shows two or three */
/* columns instead of one tall ribbon, a status dot per row, and the    */
/* explanation printed small under the field rather than hidden in a    */
/* tooltip nobody hovers on a touch screen. The title attribute carries */
/* it as well, for the mouse.                                          */
/* ------------------------------------------------------------------ */

.setpanel
{
    /* Wider than the account panel, which is width: min(720px, 100%) - and
     * WIDTH is what has to be overridden here, not max-width, or the grid
     * below never gets more than two columns however wide the screen is.
     * This panel earns the room by putting settings side by side. */
    width: min(1180px, 100%);
}

/* the tab strip ---------------------------------------------------- */

.settabs
{
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    padding: 4px 0 10px;
    border-bottom: 1px solid var(--line);
}

.settab
{
    display: inline-flex;
    align-items: center;
    gap: 7px;
    padding: 7px 13px;
    border: 1px solid var(--line);
    border-radius: 999px;
    background: var(--surface);
    color: var(--ink-soft);
    font-size: 13px;
    font-weight: 600;
    text-decoration: none;
    transition: border-color .15s ease, color .15s ease, background-color .15s ease;
}

.settab:hover
{
    color: var(--ink);
    border-color: var(--line-strong);
}

.settab.is-on
{
    background: color-mix(in srgb, var(--brand) 12%, var(--surface));
    border-color: color-mix(in srgb, var(--brand) 45%, var(--line));
    color: var(--brand-ink);
}

/* How many settings are on that tab. Small, so it informs without
 * competing with the tab's name. */
.settab-count
{
    min-width: 20px;
    padding: 1px 6px;
    border-radius: 999px;
    background: var(--surface-2);
    color: var(--ink-soft);
    font-size: 11px;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    text-align: center;
}

.settab.is-on .settab-count
{
    background: color-mix(in srgb, var(--brand) 22%, var(--surface));
    color: var(--brand-ink);
}

/* the legend and the status dots ----------------------------------- */

.setlegend
{
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px 18px;
    margin: 10px 0 4px;
    font-size: 12px;
    color: var(--ink-soft);
}

/*
 * THE DOT.
 *
 * Three states, and the colour alone is not allowed to be the whole
 * message - the legend spells each one out in words, and every row's
 * title attribute repeats it. Colour is the fast path, not the only path.
 */
.setdot
{
    flex: 0 0 auto;
    width: 8px;
    height: 8px;
    margin-right: -10px;    /* the legend's own gap does the spacing */
    border-radius: 50%;
    background: var(--line-strong);
}

.setlegend .setdot
{
    margin-right: 0;
}

.setdot.is-live
{
    background: #16a34a;    /* green: this key works here            */
}

.setdot.is-soon
{
    background: #d97706;    /* amber: specified, not yet enforced    */
}

.setdot.is-legacy
{
    background: var(--line-strong);   /* grey: old web only          */
}

/* the grid of settings --------------------------------------------- */

/*
 * One column on a phone, more as the screen allows. 320px is the width at
 * which a key name, its field and a line of explanation stay readable;
 * auto-fill rather than auto-fit so a page with two settings does not
 * stretch them across a 27-inch monitor.
 */
.setgrid
{
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
    gap: 10px;
    padding: 12px 0 4px;
}

.setcell
{
    display: grid;
    grid-template-columns: 1fr auto;
    align-items: center;
    gap: 4px 12px;
    padding: 10px 12px;
    border: 1px solid var(--line);
    border-radius: var(--radius-m);
    background: var(--surface);
}

/* A live setting is the one that matters; give it a spine in the brand
 * colour, the same device the timetable's event cards use. */
.setcell.is-live
{
    border-left: 3px solid #16a34a;
}

.setcell.is-soon
{
    border-left: 3px solid #d97706;
    background: color-mix(in srgb, #d97706 4%, var(--surface));
}

.setcell.is-legacy
{
    background: color-mix(in srgb, var(--surface-2) 55%, var(--surface));
}

.setname
{
    display: flex;
    align-items: center;
    gap: 8px;
    min-width: 0;               /* lets the key name ellipsise       */
    cursor: help;
}

.setkey
{
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 12.5px;
    font-weight: 600;
    color: var(--ink);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.setval
{
    justify-self: end;
    min-width: 0;
}

.settext
{
    /* 150, not 190. The key name shares this row and it is the label - a
     * name ellipsised to "lastWeekForS..." makes the row useless, while a
     * number field two characters narrower costs nothing. */
    width: 150px;
    max-width: 100%;
    font-size: 13px;
    padding: 6px 9px;
}

/* The mail bodies and anything else long: full width, its own row. */
.setarea
{
    width: 100%;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 12px;
    line-height: 1.45;
    resize: vertical;
}

.setcell:has(.setarea)
{
    grid-template-columns: 1fr;
}

.setcell:has(.setarea) .setval
{
    justify-self: stretch;
}

/*
 * The explanation, under both columns.
 *
 * Printed rather than left to the tooltip: a tooltip is invisible on a
 * touch screen and unfindable on a keyboard, and this is the sentence that
 * tells an administrator whether they are about to change anything at all.
 */
.sethelp
{
    grid-column: 1 / -1;
    margin: 2px 0 0;
    font-size: 11.5px;
    line-height: 1.45;
    color: var(--ink-soft);
}

.setform-actions
{
    display: flex;
    gap: 10px;
    justify-content: flex-end;
    padding: 14px 0 4px;
    position: sticky;
    bottom: 0;
    /* Save follows you down a long page. The fade keeps the rows from
     * appearing to end abruptly under the bar. */
    background: linear-gradient(to top,
        var(--bg) 60%,
        color-mix(in srgb, var(--bg) 0%, transparent));
}

/* A quiet notice, for the "this tab does nothing here" line. */
.notice-info
{
    border-color: var(--line-strong);
    background: var(--surface-2);
    color: var(--ink-soft);
}

/* the boolean toggle ----------------------------------------------- */

.toggle
{
    display: inline-flex;
    align-items: center;
    cursor: pointer;
}

.toggle input
{
    position: absolute;
    opacity: 0;
    width: 0;
    height: 0;
}

.toggle-track
{
    width: 42px;
    height: 24px;
    border-radius: 999px;
    background: var(--line-strong);
    position: relative;
    transition: background-color .15s ease;
}

.toggle-track::after
{
    content: "";
    position: absolute;
    top: 3px;
    left: 3px;
    width: 18px;
    height: 18px;
    border-radius: 50%;
    background: #fff;
    box-shadow: var(--shadow-1);
    transition: transform .15s ease;
}

.toggle input:checked + .toggle-track
{
    background: var(--brand);
}

.toggle input:checked + .toggle-track::after
{
    transform: translateX(18px);
}

.toggle input:focus-visible + .toggle-track
{
    outline: 2px solid var(--brand);
    outline-offset: 2px;
}

/*
 * Room for the key name.
 *
 * The name IS the label here - there is no friendly title, because the thing
 * being edited is a database key and an administrator searches for it by that
 * exact string. Ellipsising it to "numWeekendDa..." defeats the page. So the
 * cell's first column may shrink to nothing on paper but never below the
 * width of a name in practice, and a name that still does not fit wraps onto
 * a second line rather than being cut.
 */
.setcell
{
    grid-template-columns: minmax(0, 1fr) auto;
}

.setkey
{
    white-space: normal;
    overflow-wrap: anywhere;
    text-overflow: clip;
    overflow: visible;
    line-height: 1.35;
}

/* The sticky action bar floats over the grid; leave it somewhere to float. */
.setgrid
{
    padding-bottom: 18px;
}

/* ------------------------------------------------------------------ */
/* text controls, everywhere                                           */
/*                                                                     */
/* `color-scheme` in the token block already stops the browser painting */
/* these white on a dark page, but a control this product draws should  */
/* not look like whatever the operating system felt like: it belongs to */
/* the same surfaces, lines and radii as everything else. One rule, so  */
/* a field on the settings screen and a field in a dialog match.        */
/* ------------------------------------------------------------------ */

.input,
.settext,
input[type="text"],
input[type="password"],
input[type="number"],
input[type="search"],
input[type="email"],
select,
textarea
{
    font: inherit;
    color: var(--ink);
    background: var(--surface);
    border: 1px solid var(--line-strong);
    border-radius: var(--radius-s);
    padding: 7px 10px;

    /* The height the help mark aligns against - see --control-h. A
     * minimum rather than a height, so a textarea still grows. */
    min-height: var(--control-h);
}

/*
 * SELECT WAS THE ONE CONTROL THIS PRODUCT DID NOT DRAW.
 *
 * Every other field here is ours: our surface, our line, our radius. The
 * select was left to the operating system, so on the signage tab it sat among
 * them as a grey box with a different border and a different height - and
 * when it opened, a light list on a dark page.
 *
 * Two halves to fixing that, and only the first is ordinary CSS.
 *
 * THE CLOSED CONTROL is styled like the rest above, with the browser's own
 * arrow removed and ours drawn in its place as a background image. Padding on
 * the right leaves room for it. `appearance: none` is what makes any of the
 * rest take effect at all.
 *
 * THE OPEN LIST is drawn by the operating system and cannot be styled - only
 * TOLD which scheme to draw in, with `color-scheme`. The token block declares
 * `light dark`, which is right for the page as a whole; a select needs to be
 * explicit, because that popup is the one piece of this interface the browser
 * paints from its own palette. Set per theme, below.
 */
select
{
    appearance: none;
    -webkit-appearance: none;
    padding-right: 32px;

    /* Our own arrow: a chevron, in the muted ink colour, drawn once rather
     * than fetched. Two lines meeting - the same shape the pickers use. */
    background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill='none' stroke='%239aa3b2' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round' d='M4 6.5 8 10.5l4-4'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 9px center;
    background-size: 16px 16px;
    cursor: pointer;
}

select:hover
{
    border-color: color-mix(in srgb, var(--brand) 40%, var(--line-strong));
}

select:focus-visible
{
    outline: 2px solid var(--brand);
    outline-offset: 1px;
    border-color: var(--brand);
}

/*  THE OPEN LIST OF A SELECT, AND THE CASCADE THAT DEFEATED THE INTENT.
 *
 *  This used to read:
 *
 *      / * The light theme is the default one, so the popup is told so here
 *          and the dark block overrides it. * /
 *      select { color-scheme: light; }
 *
 *  The dark block does NOT override it. That block is five thousand lines
 *  ABOVE (search for `select { color-scheme: dark; }` in the
 *  prefers-color-scheme: dark section), the two rules have the same
 *  specificity, and the later one wins - so on a machine set to dark every
 *  select popup was painted LIGHT while the page around it stayed dark. The
 *  option text still took its colour from the dark theme's tokens, which is
 *  pale, so a lecturer picker on a dark machine was pale grey names on white:
 *  present, legible only if you tilted the screen. Mišo reported it from the
 *  course form's "who takes this line" list on 8 Sep 2026.
 *
 *  It survived because the comment described what the author meant rather
 *  than what the cascade does, and because the two halves are five thousand
 *  lines apart - nobody reading either one sees the other.
 *
 *  THE FIX IS TO MAKE THEM MUTUALLY EXCLUSIVE rather than to reorder them. A
 *  rule that only applies on a light machine cannot be beaten by, or beat,
 *  one that only applies on a dark machine, whichever order they end up in
 *  after the next edit. The administration application's hand-set themes were
 *  never affected: `body.mode-dark select` carries a class and outranks both.
 */
@media (prefers-color-scheme: light)
{
    select { color-scheme: light; }
}

.input:hover,
.settext:hover,
textarea:hover
{
    border-color: color-mix(in srgb, var(--brand) 40%, var(--line-strong));
}

.input:focus-visible,
.settext:focus-visible,
textarea:focus-visible
{
    outline: 2px solid var(--brand);
    outline-offset: 1px;
    border-color: var(--brand);
}

.settext::placeholder,
textarea::placeholder
{
    color: var(--ink-soft);
    opacity: .75;
}

/*
 * The event dialog's own text colour.
 *
 * Belt and braces behind `color-scheme`. A <dialog> takes `color: CanvasText`
 * from the user agent, and that was the reported fault - a dark panel with
 * black text on it. Saying so explicitly means the dialog stays readable even
 * where the scheme is forced the other way.
 */
.evdialog,
.evdialog .evpanel
{
    color: var(--ink);
}

/* ------------------------------------------------------------------ */
/* settings sections                                                   */
/*                                                                     */
/* The editor stopped paging when it stopped showing ninety keys. What  */
/* is left is a short list, grouped, on one screen. The tab strip and   */
/* the status dots above are kept in the sheet - unused today, and      */
/* cheap to keep for the day the list is long enough to want them.      */
/* ------------------------------------------------------------------ */

.setsection
{
    margin-top: 18px;
}

.setsection:first-of-type
{
    margin-top: 6px;
}

/*
 * A section heading that reads as a divider rather than a title: small,
 * spaced, in the soft ink, with a rule running to the right of it. Loud
 * headings over two settings each would make the page look busier than the
 * ninety-key version it replaced.
 */
.setsection-title
{
    display: flex;
    align-items: center;
    gap: 12px;
    margin: 0 0 8px;
    font-size: 11.5px;
    font-weight: 700;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--ink-soft);
}

.setsection-title::after
{
    content: "";
    flex: 1;
    height: 1px;
    background: var(--line);
}

/* No status dot any more: every row on this screen is a setting that works,
 * so a badge saying so would be noise. The cell keeps its quiet frame. */
.setcell
{
    border-left: 1px solid var(--line);
}

.setgrid
{
    padding-top: 0;
    padding-bottom: 10px;
}

/* ------------------------------------------------------------------ */
/* the settings label is prose now, not a symbol                       */
/*                                                                     */
/* It used to print the tbweb_settings key, so it was set in the mono  */
/* face - correct for an identifier, wrong for "First day of the week".*/
/* The key still exists, in the tooltip, where support wants it.       */
/* ------------------------------------------------------------------ */

.setname
{
    font-family: var(--font);
    font-size: 13.5px;
    font-weight: 600;
    letter-spacing: 0;
    color: var(--ink);
    /* A label with a tooltip should say so. `help` rather than `pointer`:
     * pointing at it does not navigate anywhere, it explains. */
    cursor: help;
}

/* ------------------------------------------------------------------ */
/* settings, compacted                                                 */
/*                                                                     */
/* The explanations were right about the words and wrong about the     */
/* space: ten settings became a page of paragraphs, mostly white. The  */
/* text now lives behind a small round "?" that opens it in place.     */
/*                                                                     */
/* A <details>, not a tooltip. A tooltip does not exist on a touch      */
/* screen and cannot be reached from a keyboard, and this sentence is   */
/* the only place a setting explains itself.                           */
/* ------------------------------------------------------------------ */

.setgrid
{
    /* Tighter, now that a row is a row rather than a row and a paragraph. */
    gap: 8px;
    padding-bottom: 6px;
}

.setcell
{
    display: grid;
    grid-template-columns: 1fr auto auto;
    align-items: center;
    gap: 10px;
    padding: 8px 12px;
}

.setcell .setval
{
    justify-self: end;
}

/* The circle. Small, quiet, and the same size as the toggle it sits
 * beside so a row reads as one line rather than three heights. */
.sethelp
{
    justify-self: end;
}

.sethelp-mark
{
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 18px;
    height: 18px;
    border-radius: 50%;
    border: 1px solid var(--line-strong);
    color: var(--ink-soft);
    font-size: 11px;
    font-weight: 700;
    line-height: 1;
    cursor: help;
    user-select: none;
    transition: color .12s ease, border-color .12s ease, background .12s ease;
}

/* The default triangle would make the circle a lopsided pill. */
.sethelp-mark::-webkit-details-marker { display: none; }
.sethelp-mark::marker                 { content: ""; }

.sethelp-mark:hover
{
    color: var(--brand);
    border-color: var(--brand);
    background: color-mix(in srgb, var(--brand) 10%, transparent);
}

.sethelp[open] .sethelp-mark
{
    color: #fff;
    background: var(--brand);
    border-color: var(--brand);
}

.sethelp-mark:focus-visible
{
    outline: 2px solid var(--brand);
    outline-offset: 2px;
}

/*
 * The opened text. It belongs to the row, so it spans the whole cell and
 * sits under everything rather than squeezing the third column - which is
 * why the cell is a grid and this claims all of its columns.
 */
.sethelp-text
{
    grid-column: 1 / -1;
    margin: 8px 0 0;
    color: var(--ink-soft);
    font-size: 12.5px;
    line-height: 1.45;
    max-width: 62ch;
}

/* A <details> cannot span its own grid cell, so the text is lifted out of
 * the summary's flow and placed on the cell instead. */
.setcell:has(.sethelp[open])
{
    grid-template-areas:
        "name value mark"
        "help help  help";
}

.setcell:has(.sethelp[open]) .setname { grid-area: name; }
.setcell:has(.sethelp[open]) .setval  { grid-area: value; }
.setcell:has(.sethelp[open]) .sethelp { grid-area: mark; }

.sethelp[open]
{
    display: contents;
}

.sethelp[open] .sethelp-mark
{
    grid-area: mark;
    justify-self: end;
}

.sethelp[open] .sethelp-text
{
    grid-area: help;
}

@media (max-width: 560px)
{
    .setcell
    {
        /* On a phone the value wants its own line rather than a sliver. */
        grid-template-columns: 1fr auto;
    }

    .setcell .setval
    {
        grid-column: 1 / -1;
        justify-self: start;
    }

    .setcell .settext
    {
        width: 100%;
    }
}

/* ------------------------------------------------------------------ */
/* a report row opens its event                                        */
/*                                                                     */
/* A whole <tr> cannot be an anchor, so two cells carry the link and    */
/* the row lights up as one thing on hover - which is the only honest   */
/* way to say "this line is clickable" without lying about the markup.  */
/* ------------------------------------------------------------------ */

.rowopen
{
    color: inherit;
    text-decoration: none;
    /* Dotted rather than solid: the row is a record first and a link
     * second, and a table full of blue underlines reads as a link farm. */
    border-bottom: 1px dotted var(--line-strong);
}

.rowopen:hover
{
    color: var(--brand-ink);
    border-bottom-color: var(--brand);
}

.is-openable:hover
{
    background: color-mix(in srgb, var(--brand) 6%, transparent);
}

.rowopen:focus-visible
{
    outline: 2px solid var(--brand);
    outline-offset: 2px;
    border-radius: 3px;
}

/* Paper has no links. */
@media print
{
    .rowopen
    {
        border-bottom: 0;
        color: inherit;
    }
}

/* ------------------------------------------------------------------ */
/* SETTINGS: three columns, thin rules, no headings                    */
/*                                                                     */
/* The version before this one was a stack of framed cards under eight */
/* section headings, and it was mostly white space over ten settings.  */
/* The names describe themselves - "First day of the week" needs no    */
/* banner reading THE WEEK above it - so the catalogue's grouping       */
/* survives as ORDER, and the page becomes what it always was: a short  */
/* list of name, value, and a question mark.                            */
/*                                                                     */
/* Everything below overrides the earlier .setgrid/.setcell rules,      */
/* which stay in the sheet because the reports screens use the tokens.  */
/* ------------------------------------------------------------------ */

.setgrid
{
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    /* The gap IS the rule: a 1px column gap over a lined background is
     * one hairline per column boundary and no extra elements. */
    column-gap: 1px;
    row-gap: 0;
    padding: 0;
    margin-top: 4px;
    background: var(--line);
    border: 1px solid var(--line);
    border-radius: var(--radius-m);
    overflow: hidden;
}

/* Each cell paints its own background over the lined grid, which is what
 * leaves the hairlines showing between them. */
.setcell
{
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto auto;
    align-items: center;
    gap: 10px;
    padding: 9px 12px;
    background: var(--surface);
    border: 0;
    border-radius: 0;
}

/* A horizontal hairline between rows, drawn on the cell so it does not
 * appear above the first row of any column. */
.setcell + .setcell
{
    box-shadow: 0 -1px 0 var(--line);
}

.setcell:nth-child(-n+3)
{
    box-shadow: none;
}

.setname
{
    font-size: 13px;
    font-weight: 600;
    line-height: 1.3;
    cursor: help;
    min-width: 0;
}

.setval
{
    justify-self: end;
}

.setval .settext
{
    /* Wide enough for a week number or a URL's beginning, narrow enough
     * that three of these fit a laptop. */
    width: 116px;
    padding: 5px 8px;
    font-size: 13px;
}

.sethelp,
.sethelp-gap
{
    justify-self: end;
    width: 18px;
}

/* The opened sentence spans its own cell, under the row it belongs to. */
.sethelp[open]
{
    display: contents;
}

.sethelp[open] .sethelp-mark
{
    justify-self: end;
}

.sethelp-text
{
    grid-column: 1 / -1;
    margin: 8px 0 0;
    font-size: 12px;
    max-width: none;
}

/* Two columns on a laptop-narrow window, one on a phone - the same list,
 * folded rather than squeezed. */
@media (max-width: 1100px)
{
    .setgrid { grid-template-columns: repeat(2, 1fr); }
    .setcell:nth-child(-n+3) { box-shadow: 0 -1px 0 var(--line); }
    .setcell:nth-child(-n+2) { box-shadow: none; }
}

@media (max-width: 700px)
{
    .setgrid { grid-template-columns: 1fr; }
    .setcell:nth-child(-n+2) { box-shadow: 0 -1px 0 var(--line); }
    .setcell:first-child     { box-shadow: none; }
    .setval .settext         { width: 100%; }
}

/* ------------------------------------------------------------------ */
/* the settings row is a flex line, not a grid                         */
/*                                                                     */
/* As a three-column grid the "?" kept landing on a second row: a      */
/* <details> is a block, and once anything in the row wrapped the      */
/* auto-placement put it below the field instead of beside it - which  */
/* is exactly the layout this round set out to remove.                 */
/*                                                                     */
/* A flex line says the intent directly: name takes the slack, field   */
/* and mark sit at the end, and the opened sentence claims a full row   */
/* by asking for 100% of the basis. No auto-placement to argue with.   */
/* ------------------------------------------------------------------ */

.setcell
{
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px 10px;
    padding: 8px 12px;
}

.setcell .setname
{
    flex: 1 1 auto;
    min-width: 0;
}

.setcell .setval
{
    flex: 0 0 auto;
    margin-left: auto;
}

.setcell .sethelp,
.setcell .sethelp-gap
{
    flex: 0 0 18px;
}

/* An open <details> is display:contents under the older rule, which would
 * take the summary out of the flex line. Here it stays a flex item and
 * only its text wraps to its own line. */
.setcell .sethelp[open]
{
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-end;
}

.setcell .sethelp[open] .sethelp-text
{
    flex: 1 0 100%;
    margin: 6px 0 0;
    text-align: left;
}

/* A long name should wrap ONTO A SECOND LINE, not push the "?" onto one.
 *
 * `flex: 1 1 auto` gives the label a content-sized basis, so "Booking types
 * marked important" claimed its full width and the mark had nowhere to go.
 * Basis 0 makes the label the thing that yields, which is right: a name can
 * take two lines without anybody noticing, and a row that breaks apart is
 * the fault this round exists to fix. */
.setcell .setname
{
    flex: 1 1 0;
}

/* And a little narrower, so three columns fit a laptop without any of them
 * having to wrap in the first place. */
.setcell .setval .settext
{
    width: 104px;
}

/* THE OPENED SENTENCE NEEDS THE WHOLE ROW.
 *
 * As a flex item the <details> was 18px wide - the width of its own mark -
 * so the text opened into an 18px column and read one word per line.
 *
 * `display: contents` dissolves the <details> box and promotes the summary
 * and the paragraph to flex items of the row itself: the mark keeps its
 * 18px place beside the field, and the text asks for a 100% basis and so
 * wraps onto its own line underneath. The element still does the opening
 * and closing; it simply stops being a box in the layout. */
.setcell .sethelp[open]
{
    display: contents;
}

.setcell .sethelp[open] .sethelp-mark
{
    flex: 0 0 18px;
}

.setcell .sethelp[open] .sethelp-text
{
    flex: 1 0 100%;
    margin: 6px 0 0;
    text-align: left;
}

/* ------------------------------------------------------------------ */
/* a setting with three answers instead of two                         */
/*                                                                     */
/* Every other row on this grid ends in one small control at the right */
/* margin. This one is three lines of prose, so it claims the whole    */
/* width under the name rather than trying to sit in that slot - and   */
/* `margin-left: auto` from `.setcell .setval` has to be undone or the */
/* block hangs off the right edge on a narrow screen.                  */
/* ------------------------------------------------------------------ */

.setcell:has(.setchoice) .setval
{
    flex: 1 0 100%;
    margin-left: 0;
}

.setchoice
{
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.setchoice-item
{
    display: flex;
    align-items: flex-start;
    gap: 8px;
    font-size: 13px;
    line-height: 1.35;
    cursor: pointer;
}

/* The dot must not shrink when the sentence beside it wraps. */
.setchoice-item input
{
    flex: 0 0 auto;
    margin: 2px 0 0;
}

/* ------------------------------------------------------------------ */
/* settings tabs                                                       */
/* ------------------------------------------------------------------ */

.settabs
{
    display: flex;
    gap: 4px;
    margin: 2px 0 14px;
    border-bottom: 1px solid var(--line);
}

.settab
{
    padding: 8px 14px;
    font-size: 13px;
    font-weight: 600;
    color: var(--ink-soft);
    border-bottom: 2px solid transparent;
    margin-bottom: -1px;
    border-radius: var(--radius-s) var(--radius-s) 0 0;
}

.settab:hover
{
    color: var(--ink);
    background: color-mix(in srgb, var(--brand) 7%, transparent);
}

.settab.is-on
{
    color: var(--ink);
    border-bottom-color: var(--brand);
}

/* ------------------------------------------------------------------ */
/* the letterhead form, which IS the preview                           */
/*                                                                     */
/* Seventeen boxes named Left_2_Value are unreadable. The same          */
/* seventeen arranged the way the paper is - titles centred, pairs in   */
/* two columns, the caption top right - explain themselves without a    */
/* word of help. So the form is laid out as the page it produces.       */
/* ------------------------------------------------------------------ */

.hdrnote
{
    margin: 0 0 12px;
    color: var(--ink-soft);
    font-size: 12.5px;
}

.hdrnote strong
{
    color: var(--ink);
    font-weight: 600;
}

.hdrsheet
{
    /* Paper: a light surface even in the dark scheme would be a lie about
     * where you are, so it stays a surface - but a framed, centred one, in
     * the proportions of a page. */
    max-width: 720px;
    margin: 0 auto;
    padding: 20px 22px 24px;
    background: var(--surface-2);
    border: 1px solid var(--line);
    border-radius: var(--radius-m);
    position: relative;
}

.hdrtitle
{
    display: block;
    width: 100%;
    margin: 0 auto 6px;
    text-align: center;
    background: transparent;
    border-color: transparent;
}

.hdrtitle:hover,
.hdrtitle:focus-visible
{
    background: var(--surface);
    border-color: var(--line-strong);
}

/* The first line is the faculty's name; it should look like one. */
.hdrtitle-1 { font-size: 16px; font-weight: 700; }
.hdrtitle-2 { font-size: 13.5px; }
.hdrtitle-3 { font-size: 13.5px; font-weight: 600; }

.hdrcaption
{
    display: flex;
    justify-content: flex-end;
    margin: 10px 0 2px;
}

.hdrcap
{
    width: 260px;
    text-align: right;
    font-size: 12.5px;
    background: transparent;
    border-color: transparent;
}

.hdrcap:hover,
.hdrcap:focus-visible
{
    background: var(--surface);
    border-color: var(--line-strong);
}

.hdrpairs
{
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 6px 28px;
    margin-top: 8px;
    padding-top: 12px;
    border-top: 1px solid var(--line);
}

.hdrpair
{
    display: flex;
    align-items: center;
    gap: 6px;
    margin-bottom: 4px;
}

.hdrname
{
    flex: 0 0 40%;
    font-weight: 600;
    font-size: 12.5px;
    background: transparent;
    border-color: transparent;
}

.hdrvalue
{
    flex: 1 1 auto;
    min-width: 0;
    font-size: 12.5px;
    background: transparent;
    border-color: transparent;
}

.hdrname:hover,  .hdrname:focus-visible,
.hdrvalue:hover, .hdrvalue:focus-visible
{
    background: var(--surface);
    border-color: var(--line-strong);
}

@media (max-width: 700px)
{
    .hdrpairs { grid-template-columns: 1fr; }
    .hdrcap   { width: 100%; }
}

/* ------------------------------------------------------------------ */
/* the letterhead pairs were escaping the paper                        */
/*                                                                     */
/* `grid-template-columns: 1fr 1fr` means minmax(AUTO, 1fr), and auto   */
/* refuses to go below the content's min-content width. A text input's  */
/* min-content width is its default size - about twenty characters -    */
/* so the two columns grew past the sheet and the right-hand boxes hung */
/* over its border.                                                     */
/*                                                                     */
/* minmax(0, 1fr) is the fix, and it has to be paired with min-width: 0 */
/* down the chain: a flex item will not shrink below its own            */
/* min-content either, so the grid cell yields and the input inside it  */
/* does not, and the overflow simply moves one level in.                */
/* ------------------------------------------------------------------ */

.hdrpairs
{
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
}

.hdrpair
{
    min-width: 0;
}

.hdrname
{
    flex: 0 0 38%;
    min-width: 0;
}

.hdrvalue
{
    /* Basis 0 rather than auto: with auto the input's default width is the
     * starting point and the box only ever gets bigger. */
    flex: 1 1 0;
    min-width: 0;
}

/* Belt and braces: nothing inside the sheet may draw outside it. If a
 * future field is added and forgets the above, it is clipped rather than
 * hanging over the border - which reads as a bug, where a slightly narrow
 * box does not. */
.hdrsheet
{
    overflow: hidden;
}


/* ------------------------------------------------------------------ */
/* the footer, doing two jobs instead of none                          */
/*                                                                     */
/* It was "Wise Timetable" and "Code: demo" in grey, which is a footer  */
/* that exists rather than one that does anything. It now carries our   */
/* mark on the left and, on the right, the school code said the way it  */
/* is actually used - the word you type into the phone app - with the   */
/* two store badges under it. See templates/_footbar.php.               */
/* ------------------------------------------------------------------ */

.foot
{
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 16px;
    flex-wrap: wrap;
    padding: 14px 18px 18px;
}

.foot-brand
{
    display: inline-flex;
    align-items: center;
    gap: 8px;
    text-decoration: none;
    color: var(--ink-soft);
}

.foot-brand:hover
{
    color: var(--ink);
}

.foot-apps
{
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 8px;
    min-width: 0;
}

.foot-code
{
    margin: 0;
    color: var(--ink-soft);
    font-size: 12.5px;
    text-align: right;
}

.foot-code b
{
    color: var(--ink);
}

.foot-badges
{
    display: flex;
    gap: 8px;
    flex-wrap: wrap;
    justify-content: flex-end;
}

/* The badge: the stores' OWN artwork, unaltered.
 *
 * These used to be drawn here - the stores' shape and proportions with our
 * glyph and lettering - which was right while the listings were in review
 * and wrong the moment they went live. Apple and Google both publish the
 * files and both forbid redrawing, recolouring or re-lettering them.
 *
 * So this rule adds NOTHING to the mark. The artwork already carries its own
 * rounded rectangle, background, border and internal padding; a box drawn
 * around a box is the one way this quietly goes wrong. No background, no
 * border, no padding, no filter, no transform.
 *
 * Fixed HEIGHT and width:auto, because two badges of different heights side
 * by side is the first thing anybody notices - and equal height is also what
 * Apple's guidelines mean by its badge being no smaller than another store's.
 * The aspect ratios differ (Apple 2.99:1, Google 3.37:1) so equalising width
 * instead would make one of them taller. */
.storebadge
{
    display: inline-flex;
    align-items: center;
    text-decoration: none;
    border-radius: 7px;   /* only so the focus ring follows the artwork */
}

.storebadge-img
{
    display: block;       /* kills the descender gap under an inline image */
    height: 40px;
    width: auto;
}

/* The store's name in words. Hidden everywhere; the room board turns it on
 * and turns the artwork off. See the note in templates/_footbar.php. */
.storebadge-word
{
    display: none;
    font-size: 12.5px;
    font-weight: 600;
    color: var(--ink-soft);
    white-space: nowrap;
}

.storebadge:hover .storebadge-word,
.storebadge:focus-visible .storebadge-word
{
    color: var(--ink);
}

/* Feedback goes AROUND the badge, never on it: no dimming, no scaling, no
 * colour shift, because every one of those is an alteration of the mark. */
.storebadge:hover,
.storebadge:focus-visible
{
    outline: 2px solid var(--brand);
    outline-offset: 3px;
}

/* Not through review yet: shown, so people know the app is coming, and
 * plainly not a link, so nobody clicks into nothing.
 *
 * Dimming ALONE is ambiguous - a faded badge reads as a picture that failed
 * to load - so the waiting badge SAYS the word, in the reader's own language.
 * It says it in a pill beside the mark rather than by fading the mark,
 * because the artwork is not ours to alter and because the difference between
 * a listing that is coming and one that is live has to be a thing the footer
 * states, not a thing the reader infers from opacity.
 *
 * Unused since 19 Aug 2026, when iOS went live. Kept because the next product
 * to ship a mobile app inherits it. */
.storebadge.is-soon
{
    gap: 6px;
    cursor: default;
}

.badge-soon
{
    padding: 2px 6px;
    border-radius: 999px;
    border: 1px solid var(--line-strong);
    color: var(--ink-soft);
    font-size: 9px;
    font-weight: 600;
    letter-spacing: .06em;
    text-transform: uppercase;
    white-space: nowrap;
}

/* .badge-glyph / .badge-text / .badge-small / .badge-big were the parts of
 * the hand-drawn badge. They went with it on 19 Aug 2026 - the artwork now
 * arrives as one image and has no parts for us to lay out. */

@media (max-width: 560px)
{
    .foot        { flex-direction: column; align-items: flex-start; }
    .foot-apps   { align-items: flex-start; }
    .foot-code   { text-align: left; }
    .foot-badges { justify-content: flex-start; }
}

/* ------------------------------------------------------------------ */
/* the flash: said once, then gone                                     */
/*                                                                     */
/* A screen that closes itself has to say why, and a notice in the      */
/* layout would push the page down for a sentence finished with in four */
/* seconds. So: over everything, and it removes itself with an          */
/* animation rather than a timer - identical behaviour with scripting   */
/* switched off.                                                       */
/* ------------------------------------------------------------------ */

.flash
{
    position: fixed;
    left: 50%;
    bottom: 28px;
    z-index: 60;
    margin: 0;
    padding: 10px 18px;
    border-radius: 999px;
    background: var(--surface-2);
    border: 1px solid var(--line-strong);
    color: var(--ink);
    font-size: 13.5px;
    box-shadow: 0 8px 24px rgba(0, 0, 0, .28);
    animation: flash-life 4s ease forwards;
    pointer-events: none;
}

@keyframes flash-life
{
    0%   { opacity: 0; transform: translate(-50%, 8px); }
    8%   { opacity: 1; transform: translate(-50%, 0); }
    80%  { opacity: 1; transform: translate(-50%, 0); }
    100% { opacity: 0; transform: translate(-50%, 8px); visibility: hidden; }
}

/* Somebody who has asked for less movement gets the sentence without the
 * slide, and it still leaves. */
@media (prefers-reduced-motion: reduce)
{
    .flash { animation: flash-fade 4s linear forwards; transform: translateX(-50%); }
}

@keyframes flash-fade
{
    0%, 85% { opacity: 1; }
    100%    { opacity: 0; visibility: hidden; }
}

/* ------------------------------------------------------------------ */
/* letterhead, second pass                                             */
/*                                                                     */
/* Two things read wrong on the first: the caption looked like a fourth */
/* title whose text had fallen to the right, and the pair labels were   */
/* clipping words schools actually use ("Effective date", "Advisor").   */
/* A label you cannot read is not a label.                              */
/* ------------------------------------------------------------------ */

.hdrcaption
{
    margin: 12px 0 0;
}

.hdrcap
{
    /* Narrower than the sheet and hard against the right edge: that is
     * where it prints, and at this width it reads as a caption rather than
     * as a title line gone wrong. */
    width: min(300px, 100%);
    text-align: right;
    color: var(--ink-soft);
    font-size: 12px;
}

.hdrpairs
{
    gap: 6px 20px;
}

.hdrname
{
    /* 38% clipped "Effective date" at this size; the value side had room
     * to spare, so the label takes it. */
    flex: 0 0 46%;
}

/* The switch above the sheet: whether any of this gets printed at all. */
.hdruse
{
    display: flex;
    align-items: center;
    gap: 8px;
    margin: 0 0 12px;
    font-size: 13px;
    color: var(--ink);
    cursor: pointer;
}

.hdruse input
{
    width: 16px;
    height: 16px;
    accent-color: var(--brand);
}

/* ------------------------------------------------------------------ */
/* which kinds of teaching a school publishes - settings, third tab    */
/*                                                                     */
/* A short list of ticks. It is deliberately not the settings grid:    */
/* these are not settings with names and explanations, they are the    */
/* school's own course types, and the only thing to know about each is */
/* whether it is on.                                                   */
/* ------------------------------------------------------------------ */

.typelist
{
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
    gap: 4px 18px;
    margin: 14px 0 18px;
    max-width: 720px;
}

.typeitem
{
    display: flex;
    align-items: center;
    gap: 9px;
    padding: 7px 10px;
    border: 1px solid transparent;
    border-radius: var(--radius-m);
    cursor: pointer;
}

.typeitem:hover
{
    background: var(--surface-2);
    border-color: var(--line);
}

.typeitem input
{
    width: 16px;
    height: 16px;
    accent-color: var(--brand);
    flex: none;
}

.typename
{
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}


/* ------------------------------------------------------------------ */
/* on paper, the card's last line was being guillotined               */
/*                                                                     */
/* A one-hour card is as tall as an hour of the ruler, and on paper    */
/* the ruler is compressed to fit the sheet - about a pixel a minute   */
/* against 1.3 on screen. The text was not compressed with it: title   */
/* over two lines, then the people, then the times came to some 72 px  */
/* of content in a 60 px box, so `overflow: hidden` did what it is     */
/* there for and cut the times in half along the border. Which reads   */
/* as a rendering fault rather than as a card that is too small - and  */
/* the times are the one thing on a printed timetable nobody can work  */
/* out from context.                                                   */
/*                                                                     */
/* The fix is metrics, and only on paper: a smaller title, smaller     */
/* detail lines and less padding bring four lines to about 57 px -     */
/* inside the hour, with room to spare. Nothing becomes hard to read;   */
/* a printed sheet is read from arm's length.                           */
/*                                                                     */
/* NOT FLEXBOX. The obvious move is a column flex with the time held    */
/* to the bottom by `margin-top: auto`, so the hours survive whatever   */
/* else is lost. It cannot be done here: `.card-badges` is a FLOAT, and */
/* a float inside a flex container stops being a float and becomes an   */
/* item on a line of its own - adding height to exactly the cards that  */
/* are already tightest.                                                */
/* ------------------------------------------------------------------ */

@media print
{
    .card-body
    {
        padding: 3px 6px;
    }

    .card-title
    {
        font-size: 11px;
        line-height: 1.18;
    }

    .card-sub, .card-note, .card-time
    {
        font-size: 9.5px;
        line-height: 1.2;
    }
}


/* ------------------------------------------------------------------ */
/* the quick link, and the way to forget                               */
/*                                                                     */
/* Two small controls under the filters: the address of the view on    */
/* screen, to bookmark or send to a class, and a way to clear the      */
/* selection AND the memory of it in one press. Both only appear when  */
/* something is actually selected - see timetable.php.                 */
/* ------------------------------------------------------------------ */

/*
 * ON THE SAME LINE AS THE MATCH CONTROL, and level with it.
 *
 * `margin-top: 8px` used to sit here, from when this was the last thing on a
 * row of its own. The filter bar wraps and has a 14px gap, so that gap
 * already separates the rows when it does wrap - and when it does NOT, the
 * margin pushed these two buttons 8px below the pill they share a line with.
 * Together with the pill being 4px taller (see .axis) that is the whole of
 * "nisu iste visine i nisu nekako lijepo poravnane", 7 Sep 2026.
 *
 * `center` and not `flex-start`, because these are all one height now: if a
 * later control here is taller, centring is what keeps the row readable.
 */
.viewtools
{
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
}

.quicklink
{
    position: relative;
}

/* The marker triangle would make the summary look like a tree node; it is
 * a button, and it should look like the button beside it. */
.quicklink > summary
{
    list-style: none;
    cursor: pointer;
}

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

.quicklink-body
{
    position: absolute;
    z-index: 30;
    top: calc(100% + 6px);

    /* Right edge to right edge: anchored to the LEFT it ran off the
     * window, and what went over the edge was the address itself. */
    right: 0;
    left: auto;

    width: min(460px, calc(100vw - 32px));
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
    padding: 12px;
    background: var(--surface);
    border: 1px solid var(--line-strong);
    border-radius: var(--radius-m);
    box-shadow: var(--shadow-2);
}

.quicklink-url
{
    flex: 1 1 240px;
    min-width: 0;
    font-size: 12.5px;
}

.quicklink-hint
{
    flex: 1 1 100%;
    margin: 0;
    color: var(--ink-soft);
    font-size: 12px;
}

@media print
{
    .viewtools { display: none; }
}


/* ------------------------------------------------------------------ */
/* the letterhead's note, and the "?" beside it                        */
/*                                                                     */
/* Its OWN styles, not the settings grid's `.sethelp`: that one opens  */
/* with `display: contents` so the grid can take its text, which       */
/* outside a grid means no box at all - and the form collapsed into a  */
/* column one word wide. The mark also lives beside the note rather    */
/* than inside it, because a <details> inside a <p> is not allowed and */
/* the browser un-nests it, taking the rest of the form with it.       */
/* ------------------------------------------------------------------ */

.hdrnoterow
{
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 8px;
    margin: 0 0 12px;
}

.hdrnoterow .hdrnote
{
    margin: 0;
}

.hdrhelp
{
    /* Not a flex item that stretches: just the mark, on the note's line. */
    flex: none;
}

.hdrhelp-mark
{
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 18px;
    height: 18px;
    border-radius: 999px;
    background: var(--surface-2);
    border: 1px solid var(--line-strong);
    color: var(--ink-soft);
    font-size: 11px;
    font-weight: 700;
    cursor: pointer;
    list-style: none;
}

.hdrhelp-mark::-webkit-details-marker
{
    display: none;
}

.hdrhelp[open] .hdrhelp-mark
{
    background: var(--brand);
    border-color: var(--brand);

    /* White, not --brand-ink: that token is a darker SHADE of brand, for
     * text ON the page, and using it here put dark blue on blue. */
    color: #fff;
}

/* The explanation takes the whole row when it opens, under both. */
.hdrhelp-text
{
    flex-basis: 100%;
    max-width: 760px;
    margin: 8px 0 0;
    color: var(--ink-soft);
    font-size: 12.5px;
    line-height: 1.5;
}


/* Open, the "?" takes a row of its own so the explanation has the width to
 * be read in. Closed it is 18 pixels beside the note, which is where
 * somebody looks for it. The text is inside the <details>, so the DETAILS
 * has to grow - giving the paragraph `width: 100%` would only make it as
 * wide as the mark. */
.hdrhelp[open]
{
    flex: 1 1 100%;
}


/* ------------------------------------------------------------------ */
/* Look and feel - the school's own logo                               */
/*                                                                     */
/* THE PREVIEW IS THE SAME BOX THE HEADER GIVES IT. A crest that is    */
/* magnificent at 400 pixels can be an illegible smudge at the 28 the  */
/* corner allows, and the only way to know is to see it at 28. So the  */
/* frame here is that corner, beside the file's real dimensions: the   */
/* number answers "is it sharp enough", the picture answers "does it   */
/* read".                                                              */
/* ------------------------------------------------------------------ */

.lookrow
{
    display: flex;
    flex-wrap: wrap;
    gap: 28px;
    align-items: flex-start;
    margin: 14px 0 18px;
}

.lookpreview
{
    flex: 0 0 auto;
}

/* Between the logo and the appearance settings that now sit under it.
 * A rule and not a heading: they are the same subject - what this school
 * looks like - and a heading would announce a change of topic that is not
 * happening. */
.lookrule
{
    height: 0;
    margin: 22px 0 18px;
    border: 0;
    border-top: 1px solid var(--line);
}

.lookpreview-label,
.lookmeta
{
    display: block;
    color: var(--ink-soft);
    font-size: 12px;
}

.lookmeta
{
    margin: 6px 0 0;
}

.lookframe
{
    display: flex;
    align-items: center;
    gap: 8px;
    min-width: 260px;
    min-height: 56px;
    margin-top: 6px;
    padding: 10px 14px;
    background: var(--surface-2);
    border: 1px solid var(--line);
    border-radius: var(--radius-m);
}

/* The header's own ceiling for the mark, so what is shown here is what
 * will be drawn there. */
.lookframe-img
{
    max-height: 32px;
    max-width: 220px;
}

.lookpick
{
    flex: 1 1 320px;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.lookadvice
{
    max-width: 620px;
    margin: 0;
    color: var(--ink-soft);
    font-size: 12.5px;
    line-height: 1.5;
}


/* ------------------------------------------------------------------ */
/* a top bar that says one word should not reserve half the row        */
/*                                                                     */
/* `.weeknav` asks for `flex: 1 1 460px`, and on the TIMETABLE that is */
/* exactly right: five icon buttons, a date field and a week chip come */
/* to about 440 px, and a smaller basis drops the chip onto a line of  */
/* its own.                                                            */
/*                                                                     */
/* On Settings, Reports, Rooms and the rest that same nav holds one    */
/* label - "Settings" - and the 460 px basis then does two visible     */
/* wrongs at once: it pushes the button rail onto a second line while  */
/* the row is mostly empty, and, because the nav is allowed to shrink, */
/* the label truncates to nothing long before the window is narrow.    */
/*                                                                     */
/* `:has()` asks the question directly - is this nav just a label? -   */
/* so no template has to remember to add a modifier class, and a       */
/* browser too old to understand it simply keeps today's behaviour.    */
/* ------------------------------------------------------------------ */

.topbar .weeknav:has(> .weeklabel:only-child)
{
    /* Its own width and no more, so the rail keeps the rest of the row. */
    flex: 0 1 auto;
}

.topbar .weeknav:has(> .weeklabel:only-child) .weeklabel
{
    /* And the label is a heading, not a value that may be abbreviated:
     * nothing else is competing for this space now. */
    margin-left: 0;
    overflow: visible;
    text-overflow: clip;
}


/* ------------------------------------------------------------------ */
/* THE "?" IN A CIRCLE                                                 */
/*                                                                     */
/* Third place this appeared - the settings grid, the letterhead, and  */
/* now the booking form - so it is one class rather than three that    */
/* had started to differ in ways nobody chose. See templates/_qhelp.   */
/*                                                                     */
/* Closed it is eighteen pixels beside the control it explains, which  */
/* is where somebody looks for it. Open it takes the whole row: an     */
/* explanation worth writing is wider than a button, and squeezing it  */
/* into a column would make it a thing people give up reading.         */
/* ------------------------------------------------------------------ */

.qhelp
{
    /* Not a flex item that stretches - just the mark, on the line. */
    flex: none;
    align-self: center;
}

.qhelp-mark
{
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 18px;
    height: 18px;
    border-radius: 999px;
    background: var(--surface-2);
    border: 1px solid var(--line-strong);
    color: var(--ink-soft);
    font-size: 11px;
    font-weight: 700;
    cursor: pointer;
    list-style: none;
}

.qhelp-mark::-webkit-details-marker
{
    display: none;
}

.qhelp[open] .qhelp-mark
{
    background: var(--brand);
    border-color: var(--brand);

    /* White, not --brand-ink: that token is a darker SHADE of brand, for
     * text ON the page, and using it here put dark blue on blue. */
    color: #fff;
}

/* Open, the DETAILS itself has to grow: giving the text `width: 100%`
 * would only make it as wide as the eighteen-pixel mark. */
.qhelp[open]
{
    flex: 1 1 100%;
}

.qhelp-text
{
    max-width: 760px;
    margin: 10px 0 0;
    color: var(--ink-soft);
    font-size: 12.5px;
    line-height: 1.55;
}

.qhelp-text p
{
    margin: 0 0 8px;
}

.qhelp-text p:last-child
{
    margin-bottom: 0;
}

/* A control and its "?" on one line, with the explanation dropping below
 * both when it opens.
 *
 * The mark is BESIDE the label, never inside it: a <details> nested in a
 * <label> makes the whole explanation a click target for the field, so
 * opening the help would put the cursor in the box and scroll the page. */
.field-help
{
    display: flex;
    align-items: flex-start;
    flex-wrap: wrap;
    gap: 8px;
}

.field-help > .field,
.field-help > .check
{
    flex: 1 1 auto;
    min-width: 0;
}

/* The mark lines up with the INPUT, not with the label above it - the
 * field is a stacked label + box, so centring it vertically would leave
 * the circle floating between the two. */
.field-help > .qhelp
{
    /* Bottom-aligned to the field, then given the control's own height
     * to centre in - so the mark sits on the middle of the input or the
     * select, not on the bottom of the label above it. */
    align-self: flex-end;
    height: var(--control-h);
    display: flex;
    align-items: center;
}

.field-help > .check + .qhelp
{
    align-self: center;
    margin-bottom: 0;
}

.field-help .qhelp-text
{
    flex-basis: 100%;
}

/* Open, the mark's row stops being a row: the explanation below it needs
 * the height, and holding the DETAILS to one control's height would clip
 * it to a sliver. */
.field-help > .qhelp[open]
{
    height: auto;
    display: block;
}

/* A row of buttons keeps its order when a help opens.
 *
 * The open details is a full-width flex item, so in the booking form it
 * landed BETWEEN Check availability and Book and pushed Book four
 * paragraphs down the page. Somebody who opens an explanation should not
 * find the button they were reaching for has moved; `order` sends the
 * details to the end of the wrapped row instead. */
.form-actions > .qhelp[open],
.setform-actions > .qhelp[open]
{
    order: 99;
}


/* ------------------------------------------------------------------ */
/* Signage displays - the settings tab                                 */
/*                                                                     */
/* THE ADDRESS IS THE DELIVERABLE. Whoever fills this in is going to    */
/* walk to a television with a remote control and type what is on this  */
/* screen, so the address is monospaced, full width and selectable -    */
/* every character has to be distinguishable at arm's length, and       */
/* l/1/I and 0/O in a proportional face are not.                        */
/*                                                                     */
/* Each display is its own form. The rule between them is what stops    */
/* eight screens reading as one long questionnaire.                     */
/* ------------------------------------------------------------------ */

.signageform
{
    padding-bottom: 8px;
    border-bottom: 1px solid var(--line);
    margin-bottom: 22px;
}

.signageform:last-of-type
{
    border-bottom: 0;
}

.signage-title
{
    margin: 18px 0 10px;
    font-size: 15px;
}

.signage-url
{
    margin: 0 0 14px;
}

/* Monospaced and wide: this is read aloud and typed into a remote
 * control, and a proportional face makes l, 1 and I the same shape. */
.signage-urlfield
{
    width: 100%;
    max-width: 560px;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 12.5px;
    background: var(--surface-2);
}

/* The room ticks. A school with two hundred rooms would otherwise be a
 * column two hundred lines long between the name and the Save button, so
 * the list is columns and its own height is capped - the fieldset
 * scrolls, the page does not grow. */
.signage-rooms
{
    margin: 0 0 14px;
    padding: 10px 14px;
    border: 1px solid var(--line);
    border-radius: var(--radius-m);
}

.signage-rooms > legend
{
    padding: 0 6px;
    font-size: 12px;
    color: var(--ink-soft);
}

.signage-room
{
    display: block;
    padding: 2px 0;
    font-size: 13px;
    break-inside: avoid;
}

.signage-rooms:has(.signage-room)
{
    max-height: 260px;
    overflow-y: auto;
}

@media (min-width: 700px)
{
    .signage-rooms
    {
        columns: 3;
        column-gap: 24px;
    }

    .signage-rooms > legend,
    .signage-rooms > .signage-hint
    {
        column-span: all;
    }
}

.signage-hint
{
    display: inline-block;
    margin: 0 0 6px;
    color: var(--ink-soft);
    font-size: 12px;
}

/* What the display is showing now, beside the field that would replace
 * it. Small on purpose: it answers "which picture is this", not "is it
 * sharp" - the screen it is going to is two metres wide. */
.signage-shot
{
    display: inline-flex;
    align-items: center;
    gap: 8px;
    margin: 0 10px 0 0;
    padding: 4px 8px;
    background: var(--surface-2);
    border: 1px solid var(--line);
    border-radius: var(--radius-m);
    vertical-align: middle;
}

.signage-shot img
{
    max-height: 40px;
    max-width: 120px;
    display: block;
}

.signage-shot .signage-hint
{
    margin: 0;
}

/* What the PDF conversion is saying. Its own line: it appears and
 * disappears, and sharing a line with the hint below the field would make
 * that hint jump sideways every time somebody picks a file. */
.signage-converting
{
    display: block;
    margin-top: 4px;
}


/* ------------------------------------------------------------------ */
/* Signage tab, second pass: a display's settings in two columns       */
/*                                                                     */
/* One paragraph per field pushed the room list, the pictures and the  */
/* Save button off the bottom of the screen - somebody configuring a   */
/* television scrolled past everything that mattered to reach the      */
/* button. Grid, not float: the labels line up without being told to.  */
/* ------------------------------------------------------------------ */

.signage-fields
{
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
    gap: 2px 28px;
    align-items: start;
    margin: 0 0 12px;
}

.signage-fields > p
{
    display: grid;
    grid-template-columns: minmax(110px, max-content) 1fr;
    align-items: center;
    gap: 3px 10px;
    margin: 3px 0;
}

.signage-fields > p > label
{
    color: var(--ink-soft);
    font-size: 13px;
}

/* Anything after the field - a thumbnail, a hint, the conversion's line -
 * goes under it rather than beside it, so a long sentence cannot squeeze
 * the field itself into a sliver. */
.signage-fields > p > .signage-hint,
.signage-fields > p > .signage-shot,
.signage-fields > p > .signage-defaultnote,
.signage-fields > p > .signage-converting
{
    grid-column: 2;
}

/* The address spans both columns: it is one long string that gets read
 * aloud and typed in, and it must not be broken across a narrow box. */
.signage-fields > p.signage-url
{
    grid-column: 1 / -1;
    margin: 3px 0;
}

.signage-wide
{
    display: block;
    margin: 0 0 12px;
}

/* The fixed part of the address printed, and only the last part editable -
 * on one line and in one face, so what is read here is what gets typed
 * into the television. */
.signage-urlline
{
    display: flex;
    align-items: stretch;
    max-width: 560px;
}

.signage-urlfixed
{
    display: flex;
    align-items: center;
    padding: 0 2px 0 8px;
    background: var(--surface-2);
    border: 1px solid var(--line);
    border-right: 0;
    border-radius: var(--radius-m) 0 0 var(--radius-m);
    color: var(--ink-soft);
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 12.5px;
    white-space: nowrap;
}

.signage-urltail
{
    flex: 1 1 auto;
    min-width: 0;
    border-radius: 0 var(--radius-m) var(--radius-m) 0;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 12.5px;
}

/* All / None. A school with two hundred rooms is not going to click two
 * hundred boxes, and a screen that covers one floor is quicker to reach by
 * clearing the lot and ticking eight. */
.signage-roomtools
{
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 16px;
    margin: 0 0 6px;
}

.signage-pick
{
    display: inline-flex;
    gap: 6px;
}

.signage-pick button
{
    padding: 2px 10px;
    font: inherit;
    font-size: 12px;
    color: var(--ink-soft);
    background: var(--surface-2);
    border: 1px solid var(--line);
    border-radius: var(--radius-m);
    cursor: pointer;
}

.signage-pick button:hover
{
    color: var(--ink);
    border-color: var(--line-strong);
}

/* Tighter room ticks - the list is now a means, not the subject. */
.signage-rooms
{
    padding: 8px 12px;
}

.signage-room
{
    padding: 1px 0;
    font-size: 12.5px;
}

@media (min-width: 700px)
{
    .signage-rooms
    {
        columns: 4;
    }

    .signage-rooms > .signage-roomtools
    {
        column-span: all;
    }
}


/* ------------------------------------------------------------------ */
/* NO VIEW TRANSITION - it was here for half a day, and the reason it  */
/* left matters more than the reason it came.                          */
/*                                                                     */
/* A navigation:auto view transition held the old page's image across  */
/* a navigation and crossfaded into the new one, to remove the blank   */
/* frame between documents. It also brought two timing constants this  */
/* project had just finished removing from the busy indicator: our own */
/* fade duration, and Chrome's undocumented heuristic for skipping the */
/* transition (measured: skipped whenever the next click came inside   */
/* ~1.5 s). The screen behaved differently at different click speeds - */
/* a guard measured in milliseconds, in a dynamic system, again. The   */
/* reporter called it, and he was right.                               */
/*                                                                     */
/* And the blink it was fighting was never the navigation blank: a     */
/* browser EXTENSION was injecting a 14 kB stylesheet and a div into   */
/* every page ~130 ms after first paint, restyling the whole document. */
/* Invisible to layout-shift instruments (same geometry), and it       */
/* travels with the browser to every server - which is why it survived */
/* every fix. The mutation probe that found it lives in                */
/* tools/dev-router.php.                                               */
/* ------------------------------------------------------------------ */


/* ------------------------------------------------------------------ */
/* Signage tab, third pass: one display at a time                      */
/*                                                                     */
/* The strip at the top holds the two things that are not about any    */
/* one display: which one to edit, and making a new one. Everything    */
/* else on the tab belongs to the chosen display alone.                */
/* ------------------------------------------------------------------ */

.signage-topbar
{
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 12px 24px;
    margin: 0 0 16px;
    padding: 10px 14px;
    background: var(--surface-2);
    border: 1px solid var(--line);
    border-radius: var(--radius-m);
}

.signage-pickform
{
    display: inline-flex;
    align-items: center;
    gap: 10px;
}

.signage-pickform label
{
    color: var(--ink-soft);
    font-size: 13px;
}

/* The picker is the main control of the whole tab - give it room. */
.signage-pickform select
{
    min-width: 220px;
}

.signage-addform
{
    display: inline-flex;
    align-items: center;
    gap: 8px;
}

.signage-addform input[type=text]
{
    width: 200px;
}


/* ------------------------------------------------------------------ */
/* Signage tab, fourth pass - the reporter's punch list                */
/* ------------------------------------------------------------------ */

/*
 * THE NOTICES, properly dressed on every page.
 *
 * .notice's box (padding, border, radius) and the green of .notice-ok
 * lived only in booking.css - so "Settings saved" on the settings screen
 * rendered as a floating unstyled line of text. The base belongs in the
 * stylesheet every page loads.
 */
.notice
{
    display: block;
    margin: 0 0 14px;
    padding: 10px 14px;
    border: 1px solid var(--line-strong);
    border-radius: var(--radius-m);
    background: var(--surface-2);
    color: var(--ink-soft);
    font-size: 13.5px;
}

.notice-ok
{
    color: var(--ok);
    background: color-mix(in srgb, var(--ok) 10%, var(--surface));
    border-color: color-mix(in srgb, var(--ok) 34%, var(--line));
}

.notice-error
{
    color: var(--danger);
    background: color-mix(in srgb, var(--danger) 10%, var(--surface));
    border-color: color-mix(in srgb, var(--danger) 34%, var(--line));
}

/*
 * FILE INPUTS IN THE HOUSE STYLE. The browser's default button is a grey
 * island on a dark form, and the filename was ellipsised by a too-narrow
 * box ("No fil…osen"). The pseudo-element is standard and inert where
 * unsupported.
 */
.signageform input[type=file]
{
    min-width: 280px;
    color: var(--ink-soft);
    font-size: 12.5px;
}

.signageform input[type=file]::file-selector-button
{
    margin-right: 10px;
    padding: 5px 12px;
    font: inherit;
    font-size: 12.5px;
    color: var(--ink);
    background: var(--surface-2);
    border: 1px solid var(--line-strong);
    border-radius: var(--radius-m);
    cursor: pointer;
}

.signageform input[type=file]::file-selector-button:hover
{
    border-color: var(--ink-soft);
}

/* The top strip's own controls in the same dress as the rest of the page -
 * the default select was a white island. */
.signage-topbar select,
.signage-topbar input[type=text]
{
    padding: 6px 10px;
    font: inherit;
    font-size: 13px;
    color: var(--ink);
    background: var(--surface);
    border: 1px solid var(--line-strong);
    border-radius: var(--radius-m);
}

/* Alignment: every number on the form the same width, so the label column
 * and the boxes make two straight lines instead of a staircase. */
.signage-fields input[type=number]
{
    width: 110px;
}

/* The copy button rides the end of the address line. */
.signage-copy
{
    margin-left: 6px;
    padding: 0 10px;
    font-size: 15px;
    color: var(--ink-soft);
    background: var(--surface-2);
    border: 1px solid var(--line-strong);
    border-radius: var(--radius-m);
    cursor: pointer;
}

.signage-copy:hover { color: var(--ink); }

/* The thumbnail's small remove cross - deleting a picture is a real act
 * and gets a real control, but a quiet one: the picture is the subject. */
.signage-drop
{
    padding: 0 8px;
    font-size: 14px;
    line-height: 1.6;
    color: var(--ink-soft);
    background: transparent;
    border: 1px solid var(--line);
    border-radius: var(--radius-m);
    cursor: pointer;
}

.signage-drop:hover
{
    color: var(--danger);
    border-color: var(--danger);
}

/*
 * REMOVING A DISPLAY ASKS FIRST. The closed control looks like the button
 * it used to be; open, the question and the real submit appear beside it.
 * All of it is a <details> - works with scripting off, and the destructive
 * submit does not exist in the DOM path of a stray click.
 */
.signage-confirm { position: relative; display: inline-block; }

.signage-confirm > summary
{
    list-style: none;
    cursor: pointer;
}

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

.signage-confirm[open] > .signage-confirm-box
{
    position: absolute;
    right: 0;
    bottom: calc(100% + 8px);
    z-index: 5;
    display: flex;
    flex-direction: column;
    gap: 10px;
    width: 300px;
    padding: 12px 14px;
    background: var(--surface-2);
    border: 1px solid var(--line-strong);
    border-radius: var(--radius-m);
    box-shadow: 0 8px 24px rgba(0, 0, 0, .35);
    font-size: 13px;
    color: var(--ink);
}


/* The not-yet-saved preview: dashed, so "what you just picked" cannot be
 * mistaken for "what is on the wall now" standing beside it. Shared by the
 * signage pictures and the Look-and-feel logo. */
.signage-shot-new
{
    border-style: dashed;
    border-color: var(--ink-soft);
}


/* THE CORNER NOBODY UPLOADED.
 *
 * A display with no picture of its own now shows the Wise Timetable
 * wordmark on the wall, so the tab shows that same mark here. Dashed and
 * faded, because it is a fallback rather than a choice this school made,
 * and it carries no remove button - there is nothing to remove, only
 * something to replace, and the field that replaces it is right there. */
.signage-shot.is-default
{
    border-style: dashed;
    opacity: .55;
}

/* And said in words. A greyed thumbnail on its own does not explain why a
 * logo nobody chose is in the corner of a corridor screen; this line does,
 * and it is what keeps the default from being a surprise. */
.signage-defaultnote
{
    display: block;
    margin: 2px 0 0;
    color: var(--ink-soft);
    font-size: 12.5px;
}


/* ------------------------------------------------------------------ */
/* The bell: notices about what is on this screen                      */
/*                                                                     */
/* Shape, position and outside-click all come from the language        */
/* picker, like the menu and the person menu - a fourth kind of        */
/* pop-over in one rail would be a fourth thing to learn for nothing.  */
/* Only two things here are its own: the dot, and the list.            */
/* ------------------------------------------------------------------ */

.noticepick-pop
{
    /* Wider than the menu. A notice is a sentence somebody typed, not a
     * label we chose, and it has to be readable without a scrollbar in the
     * middle of it. Capped against the viewport so a narrow phone still
     * gets a panel that fits on the screen. */
    min-width: 280px;
    max-width: min(380px, calc(100vw - 32px));
}

/*
 * THE DOT. Positioned on the button rather than in the icon, so the bell
 * stays one shape and the mark is unmistakably an overlay - a bell drawn
 * two ways is two icons to recognise.
 *
 * It uses the danger colour and not the brand: the brand is on everything,
 * and the whole job of this dot is to be the one thing in the rail that is
 * not the same as it was a second ago.
 */
.noticepick-btn
{
    position: relative;
}

.noticepick-dot
{
    position: absolute;
    top: 5px;
    right: 6px;
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--danger);

    /* A ring in the button's own background, so the dot reads as separate
     * from the bell underneath it at any size and on any theme. */
    box-shadow: 0 0 0 2px var(--surface);
}

.noticepick-none
{
    margin: 0;
    padding: 2px 8px 6px;
    color: var(--ink-soft);
    font-size: 13px;
}

.noticepick-list
{
    margin: 0;
    padding: 0;
    list-style: none;

    /* Twenty notices would run off the bottom of a laptop. The panel keeps
     * its own scroll rather than growing the page behind it. */
    max-height: 60vh;
    overflow-y: auto;
}

.noticepick-item
{
    padding: 7px 8px;
    border-radius: var(--radius-s);
    font-size: 13px;
    line-height: 1.45;

    /* The text is the school's, typed into the desktop, and it can be one
     * unbroken word as easily as a sentence. */
    overflow-wrap: anywhere;
}

.noticepick-item + .noticepick-item
{
    border-top: 1px solid var(--line);
    border-radius: 0;
}

/* WHAT IT IS ABOUT, on its own line and first. A reader scans these for
 * their own subject and stops reading the moment it is not one. */
.noticepick-subject
{
    display: block;
    color: var(--ink);
}

.noticepick-text
{
    display: block;
    color: var(--ink-soft);
}

/*
 * The three levels, marked with a stripe rather than a word.
 *
 * A label reading "course" would need translating into thirteen languages
 * to say something the subject line already says - "Financial Accounting"
 * is plainly a subject and "BCIT2-1" is plainly a group. The stripe is
 * there so a list of eight notices has some structure at a glance, and
 * carries no information a reader has to decode.
 */
.noticepick-item.is-course   { box-shadow: inset 3px 0 0 var(--brand); }
.noticepick-item.is-group    { box-shadow: inset 3px 0 0 var(--ok); }
/* Amber has no token: the sheet defines brand, ok and danger and nothing
 * between. Written out rather than added to :root, because a token exists to
 * be reused and this is the only place that wants one. */
.noticepick-item.is-lecturer { box-shadow: inset 3px 0 0 #c08a2e; }


/* ------------------------------------------------------------------ */
/* Signage tab, third pass: six boxes instead of one long column       */
/*                                                                     */
/* The tab grew one field at a time and every addition was right on    */
/* the day it was made. Eighteen controls later it was an              */
/* undifferentiated column, and "where do I set how long the poster    */
/* stays up" had no answer except reading all of it.                   */
/*                                                                     */
/* So the fields are grouped by the KIND of decision they are, and     */
/* each group says which kind it is. The heading carries a drawn icon  */
/* - not decoration: six headings in a row are told apart by shape     */
/* faster than they are read, which is the whole job of a signpost.    */
/* ------------------------------------------------------------------ */

.sigbox
{
    margin: 0 0 14px;
    padding: 12px 14px 4px;
    border: 1px solid var(--line);
    border-radius: var(--radius-m);
    background: var(--surface);
}

.sigbox-head
{
    display: flex;
    align-items: center;
    gap: 8px;
    margin: 0 0 10px;
    padding: 0 0 8px;
    border-bottom: 1px solid var(--line);
    color: var(--ink-soft);
    font-size: 12px;
    font-weight: 700;
    letter-spacing: .07em;
    text-transform: uppercase;
}

/* The icon takes the heading's colour, so a box never gains a second
 * accent colour nobody chose. */
.sigbox-icon
{
    flex: none;
    opacity: .8;
}

/* The one box whose heading should be read rather than scanned past: it is
 * the only place on this screen that destroys anything. */
.sigbox-danger .sigbox-head
{
    color: var(--danger);
    border-bottom-color: color-mix(in srgb, var(--danger) 30%, var(--line));
}

.sigbox-danger { border-color: color-mix(in srgb, var(--danger) 25%, var(--line)); }

/* The box already draws the frame the room list used to draw for itself,
 * and two nested borders read as a mistake. */
.sigbox .signage-rooms
{
    margin: 0;
    padding: 0;
    border: 0;
}

/* Our mark, shown rather than offered - there is no field for it because
 * there is no decision. Laid out like a thumbnail so it reads as "this
 * picture", not as an illustration in a paragraph. */
.signage-ours
{
    display: flex;
    align-items: center;
    gap: 10px;
    margin: 6px 0 10px;
    padding: 8px 10px;
    border: 1px dashed var(--line-strong);
    border-radius: var(--radius-m);
    background: var(--surface-2);
}

.signage-ours img
{
    flex: none;
    max-height: 30px;
    max-width: 120px;
    opacity: .75;
}

.signage-ours .signage-hint { margin: 0; }

/* A legend that only a screen reader needs: the visible heading is the
 * box's own, and printing the word twice is how a form starts to look
 * like it was assembled rather than designed. */
.visually-hidden
{
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}


/* Save belongs to the form, not to a section of it. Right-aligned and given
 * air above the danger box, so the eye lands on it on the way down and the
 * red box below reads as a separate decision rather than the next step. */
.signage-save
{
    justify-content: flex-end;
    margin: 0 0 18px;
}


/* THE PICTURES BOX IS ONE COLUMN, and the rest of the tab is two.
 *
 * `.signage-fields` puts two short label-and-field pairs on a line, which is
 * right for a name beside a design and wrong for these: a picture row is a
 * label, a thumbnail, a file button carrying a filename the school chose, and
 * a sentence explaining what the picture is for. Two of those side by side
 * wrap at different points, so the labels stop lining up and the hints run
 * into the neighbouring column - the "untidy" this box was reported as.
 *
 * One picture per row. Wider label track than the two-column grid, because
 * these labels are phrases ("Fullscreen message") rather than words. */
.signage-pics { grid-template-columns: 1fr; }

.signage-pics > p
{
    grid-template-columns: minmax(150px, max-content) minmax(0, 1fr);
    align-items: center;
    gap: 6px 14px;
    padding: 8px 0;
}

.signage-pics > p + p { border-top: 1px solid var(--line); }

/* The label sits on the row's first line rather than floating in the middle
 * of a two-line cell. */
.signage-pics > p > label { align-self: center; }

/* Thumbnail, then the field, then the sentence - each on its own line in the
 * second column, so a long filename cannot push the hint out of the box. */
.signage-pics > p > .signage-shot { justify-self: start; margin: 0; }

.signage-pics > p > .signage-hint { margin: 0; }


/* "HOW IT LOOKS" IS COLUMNS, not rows.
 *
 * Every setting in that box is a SHORT answer with a LONG explanation - a
 * number three characters wide under a sentence three lines deep. The
 * two-column label-and-field grid put two of those side by side and let the
 * hints hang below at different depths, so the row after them started at a
 * different place in each column and the whole box read as a staircase.
 *
 * Turned upright it settles: each setting is one narrow vertical cell -
 * label, control, explanation, in that order, top-aligned - and a hairline
 * between cells says where one ends. Equal widths, so however many settings a
 * design has (the matrix shows four, the flow four different ones) the box is
 * the same shape.
 *
 * auto-fit rather than a fixed count: at 230px a laptop gets four across and
 * a narrow window folds them to two and then one, without a breakpoint. */
.signage-looks
{
    grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
    gap: 0;
    align-items: stretch;
}

.signage-looks > p
{
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 7px;
    margin: 0;
    padding: 2px 18px 12px 0;
}

/* The divider belongs to the cell on the right, and only when there IS one
 * to its left - so the first column in every wrapped row keeps a clean edge. */
.signage-looks > p + p
{
    padding-left: 18px;
    border-left: 1px solid var(--line);
}

/* The label is a heading here rather than a column, so it reads as the name
 * of the cell under it. */
.signage-looks > p > label
{
    color: var(--ink-soft);
    font-size: 13px;
}

/* The hint is the cell's bottom, whatever height it takes. Pushed down so
 * every explanation in the row starts at the same line as its neighbours'
 * only when the controls above are the same height - which they are, being
 * the one control height. */
.signage-looks > p > .signage-hint
{
    margin: 0;
    grid-column: auto;
}

/* The checkbox's wrapper span would otherwise be a full-width block with the
 * box adrift at its left edge. */
.signage-looks > p > span:not(.signage-hint)
{
    display: inline-flex;
    align-items: center;
    min-height: var(--control-h);
}


/* THE FIRST BOX IS TWO HALVES AND THEN A LINE.
 *
 * Name and design are what the display IS - two short answers, side by side,
 * with a rule between them so the eye reads two things and not one long row.
 * The address is what it is REACHED AT: a different kind of fact, the longest
 * control on the form, and the one somebody types into a television. Its own
 * full-width line, under a horizontal rule.
 *
 * The two rules are the same hairline, one vertical and one horizontal, so
 * the box reads as a table of two rows rather than as three stacked fields. */
.signage-ident > p:nth-child(2)
{
    padding-left: 20px;
    border-left: 1px solid var(--line);
}

.signage-ident > p.signage-url
{
    margin-top: 12px;
    padding-top: 12px;
    border-top: 1px solid var(--line);
}


/* ------------------------------------------------------------------ */
/* Signage tab, fourth pass: one action row, and a calmer first line   */
/* ------------------------------------------------------------------ */

/* REMOVE HARD LEFT, SAVE HARD RIGHT.
 *
 * Removing had a red section of its own at the foot of the form - a heading,
 * a warning icon, a whole box - for the thing done once in a display's life,
 * and it pushed Save further down the page every time the form grew.
 *
 * One line instead, with the two at opposite ends. Distance is the
 * separation: nobody reaches for the far left on the way to the far right. */
.signage-actions
{
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    margin: 2px 0 0;
}

/* The soft red. Not the alarm colour: removing a display is not an emergency,
 * it is a door you have to open on purpose, and a screen that shouts at you
 * every time you open the tab has taught you to stop reading it. The full
 * --danger stays for the button INSIDE the question, which is the one that
 * actually deletes something. */
.btn-soft-danger
{
    background: color-mix(in srgb, var(--danger) 16%, var(--surface));
    border: 1px solid color-mix(in srgb, var(--danger) 45%, var(--line));
    color: var(--danger);
}

.btn-soft-danger:hover
{
    background: color-mix(in srgb, var(--danger) 24%, var(--surface));
    border-color: var(--danger);
}

/* The question's two answers, side by side. The "no" is a link back to this
 * same tab - only a <summary> can close a <details>, so a button cannot, and
 * a destructive question with no visible way back is the wrong thing to leave
 * a reader holding. */
.signage-confirm-row
{
    display: flex;
    gap: 8px;
    align-items: center;
}

/* The panel hangs off the LEFT-hand button now, so it opens to the right
 * instead of off the edge of the form. */
.signage-actions .signage-confirm[open] > .signage-confirm-box
{
    right: auto;
    left: 0;
}

/* A label belongs to the field beside it, and at 28px of gap it did not look
 * as though it did. The identity line is short answers; the room for them
 * comes from the field, not from the gutter. */
.signage-ident > p
{
    grid-template-columns: max-content minmax(0, 1fr);
    gap: 3px 10px;
}

/* CALMER FOCUS ON THE SELECT.
 *
 * It carried a 2px bright-brand outline AND a bright-brand border, which on a
 * dark panel is two rings of the lightest colour in the palette around the
 * one control that is already the widest thing on the line. One ring, in the
 * darker brand ink, hugging the control. */
select:focus-visible
{
    outline: 2px solid var(--brand-ink);
    outline-offset: 0;
    border-color: var(--brand-ink);
}


/* ------------------------------------------------------------------ */
/* A POP-OVER MUST NOT LEAVE THE WINDOW                                */
/*                                                                     */
/* Every pop-over above hangs from the RIGHT edge of the control that  */
/* opens it - `right: 0` against a `position: relative` <details>. On a */
/* wide screen that is correct, and for the same reason in both bars:  */
/* .topbar-actions is justify-content: flex-end and .viewtools is      */
/* pushed right by .filter-row's flex: 1, so every one of these        */
/* buttons already sits near the right edge of the window and a panel  */
/* growing leftwards from it lands on the screen.                      */
/*                                                                     */
/* IT STOPS BEING TRUE THE MOMENT THE ROW WRAPS. A wrapped row starts  */
/* its new line at the LEFT, so `right: 0` is now measured from a      */
/* button at x = 20 and a 320-460px panel grows straight off the       */
/* window. Nothing clips it and no scrollbar appears, because an       */
/* absolutely positioned box is not a layout participant - it simply   */
/* leaves. Measured on the demo school: on a 360px screen the          */
/* Full-view list starts at -16px, Quick link at -117, the ? at -152.  */
/*                                                                     */
/* GUESSING THE OTHER DIRECTION IS NOT THE FIX. Opening rightwards     */
/* fails on exactly the same screens the other way round, on whichever */
/* buttons happen to END the wrapped row.                              */
/*                                                                     */
/* The panel is anchored to the wrong thing. Below the wrap it must be */
/* anchored not to a BUTTON, whose position on the line nobody can     */
/* predict, but to the BAR, which is as wide as the window by          */
/* construction. So the <details> goes `position: static` and the      */
/* containing block becomes the nearest positioned ancestor - the      */
/* sticky .topbar, or .filterbar once it is told to be relative - and  */
/* right/left then measure from the window's own edges.                */
/*                                                                     */
/* This is the same trick .epanel-picker already uses to make a picker */
/* hang from its whole panel instead of from the little + button.      */
/*                                                                     */
/* No script is involved, so it holds with JavaScript off, on the      */
/* first paint, and through a rotation - there is nothing to           */
/* re-measure. app.js carries a corrective belt for the widths ABOVE   */
/* this breakpoint, where a long school name can wrap a rail early.    */
/*                                                                     */
/* 900px and not 700, which is where the measurements stop failing:    */
/* the filter row drops to two columns at 820 and the panel rule below */
/* had to live in a query at least that wide. One breakpoint for one   */
/* problem beats two that nearly agree.                                */
/* ------------------------------------------------------------------ */

@media (max-width: 900px)
{
    /*
     * .topbar is already `sticky`, and a sticky box is a containing block for
     * its absolutely positioned descendants - so the header rail needs
     * nothing here. .filterbar is not positioned at all, and without this its
     * pop-over would escape all the way to the initial containing block.
     */
    .filterbar
    {
        position: relative;

        /*
         * AND IT STACKS, which is a second fault the first one was hiding.
         *
         * The bar is three items - the programme cascade, the four filter
         * panels, the view tools - and `.filter-row { flex: 1 }` has a ZERO
         * basis. A zero basis is a promise not to demand any width, so on a
         * narrow screen the other two take their content width first and the
         * panels divide whatever is left over. Measured on the demo school:
         * 44px for all four at 360, which is 17px each - Groups, Subjects,
         * Lecturers and Rooms printed on top of one another, each with a
         * picker hanging off a box narrower than its own + button.
         *
         * There is no width here to share three ways, so below the fold they
         * take turns instead. The panels then get the whole line: 155px each
         * at 360, which is enough for a name and a chip.
         */
        flex-direction: column;
        align-items: stretch;
    }

    .helppick,
    .noticepick,
    .viewpick,
    .langpick,
    .menupick,
    .whopick,
    .quicklink
    {
        position: static;
    }

    /*
     * Right edge to right edge with the bar, less a hair so the shadow has
     * somewhere to fall. Each panel KEEPS the width it asked for - a language
     * list stretched across a tablet would be a column of six-character words
     * in a 900px box - and only the cap is tightened, so that nothing can
     * ever be wider than the window it is drawn in.
     */
    .helppick-pop,
    .noticepick-pop,
    .viewpick-pop,
    .langpick-pop,
    .menupick-pop,
    .whopick-pop,
    .quicklink-body
    {
        right: 12px;
        left: auto;
        max-width: calc(100vw - 24px);
    }

    /*
     * There used to be an exception here for the language list NESTED INSIDE
     * THE MENU - it flowed inside the menu panel, so edges to hang from meant
     * nothing. It is back in the rail as of 3 Sep 2026 and is now one of the
     * pop-overs above, like every other one.
     */
}

/*
 * THE FILTER PANELS, WHERE "THE LAST TWO" STOPPED MEANING "ON THE RIGHT".
 *
 * `.filter-row .epanel:nth-last-child(-n+2) .picker-pop` opens the last two
 * pickers leftwards, and in ONE row of four that is exactly the two on the
 * right-hand side. At 820px the row becomes two columns of two - and the last
 * two are then the BOTTOM ROW, the first of which sits at the LEFT edge.
 * Rooms opened leftwards from x = 165 and 274px of it was off the window, on
 * every tablet and every phone.
 *
 * What decides the direction was never the document order. It is the COLUMN,
 * and in a two-column grid the column is odd and even.
 */
@media (max-width: 820px)
{
    .filter-row .epanel:nth-child(odd) .picker-pop
    {
        left: 0;
        right: auto;
    }

    .filter-row .epanel:nth-child(even) .picker-pop
    {
        left: auto;
        right: 0;
    }
}


/* ------------------------------------------------------------------ */
/* THE FILTERS TOGGLE, AND WHY IT IS A PHONE-ONLY CONTROL              */
/*                                                                     */
/* Above 900px the four panels are across the top of the page and      */
/* visible without being asked for. A button offering to show what is  */
/* already on screen is a button that can only confuse, so neither     */
/* copy is drawn at all - `display: none` here, and the two queries    */
/* below are the only things that ever turn one on.                    */
/*                                                                     */
/* This also means the desktop layout is untouched by every rule in    */
/* this section, including the collapse: `.filterbar.is-collapsed`     */
/* exists ONLY inside a query, so a wide window cannot hide its own    */
/* filters even if the class is left on the element - which is exactly */
/* what happens when a phone is turned landscape, and is why app.js    */
/* needs no resize listener.                                           */
/*                                                                     */
/* 900px, the same number as the pop-over rules above and the same     */
/* number app.js tests, because it is one question: is this window     */
/* narrow enough that the header rail and the filter bar have stacked. */
/*                                                                     */
/* TWO COPIES, ONE DRAWN. Where the control stands is not the same     */
/* question at 390px as at 700px. Header height, measured on demo,     */
/* against 139/97px with no button at all:                             */
/*                                                                     */
/*     width   no button   in the rail   in the corner                 */
/*     390          139           182             141                  */
/*     480          139           182             141                  */
/*     490          139           139             141                  */
/*     560           97            97             141                  */
/*     900           97            97             141                  */
/*                                                                     */
/* In the rail it costs a whole row of a sticky header on a phone and  */
/* nothing from 490 up; in the corner - the school-name row, which has */
/* had an empty right-hand edge since 520 - it costs two pixels on a   */
/* phone and a row on a tablet. So each width gets the copy that is    */
/* free there, and the swap is at 500px BECAUSE THE WEEK CHIP ALREADY  */
/* SWAPS THERE (see .weekchip-corner above). Below 500 the first row   */
/* is the header's overflow shelf and both homeless controls stand on  */
/* it. Two breakpoints five pixels apart would be two things to keep   */
/* in step forever.                                                    */
/* ------------------------------------------------------------------ */

.filtertoggle
{
    display: none;
    align-items: center;
    gap: 6px;
}

@media (max-width: 500px)
{
    .filtertoggle-corner
    {
        display: inline-flex;
    }
}

@media (min-width: 501px) and (max-width: 900px)
{
    .filtertoggle-rail
    {
        display: inline-flex;
    }
}

/*
 * THE COUNT, AND IT IS NOT A NOTIFICATION DOT.
 *
 * A closed bar hides the fact that the timetable on screen is one group's and
 * not the whole school's. The number is the only thing on the page that still
 * says so, so it is set in the brand colour and given its own pill rather
 * than being left as a second word in the label.
 *
 * `min-width` with centred text so 1 and 12 are the same shape - a pill that
 * changes width as the reader ticks boxes drags the whole rail with it, and
 * the rail is where people aim without looking.
 *
 * OUTSIDE THE QUERIES, because there are now two buttons that can carry it
 * and they are shown by two different ones. A rule that has to be repeated in
 * both is a rule that will be changed in one.
 */
.filtertoggle-n
{
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 18px;
    height: 18px;
    padding: 0 5px;
    border-radius: 999px;
    background: var(--brand);
    color: #fff;
    font-size: 11px;
    font-weight: 700;
    line-height: 1;
}

@media (max-width: 900px)
{
    /*
     * CLOSED IS GONE, NOT SHRUNK.
     *
     * `display: none` and not `height: 0` with `overflow: hidden`: the panels
     * contain <details> pickers, and a picker left open inside a zero-height
     * box still takes focus and still lands its pop-over on the page, over a
     * grid the reader thought was the whole screen. Taking the subtree out of
     * the layout takes its pop-overs with it.
     *
     * The cost is that there is nothing to animate. That is the right trade
     * here - the bar is most of a phone screen tall, and sliding that much of
     * the window up and down is a wait, not a delight.
     */
    .filterbar.is-collapsed
    {
        display: none;
    }
}


/* ------------------------------------------------------------------ */
/* Signage: the way to try a screen on a day that has lessons          */
/* ------------------------------------------------------------------ */

/*
 * WHY THIS SITS BESIDE THE ADDRESS.
 *
 * An info screen shows today. Set one up in August, or on a Saturday, and it
 * is honestly empty - and from the other side of the desk that is
 * indistinguishable from a screen pointed at the wrong rooms. `?now=` has
 * always existed; it was written down nowhere the person configuring the
 * screen would ever see it.
 *
 * So it goes on the address line, to the right of the copy button, where
 * somebody is already looking at the thing they just made. The address keeps
 * its 560px and its own family of styles; this is a quiet second column that
 * drops under the line when there is no room for it.
 */
.signage-urlrow
{
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px 16px;
}

/* Keeps the address at its own width - .signage-urlline already carries
 * max-width: 560px - while letting it give way first on a narrow panel. */
.signage-urlrow > .signage-urlline
{
    flex: 1 1 340px;
}

.signage-try
{
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 2px 8px;
    font-size: 12px;
    color: var(--ink-soft);
}

/* The example is the interesting half of the pair, so it is the one in the
 * address's own monospaced face: what is read here is what gets typed. */
.signage-tryex
{
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 12px;
    color: var(--brand-ink);
    text-decoration: underline;
    text-underline-offset: 2px;
    white-space: nowrap;
}

.signage-tryex:hover
{
    color: var(--brand);
}

/* Two hint lines under the address now - the address's own, and this one -
 * so they need to read as two sentences and not as one paragraph. */
.signage-hint.signage-wide + .signage-hint.signage-wide
{
    margin-top: -6px;
}


/* ------------------------------------------------------------------ */
/* The WTT ID on the event panel, and in the reports                   */
/* ------------------------------------------------------------------ */

/*
 * A STRING THAT GETS COPIED, NOT READ.
 *
 * Alma Mater's technician roster has a WTT_ID column; a registrar reads the id
 * off this panel and types it there. So it is monospaced - the face that makes
 * l, 1 and I different shapes, the same reasoning as the signage address - and
 * `user-select: all` makes one click select the whole thing.
 *
 * That IS the affordance. A copy button would have needed a label, a
 * translation and a script, for eight characters.
 */
.evwttid,
.repwttid
{
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 12.5px;
    color: var(--ink-soft);
    -webkit-user-select: all;
    user-select: all;
}

/* In a report table it is a column of them, so it also has to stay on one
 * line - a wrapped identifier is one somebody copies half of. */
.repwttid
{
    white-space: nowrap;
}


/* ------------------------------------------------------------------ */
/* Copy, one field at a time, on the event panel                       */
/* ------------------------------------------------------------------ */

/*
 * A SMALL BUTTON THAT MUST NOT BECOME THE LOUDEST THING IN THE ROW.
 *
 * Six of these can be on one panel - description, internal note, meeting
 * links, subject code, GUID - and the panel is a page somebody READS. So the
 * glyph carries the soft ink of a label until the row is hovered or the
 * button focused, and it never takes a border or a fill: the thing it sits
 * beside is the content, and this is a handle on it.
 *
 * NOT DRAWN WITHOUT SCRIPT. `.js` is set by app.js's first statement. The
 * copying lives entirely in that file, and a pressable-looking button that
 * does nothing would be a worse answer than the plain selectable text that
 * is there either way. See templates/_copybtn.php.
 */
.evcopy
{
    display: none;
}

.js .evcopy
{
    display: inline-flex;
    align-items: center;
    justify-content: center;
    vertical-align: middle;
    margin-left: 6px;
    padding: 3px;
    border: 0;
    border-radius: var(--radius-s);
    background: none;
    color: var(--ink-soft);
    cursor: pointer;
    /* A touch target that a finger can hit, around a 14px glyph. */
    min-width: 24px;
    min-height: 24px;
}

.js .evcopy:hover,
.js .evcopy:focus-visible
{
    color: var(--ink);
    background: var(--surface-2);
}

/* The tick replaces the clipboard for the moment after a copy - the icon
 * button's version of the word "Copied", which a button with text swaps in
 * place. Both are set by the one handler in app.js. */
.evcopy-ok
{
    display: none;
}

.evcopy.is-copied
{
    color: rgb(46 125 50);
}

.evcopy.is-copied .evcopy-i
{
    display: none;
}

.evcopy.is-copied .evcopy-ok
{
    display: block;
}

/* A label with a handle on its own value sits on one line with it. */
.evpanel-label.has-copy
{
    display: flex;
    align-items: center;
    gap: 2px;
}

/* The meeting links keep their buttons side by side; the copy handle rides
 * with its own link rather than at the end of the row, or a panel with two
 * rooms would have two handles and no way to tell which is which. */
.evlink-pair
{
    display: inline-flex;
    align-items: center;
}

@media print
{
    /* Paper has no clipboard. */
    .evcopy
    {
        display: none !important;
    }
}
