/* ==========================================================================
   Timetable DESKTOP layout (≥769px) — loaded ONLY by timetable/timetable.html.

   Why a separate file: layout.css and components.css are also loaded by
   shared.html / print.html, so any live-page-only restyling added there
   leaks into the share and print views. This file makes the scoping
   structural — those pages never load it.

   Rules live inside @media (min-width: 769px) (the same desktop seam as
   layout.css:63 / mobile.css). The only rules allowed OUTSIDE the media
   query are hide-by-default rules for new live-page-only elements, so the
   mobile rendering stays byte-identical.

   Scoping hooks (added by timetable.html only):
     main.tt-page  — the <main> wrapper (via {% block page_content_class %})
     .slots-ready  — set on <main> by pages/timetable/layout-slots.js once
                     the mobile action bar's controls have been moved into
                     the desktop rows (gates hiding the bar)
     .tt-shell     — the page content wrapper div
     .tt-row       — the desktop toolbar row (#tt-toolbar); it ships `hidden`
                     and is un-hidden by layout-slots.js. .tt-actions is its
                     right-hand button cluster
     .tt-wide      — set on <main> by layout-slots.js while the WIDE band is
                     in effect (three columns; ≥1440px, or ≥1680px with
                     weekend columns). Without it the desktop page is the
                     two-column NARROW band. The media queries are CONFIG.UI.
     .tt-weekend   — set on <main> by grid-weekends.js while the weekend
                     columns are shown (it is what raises the wide threshold)
   ========================================================================== */

/* --------------------------------------------------------------------------
   Live-page-only elements hidden at ALL widths by default (the one category
   of rule allowed outside the media query — see the header note). Revealed
   desktop-only inside the media block below.
   -------------------------------------------------------------------------- */
/* The favourites row's own fields. Search results and favourites are built
   by the same component and told apart only by their container, so each of
   these is hidden at every width here and revealed under #favoritesList
   inside the media block — a search row can never pick them up. */
.course-result .result-section,
.course-result .result-room,
.course-result .result-seats,
.course-result .result-conflict {
    display: none;
}
/* Course Information's desktop-only nodes. The panel renders on mobile too
   and is not being restyled there, so each of these is ADDED beside the
   mobile node it replaces (never in place of it): the alternate row label,
   the per-meeting-pattern lines, and the Credits row. */
#courseInfo .ci-label-alt,
#courseInfo .ci-patterns,
#courseInfo .ci-credits {
    display: none;
}
/* Font Awesome's .fas display rule loads after Tailwind and beats `hidden`,
   so the context-selector chevron is hidden here instead (this file loads
   after FA). Shown desktop-only below. */
#campusForm .ctx-chevron {
    display: none;
}
.right-panel .panel-chevron {
    display: none;
}
.course-block .block-meta {
    display: none;
}
.expert-tip-card .tips-close {
    display: none;
}
#time-format-btn .tf-switch {
    display: none;
}
.floating-label-container .search-kbd-hint {
    display: none;
}
#credit-counter-btn .gpa-label {
    display: none;
}
/* Desktop draft-tab badges (the active tab's own term · campus, an inactive
   tab's course count) — revealed by the toolbar rules in the media block. */
.version-tab .tab-ctx-self,
.version-tab .tab-count {
    display: none;
}
/* The desktop rows ship `hidden`; the UA stylesheet already hides them —
   this makes it explicit and independent of the built Tailwind bundle. */
.tt-row[hidden] {
    display: none;
}

@media (min-width: 769px) {

    /* ----------------------------------------------------------------------
       Z-INDEX SCALE (the page's paint order — every new layer references
       this table instead of picking a number):

         0   .timetable-section — isolation:isolate. Contains every inline z
             the grid JS writes (preview 5, blocks 10, hover/highlight
             100/200, remove-btn 101, #schDays 10, conflict-indicator 100)
             inside one context that sits below all page chrome.
        30   #tt-toolbar — in the NARROW band the results overlay (45) and
             the tips popover (46) live inside its stacking context; they
             are anchored on the row so they can span the grid card. (In
             the pre-slotting fallback the .search-section itself carries
             the 30 instead.) In the WIDE band nothing floats out of it.
          47   #campus-selection — inside the toolbar's context, above the
               overlay (45) and the tips popover (46), so its context
               popover (46, inside its own context) wins when two are open.
        40   #tt-more-modal — the ⋯ overflow's backdrop + panel
             (toolbar-overflow.js); a root-level sibling of the frame, so it
             blurs and covers the toolbar (30) too, and stays under the
             page modals.
        50   navbar, page modals (Tailwind z-50 — chrome must stay below)
        60   toasts (#notification-container z-[60])
       The toolbar's buttons and the frame's columns have no z-index (root
       level, below everything above). Pre-existing outliers, unused/
       unreached on this page: toast.js's 9999 fallback (only mounts when
       the template container is missing) and components.css's
       #ai-floating-button 1000 (element doesn't exist).
       ---------------------------------------------------------------------- */

    /* ----------------------------------------------------------------------
       Result rows (search column / results overlay, Favorites): code, title
       (two-line clamp) and time — nothing else; the Course Information panel
       carries instructor, room and capacity. Tighter than the mobile card
       so a 17rem search column and the Favorites list show more rows. The
       Favorites row alone keeps the section beside the code (the same
       element module renders both lists; the span is hidden everywhere
       else by the outside-media block).
       ---------------------------------------------------------------------- */
    #searchResults .course-result,
    #favoritesList .course-result {
        padding: 0.6rem 0.75rem;
        margin-bottom: 0.5rem;
    }
    #searchResults .course-result .text-lg,
    #favoritesList .course-result .text-lg {
        font-size: 1rem;
        line-height: 1.4;
        margin-bottom: 0.15rem;
    }
    #searchResults .course-result .line-clamp-2,
    #favoritesList .course-result .line-clamp-2 {
        margin-bottom: 0.35rem;
    }
    #favoritesList .course-result .result-section {
        display: inline;
        font-size: 0.78rem;
        font-weight: 500;
        color: #6b7280;
    }
    .dark #favoritesList .course-result .result-section {
        color: #9ca3af;
    }

    /* ----------------------------------------------------------------------
       Favorites rows carry more than a search row, in less height. A search
       result is being scanned; a favourite is a shortlist entry, and what
       keeps it on the list is its meeting time, its room, and how many
       seats are still there — a number that moves during registration week
       while nobody is looking. So: title on one line (search keeps two),
       and a meta line under it that ellipsizes, so anything added to it
       later cannot push the row taller.
       ---------------------------------------------------------------------- */
    #favoritesList .course-result {
        margin-bottom: 0.4rem;
    }
    #favoritesList .course-result .text-lg {
        margin-bottom: 0.1rem;
    }
    #favoritesList .course-result .line-clamp-2 {
        -webkit-line-clamp: 1;
        margin-bottom: 0.2rem;
    }
    #favoritesList .course-result .result-meta {
        display: block;
        min-width: 0;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
    }
    #favoritesList .course-result .result-meta .fa-clock {
        display: none;   /* the meta line is text; the icon costs its width */
    }
    #favoritesList .course-result .result-meta span {
        display: inline;
        font-size: 0.78rem;
        font-weight: 500;
        color: #4b5563;
    }
    /* The line is one run of text now, so space-x-1's inter-child margins
       would double up with the " · " separators. Tailwind emits those as
       `> :not([hidden]) ~ :not([hidden])` (0,3,0) — an id outranks it. */
    #favoritesList .course-result .result-meta > span {
        margin-left: 0;
    }
    /* The row as a grid: the meta and conflict lines run the FULL width
       instead of sharing the text column with the two controls, which only
       ever sit beside the first line anyway. Without this, 1440 (the
       narrowest the right column gets — 340px, the wide band's minimum)
       clipped the meta line by 24px on any row whose button reads
       "Added ✓", losing the word "seats" — the one field on the line that
       has to survive.

       `display: contents` lifts the component's text wrapper out of the
       way so its three lines become grid items directly; the DOM is
       untouched, so the mobile row still renders as the plain flex box it
       always was. */
    #favoritesList .course-result > div {
        display: grid;
        grid-template-columns: minmax(0, 1fr) auto;
        column-gap: 0.5rem;
        align-items: start;
    }
    #favoritesList .course-result > div > .flex-1 {
        display: contents;
    }
    #favoritesList .course-result .text-lg,
    #favoritesList .course-result .line-clamp-2 {
        grid-column: 1;
    }
    #favoritesList .course-result .result-meta,
    #favoritesList .course-result .result-conflict {
        grid-column: 1 / -1;
    }
    /* Beside the code line and the title, not the lines under them. */
    #favoritesList .course-result .gap-3 {
        grid-column: 2;
        grid-row: 1 / 3;
    }
    .dark #favoritesList .course-result .result-meta span {
        color: #9ca3af;
    }

    /* Why this favourite is still a candidate — it clashes with the draft as
       it stands. Only the favourites renderer computes it. */
    #favoritesList .course-result .result-conflict {
        display: block;
        margin-top: 0.15rem;
        font-size: 0.72rem;
        font-weight: 600;
        color: #dc2626;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
    }
    .dark #favoritesList .course-result .result-conflict {
        color: #f87171;
    }

    /* ----------------------------------------------------------------------
       Page chrome. <main> is a plain block again (the rail is gone); the
       content frame below centres itself inside the shell.

       The mobile action bar is hidden only once layout-slots.js has moved
       its controls into the toolbar row (`slots-ready` on <main>). Without that
       class — before the module runs, or if its desktop pass failed — the
       bar renders as layout.css's horizontal sticky bar with every control
       still inside it, so the page can never lose its controls.

       The shell clips horizontal overflow only. The rows are built so their
       content shrinks instead of growing past the frame (#draft-tabs-strip
       below), and this clip is the guarantee that no horizontal page scroll
       can ever appear; overflow-y stays visible for the popovers that hang
       below the rows (`overflow-x: clip` + `overflow-y: visible` is a valid
       pair and creates no scroll container, so nothing vertical is
       clipped). The fixed-position modals inside the shell are unaffected:
       their containing block is the viewport.
       ---------------------------------------------------------------------- */
    main.tt-page.slots-ready > #timetable-bar {
        display: none;
    }
    main.tt-page > .tt-shell {
        overflow-x: clip;
        /* 1rem per side instead of Tailwind's lg:px-8: the frame centres
           itself, so the padding only ever binds at the band thresholds —
           where 32px is the difference between a fed and a starved card. */
        padding-inline: 1rem;
    }

    /* ----------------------------------------------------------------------
       Toolbar controls — the mobile bar's buttons, slotted into the row's
       .tt-actions cluster by layout-slots.js. They keep layout.css's base
       .tb-action-btn look (white pill, border, 0.8rem); only emphasis and
       the two special controls are restyled here. Every button is rigid
       (flex-shrink:0); the draft strip (and, in the narrow band, the search
       pill) are the row items that flex.
       ---------------------------------------------------------------------- */
    main.tt-page #import-btn,
    main.tt-page .tb-download,
    main.tt-page #share-btn,
    main.tt-page #add-temp-course-btn,
    main.tt-page #credit-counter-btn,
    main.tt-page #clear-all-btn {
        flex-shrink: 0;
    }
    /* The action cluster sits at the row's right end; its buttons keep DOM
       order (Add Course first, Clear All last). It never shrinks — the draft
       strip and, in the narrow band, the search pill are the flexible items. */
    #tt-toolbar > .tt-actions {
        display: flex;
        align-items: center;
        gap: 0.5rem;
        margin-left: auto;
        flex-shrink: 0;
    }

    /* ⋯ overflow (toolbar-overflow.js). The trigger ships `hidden`; the
       attribute must beat .tb-action-btn's display (same specificity,
       layout.css loads later), hence the id rule. The collapsed buttons are
       moved into #tt-more-panel — a card under the trigger over a blurred
       backdrop (#tt-more-modal: the `.fixed.inset-0` + `hidden` shape
       ui/modal.js expects; z 40 per the scale above). Backdrop colour and
       blur are the page's existing values (the shadow tint and
       components.css's blur). */
    #tt-more-btn[hidden] {
        display: none;
    }
    #tt-more-btn {
        padding: 0.35rem 0.6rem;
    }
    #tt-more-modal {
        z-index: 40;
        background: rgba(15, 23, 42, 0.18);
        backdrop-filter: blur(10px);
        -webkit-backdrop-filter: blur(10px);
    }
    .dark #tt-more-modal {
        background: rgba(0, 0, 0, 0.5);
    }
    #tt-more-panel {
        position: absolute; /* top/right set from the trigger's rect by the module */
        min-width: 13rem;
        display: flex;
        flex-direction: column;
        gap: 0.125rem;
        padding: 0.375rem;
        background: white;
        border: 1px solid var(--color-border);
        border-radius: 0.75rem;
        box-shadow: 0 12px 32px rgba(15, 23, 42, 0.18);
    }
    .dark #tt-more-panel {
        background: #1f2937;
        border-color: #374151;
    }
    /* Buttons read as menu rows inside the panel; the id-level looks (GPA's
       indigo, Clear All's red text + confirm state) keep their colours but
       lose their pill borders. */
    main.tt-page #tt-more-panel > .tb-action-btn {
        width: 100%;
        justify-content: flex-start;
        background: transparent;
        border-color: transparent;
        border-radius: 0.5rem;
        padding: 0.55rem 0.75rem;
        font-size: 0.85rem;
    }
    main.tt-page #tt-more-panel > .tb-action-btn:hover {
        background: #f3f4f6;
        border-color: transparent;
    }
    main.tt-page #tt-more-panel > #clear-all-btn.confirming {
        background: #fef2f2;
    }
    .dark main.tt-page #tt-more-panel > .tb-action-btn {
        background: transparent;
        color: #e5e7eb;
    }
    .dark main.tt-page #tt-more-panel > .tb-action-btn:hover {
        background: #374151;
    }
    .dark main.tt-page #tt-more-panel > #credit-counter-btn {
        color: #a5b4fc;
    }
    .dark main.tt-page #tt-more-panel > #clear-all-btn {
        color: #f87171;
    }
    .dark main.tt-page #tt-more-panel > #clear-all-btn.confirming {
        background: #450a0a;
    }

    /* Emphasis inversion: Add Course is the PRIMARY action; the GPA control
       is a stat that happens to open the GPA modal (still the button
       tests/smoke_local.py flow 4 clicks). Overrides .tb-action-btn.primary's
       solid fill desktop-only; the mobile bar keeps the original emphasis. */
    main.tt-page #credit-counter-btn {
        background: white;
        border-color: #c7d2fe;
        color: var(--color-primary);
    }
    main.tt-page #credit-counter-btn:hover {
        background: #eef2ff;
        border-color: #a5b4fc;
    }
    /* "GPA · 12 cr": the GPA label leads and the credit total is its
       secondary figure; the mobile "Credits:" text is hidden here. */
    main.tt-page #credit-counter-btn .credits-label {
        display: none;
    }
    main.tt-page #credit-counter-btn .gpa-label {
        display: inline;
        font-weight: 600;
    }
    main.tt-page #credit-counter-btn #credit-count {
        color: #6b7280;
    }
    main.tt-page #credit-counter-btn #credit-count::after {
        content: ' cr';
    }
    main.tt-page #add-temp-course-btn {
        background: var(--color-primary);
        border-color: var(--color-primary);
        color: white;
    }
    main.tt-page #add-temp-course-btn:hover {
        background: #4f46e5;
        border-color: #4f46e5;
    }

    /* Clear All: a destructive TEXT button at the end of the drafts cluster
       (no dropdown component exists to fold it into). Its 2-click confirm
       state (.confirming, layout.css) is restated at this specificity so the
       id selector above can't flatten it. */
    main.tt-page #clear-all-btn {
        background: transparent;
        border-color: transparent;
        color: #dc2626;
    }
    main.tt-page #clear-all-btn:hover {
        background: #fef2f2;
        border-color: #fecaca;
    }
    main.tt-page #clear-all-btn.confirming {
        background: #fef2f2;
        border-color: #fca5a5;
        color: #dc2626;
    }
    main.tt-page #clear-all-btn.confirming:hover {
        background: #fee2e2;
        border-color: #f87171;
    }

    /* Time format: the bare "12h" label was ambiguous (current mode or
       target?). Desktop shows a two-cell switch — ONE control, a shared
       track whose filled cell marks the CURRENT format; clicking anywhere
       on the button toggles, as before. applyTimeFormat() moves .tf-active. */
    main.tt-page #time-format-label {
        display: none;
    }
    main.tt-page #time-format-btn .tf-switch {
        display: inline-flex;
        border: 1px solid var(--color-border);
        border-radius: 0.375rem;
        overflow: hidden;
        background: #f3f4f6;
    }
    main.tt-page #time-format-btn .tf-seg {
        display: block;
        font-size: 0.6rem;
        line-height: 1.5;
        padding: 0.08rem 0.3rem;
        color: #9ca3af;
    }
    main.tt-page #time-format-btn .tf-seg.tf-active {
        background: var(--color-primary);
        color: white;
    }

    /* ----------------------------------------------------------------------
       Content frame — this is layout.css's .container (overridden here, never
       edited; the two-class selector outranks it and this file loads later).
       A fit-content grid sized AROUND the fixed 675px canvas instead of the
       canvas floating inside a fluid track:
         · column 1 is minmax(0, max-content): sized by the timetable card,
           so the card is exactly canvas + scrollbar gutter + card padding
           and no dead space can open inside it; the 0 minimum lets it
           shrink when the frame can't fit;
         · column 2 (the right column) is clamp(380px, 26vw, 480px);
         · rows: the toolbar (spanning every column), then the grid card |
           right column row taking the rest;
         · width: fit-content = the sum of the tracks, centred by the auto
           margins. max-width:100% lets the frame get NARROWER than that on
           small desktops — the card track then shrinks and the canvas clips
           on the right (today's failure mode), never a horizontal page
           scroll (free space is distributed equally up to each track's
           limit, so the right column keeps its clamp and the card gives).
       Height: layout.css's calc(100vh - 200px) covered the navbar reserve
       (main pt-24 = 96) + the shell's py-4 (32) + 72px of slack left behind
       by the old horizontal action bar; 144 keeps only the real neighbours.
       min-height: layout.css's 800px floor is dropped HERE, in BOTH bands.
       It outlives the height it was written for: with it the frame is 800
       tall whatever the viewport is, so on any screen shorter than 944
       (800 + 144) the frame's bottom — and with it the right column's —
       falls below the fold: 12px at 1366x900, 144px at 1366x768, i.e. on
       exactly the 1366 laptops the narrow band exists for. The grid card
       already scrolls internally, so a shorter frame costs visible hours,
       never content. layout.css is untouched, so the print and shared
       views keep their own floor.
       ---------------------------------------------------------------------- */
    .tt-shell .main-content-grid {
        display: grid;
        grid-template-columns: minmax(0, max-content) clamp(380px, 26vw, 480px);
        grid-template-rows: auto minmax(0, 1fr);
        gap: 1rem;
        width: fit-content;
        max-width: 100%;
        height: calc(100vh - 144px);
        min-height: 0;
        margin-inline: auto;
        padding: 0;
        justify-content: start;
    }
    /* Neutralize the md:space-x-4 sibling margins — grid gap replaces them.
       Tailwind emits them as `> :not([hidden]) ~ :not([hidden])` (0,3,0),
       which outranks a plain `> *` (0,2,0) — so that rule alone never won
       and every non-first child kept a 16px margin-left on desktop (the
       card sat 32px from the frame edge, the panel 32px from the card).
       Mirror the selector to beat it on specificity. */
    .tt-shell .main-content-grid > * {
        margin: 0;
    }
    .tt-shell .main-content-grid > :not([hidden]) ~ :not([hidden]) {
        margin: 0;
    }

    /* The toolbar row (.tt-row). It ships `hidden` in the template;
       layout-slots.js un-hides it once it has moved the controls in (the
       :not([hidden]) guard keeps a still-hidden row at display:none on
       desktop too). contain:inline-size — a fit-content grid takes the width
       of its WIDEST row; without it the row's natural width (selector +
       three draft pills + search + six buttons) would silently widen the
       card track and reopen the dead space this frame exists to close. The
       row sizes to the tracks instead and its content shrinks to fit (see
       #draft-tabs-strip). Inline-size containment creates no containing
       block, so the popovers anchored inside still overflow the row. No
       overflow:hidden here — it would clip the context popover and the
       narrow band's results overlay; the shell's overflow-x:clip is the
       page-scroll guarantee. */
    .tt-shell .main-content-grid > .tt-row:not([hidden]) {
        display: flex;
        align-items: center;
        gap: 0.5rem;
        min-width: 0;
        contain: inline-size;
    }
    .tt-shell .main-content-grid > #tt-toolbar {
        grid-row: 1;
        grid-column: 1 / -1;
    }
    /* Before layout-slots.js runs — and if its desktop pass fails — the
       context selector and the search pill are still direct children of the
       frame. They are deliberately NOT placed: grid auto-placement drops
       them into row 1's free cells (the pill | the selector, side by side,
       where the toolbar would be), so the page is coherent either way
       without a spare row whose gap would cost 16px in the normal state. */
    /* Toolbar, far left: the context selector (compacted — its trigger shows
       the current term + campus, its controls become a popover). z 47, see
       the scale above. Tailwind's order-5 is the MOBILE column order — inert
       under explicit grid placement, wrong inside the flex row. */
    main.tt-page #campus-selection {
        position: relative;
        max-width: none;
        margin: 0;
        order: 0;
        z-index: 47;
    }
    /* Narrow band: the search pill takes the flexible middle of the toolbar
       (Tailwind's order-1 is again the mobile order). Anchor for the results
       overlay + tips popover; the overlay floats above the grid card. */
    main.tt-page .search-section {
        position: relative;
        height: auto;
        width: auto;
        margin: 0;
        order: 0;
        z-index: 30;
    }
    #tt-toolbar > .search-section {
        /* Basis = floor: the pill grows into whatever is free and never
           competes with the draft strip for shrink space (a larger basis
           made flexbox squeeze both proportionally, ellipsizing tab names
           while the pill still had room). toolbar-overflow.js budgets the
           pill at exactly this floor. */
        flex: 1 1 12rem;
        min-width: 12rem;
        /* Inside the row the SECTION is not the anchor any more: the row is,
           so the results overlay and the tips popover can span the grid
           card rather than the pill (see the overlay block below). */
        position: static;
        z-index: auto;
    }
    .tt-shell .main-content-grid > #tt-toolbar:not([hidden]) {
        position: relative;   /* anchor for the results overlay + tips popover */
        z-index: 30;
    }
    .tt-shell .main-content-grid > .timetable-section {
        grid-area: 2 / 1 / 3 / 2;
        min-width: 0;
        min-height: 0;
        height: 100%;
        width: auto;
        /* Level 0 of the z-scale: the grid JS writes inline z-index values
           up to 200 on blocks (hover/highlight) which otherwise compete at
           ROOT level and paint over the results overlay (45) and popovers
           (46). Isolating the section clamps all of them beneath the chrome.
           Audited: every z/positioned descendant is grid-internal — the six
           modals and the toast mount are siblings outside this section. */
        isolation: isolate;
    }
    .tt-shell .main-content-grid > .right-panel {
        grid-area: 2 / 2 / 3 / 3;
        min-height: 0;
        height: auto;
        /* The template's md:w-80 (20rem) is an explicit width, which turns
           off justify-self:stretch — the panel would sit 320px wide inside
           the clamp() track. Let the track size it. */
        width: auto;
    }

    /* ----------------------------------------------------------------------
       WIDE band (main.tt-page.tt-wide — set by layout-slots.js at ≥1440px,
       or ≥1680px while weekend columns are shown): three columns,
       search | grid card | right column.
         · The card track stays minmax(0, max-content) and both side tracks
           are minmax(<min>, 1fr). Measured in Chromium: the max-content
           track is fed FIRST (737px at 1440 with both sides at their
           minima) and only then is the remainder shared equally between the
           fr sides — a fixed-max side track (minmax(17rem, 30rem)) would
           split the free space three ways and starve the card instead.
         · width:100%, not fit-content — with fr sides a fit-content frame
           collapses to the minima (1449px wide at 1920).
         · max-width is the sides' cap (fr tracks have no per-track cap):
           1720 = 737 card + 32 gaps + 2 × 475.5.
         · Minima: 17rem search (code + two-line title + time beside the
           Add/star controls), 21.25rem right column (the Favorites item's
           text column stays ≈190px). 1440 is where both fit beside the 737
           card with a 16px page scrollbar (frame 1392 − 769 = 623 ≥ 612);
           1366 is 63px short, which is why it keeps the two-column frame.
         · min-height:0 comes from the base frame rule now (both bands fit
           the viewport; the grid and both columns scroll internally).
       Without the class (pre-JS, or a failed slotting pass) the page is the
       two-column frame above — the intended degraded state.
       ---------------------------------------------------------------------- */
    main.tt-page.tt-wide .main-content-grid {
        grid-template-columns: minmax(17rem, 1fr) minmax(0, max-content) minmax(21.25rem, 1fr);
        width: 100%;
        max-width: 107.5rem;
        justify-content: center;
    }
    main.tt-page.tt-wide .main-content-grid > .search-section {
        grid-area: 2 / 1 / 3 / 2;
        min-width: 0;
        min-height: 0;
    }
    main.tt-page.tt-wide .main-content-grid > .timetable-section {
        grid-area: 2 / 2 / 3 / 3;
    }
    main.tt-page.tt-wide .main-content-grid > .right-panel {
        grid-area: 2 / 3 / 3 / 4;
    }

    /* ----------------------------------------------------------------------
       Toolbar, after the selector: the draft strip leaves the grid card
       (layout-slots.js) and loses its card-internal divider. It is the ONE
       row item allowed to shrink — to nothing if it must — clipping its own
       content (it holds no popovers); every button and the search floor are
       rigid, so the row's minimum content (selector + Add + search floor +
       gaps, ≈600px) fits every desktop width and the row can never be wider
       than the frame. Which buttons stay visible is toolbar-overflow.js's
       call, not this stylesheet's.
       ---------------------------------------------------------------------- */
    #tt-toolbar > #draft-tabs-strip {
        flex: 0 1 auto;
        min-width: 0;
        overflow: hidden;
        flex-wrap: nowrap;
        padding-bottom: 0;
        margin-bottom: 0;
        border-bottom: 0;
    }
    #tt-toolbar #timetable-tabs {
        flex-wrap: nowrap;
        min-width: 0;
    }
    #tt-toolbar .version-tab {
        min-width: 0;
        flex: 0 1 auto;
    }
    /* Tab badges (built by renderVersionTabs; all three carry .tab-context's
       look from layout.css / dark-mode.css). The toolbar shows ONE per tab:
       the ACTIVE tab states its own term · campus — the context the page is
       showing, and the only place it will be shown once the selector goes —
       and an inactive tab shows how many courses it holds. The mobile
       foreign-context badge is hidden here; the two desktop badges are
       hidden at every width by the outside-media block above. */
    #tt-toolbar .version-tab .tab-ctx-foreign {
        display: none;
    }
    #tt-toolbar .version-tab.active .tab-ctx-self {
        display: inline;
    }
    #tt-toolbar .version-tab:not(.active) .tab-count {
        display: inline;
    }

    /* ----------------------------------------------------------------------
       Context selector: strip the big filter card down to a compact trigger;
       the selects + Apply move into a popover (.ctx-open, context-popover.js).
       ---------------------------------------------------------------------- */
    #campus-selection .modern-filter-card {
        padding: 0;
        border: none;
        box-shadow: none;
        background: transparent;
        /* components.css:539 clips descendants — the popover hangs BELOW the
           (now slim) card, so it must not be clipped here. */
        overflow: visible;
        animation: none;
    }
    /* The card's shimmer overlay (::before) escapes with overflow visible —
       and has no card to shimmer over anymore. */
    #campus-selection .modern-filter-card::before {
        display: none;
    }
    #campusForm {
        position: relative;
    }
    #campusForm .context-current {
        cursor: pointer;
        border: 1px solid var(--color-border);
        border-radius: 0.75rem;
        padding: 0.4rem 0.75rem;
        background: white;
        transition: background 0.15s, border-color 0.15s;
        white-space: nowrap;
    }
    #campusForm .context-current:hover {
        background: #f3f4f6;
        border-color: #d1d5db;
    }
    /* Toolbar: the trigger is one slim line — a smaller icon and no
       "Currently viewing" caption (the row IS the current context; the
       term/campus text itself stays: #currently-viewing-text). */
    #campusForm .context-current .filter-icon-wrapper {
        width: 32px;
        height: 32px;
        border-radius: 8px;
    }
    #campusForm .context-current .filter-icon-wrapper i {
        font-size: 0.9rem;
    }
    #campusForm .context-current .uppercase {
        display: none;
    }
    #campusForm .ctx-chevron {
        display: inline-block;
        transition: transform 0.2s ease;
    }
    #campusForm.ctx-open .ctx-chevron {
        transform: rotate(180deg);
    }
    #campusForm .context-controls {
        display: none;
        position: absolute;
        top: 100%;
        left: 0;
        margin-top: 0.5rem;
        z-index: 46;
        background: white;
        border: 1px solid var(--color-border);
        border-radius: 0.75rem;
        box-shadow: 0 12px 32px rgba(15, 23, 42, 0.18);
        padding: 1rem;
        min-width: 24rem;
    }
    #campusForm.ctx-open .context-controls {
        display: flex;
    }
    .dark #campusForm .context-current {
        background: #1f2937;
        border-color: #374151;
    }
    .dark #campusForm .context-current:hover {
        background: #111827;
    }
    .dark #campusForm .context-controls {
        background: #1f2937;
        border-color: #374151;
    }

    /* ----------------------------------------------------------------------
       Search pill (narrow band, in the toolbar): the old full-height card
       collapses to one slim row; the input fills it from the left (the old
       right-aligned 15rem field that grew on focus left dead space at the
       pill's start).
       ---------------------------------------------------------------------- */
    .search-section > .modern-card {
        height: auto;
        flex-direction: row;
        align-items: center;
        justify-content: flex-start;
        gap: 0.5rem;
        padding: 0.35rem 0.75rem;
        /* The whole pill is a click target (search-overlay.js focuses the
           input); advertise it. */
        cursor: text;
    }
    /* The pill must not be a containing block, or the overlay anchors on it
       instead of on the toolbar: components.css gives .modern-card a
       backdrop-filter (a backdrop filter establishes a containing block for
       absolute descendants, like transform does) and a hover translate.
       Both off in the row; the pill is 95% opaque, the blur was invisible. */
    #tt-toolbar .search-section > .modern-card {
        backdrop-filter: none;
        -webkit-backdrop-filter: none;
    }
    #tt-toolbar .search-section > .modern-card:hover {
        transform: none;
    }
    /* The floating placeholder must not wrap when the pill sits at its
       12rem floor: one line, clipped short of the kbd hint and buttons. */
    #tt-toolbar .floating-label {
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
        max-width: calc(100% - 5.5rem);
    }
    /* Slimmer input in the row (py-3 + .modern-input's 12px -> 8px). */
    #tt-toolbar .modern-search-input {
        padding-top: 0.5rem;
        padding-bottom: 0.5rem;
    }
    .search-section #searchForm {
        margin-bottom: 0;
        flex: 1 1 auto;
        min-width: 0;
        width: auto;
        max-width: none;
    }

    /* WIDE band: the search section is a full-height column card — the form
       on top, the tips toggle under it, the results filling the rest and
       scrolling inside the card (the template's flex-1 / min-h-0 /
       overflow-y-auto on #searchResults do that once the card is a column
       again). No z-index: nothing floats out of the column. */
    main.tt-page.tt-wide .search-section {
        z-index: auto;
    }
    main.tt-page.tt-wide .search-section > .modern-card {
        flex-direction: column;
        align-items: stretch;
        height: 100%;
        min-height: 0;
        padding: 0.75rem;
        gap: 0.5rem;
        cursor: default;
    }
    main.tt-page.tt-wide .search-section #searchForm {
        flex: 0 0 auto;
    }
    main.tt-page.tt-wide #search-tips-btn {
        align-self: flex-end;
        margin-top: -0.25rem;
    }

    /* "/" shortcut hint — a small kbd chip left of the clear/search buttons.
       Hidden while typing or focused (same sibling trick layout.css uses for
       the floating label), and inert to clicks. */
    .floating-label-container .search-kbd-hint {
        display: inline-block;
        position: absolute;
        right: 4.25rem;
        top: 50%;
        transform: translateY(-50%);
        padding: 0.05rem 0.45rem;
        border: 1px solid var(--color-border);
        border-radius: 0.375rem;
        font-size: 0.72rem;
        line-height: 1.3;
        font-family: inherit;
        color: #9ca3af;
        background: #f9fafb;
        pointer-events: none;
    }
    .floating-label-container input:focus ~ .search-kbd-hint,
    .floating-label-container input:not(:placeholder-shown) ~ .search-kbd-hint {
        display: none;
    }
    .dark .floating-label-container .search-kbd-hint {
        background: #111827;
        border-color: #374151;
        color: #6b7280;
    }
    /* The idle empty-state placeholder lives inside #searchResults, which is
       hidden until the overlay opens — the row stays slim. */

    /* ----------------------------------------------------------------------
       Results overlay (narrow band): floats under the toolbar row and spans
       exactly the GRID CARD —
       its containing block is the row (position:relative above; the pill
       is static and its hover lift is off, so nothing in between can become
       the containing block), left:0 is the card's left edge and the right
       inset is the right column's clamp() plus the grid gap. Results are
       the primary add-course surface; the old pill-wide panel was narrower
       than the card at every common width. In the pre-slotting fallback
       the section spans both columns itself, so the same inset holds.
       #searchResults itself is the node handed to initRender —
       hidden/shown in place, never replaced.
       ---------------------------------------------------------------------- */
    .search-section #searchResults {
        display: none;
        position: absolute;
        top: 100%;
        left: 0;
        right: calc(clamp(380px, 26vw, 480px) + 1rem);
        margin-top: 0.5rem;
        z-index: 45;
        background: white;
        border: 1px solid var(--color-border);
        border-radius: 0.75rem;
        box-shadow: 0 12px 32px rgba(15, 23, 42, 0.18);
        padding: 0.75rem;
        overflow-y: auto;
        max-height: 24rem; /* fallback — JS fits to 3.5 rows when it can */
    }
    .search-section.overlay-open #searchResults {
        display: block;
    }
    .dark .search-section #searchResults {
        background: #1f2937;
        border-color: #374151;
    }
    /* WIDE band: the results are inline — an ordinary scrolling child of the
       column card, never a floating panel. #searchResults is still the
       initRender root; only its placement changes, so the idle "Ready to
       Search" state now shows in the column. (search-overlay.js never adds
       overlay-open in this band; this rule makes the CSS agree even if it
       did.) */
    main.tt-page.tt-wide .search-section #searchResults {
        display: block;
        position: static;
        margin-top: 0;
        padding: 0;
        border: 0;
        border-radius: 0;
        box-shadow: none;
        background: transparent;
        max-height: none;
    }

    /* ----------------------------------------------------------------------
       Expert Search Tips: no longer a permanent card — a popover toggled by
       #search-tips-btn (.tips-open on .search-section). Its content is
       forced open inside the popover; the !importants beat the accordion's
       inline max-height/opacity toggles, which still drive the MOBILE
       accordion unchanged.
       ---------------------------------------------------------------------- */
    .search-section .expert-tip-card {
        display: none;
        position: absolute;
        top: 100%;
        right: calc(clamp(380px, 26vw, 480px) + 1rem);  /* the card's right edge */
        width: 24rem;
        max-width: 100%;
        margin: 0.5rem 0 0;
        z-index: 46; /* tips/context popover level of the z-scale */
        box-shadow: 0 12px 32px rgba(15, 23, 42, 0.18);
        /* Guaranteed-opaque surface UNDER the card's Tailwind gradient
           (background-image) — the popover floats over the grid and must
           never let it show through. */
        background-color: #fff;
        border: 1px solid var(--color-border);
    }
    .search-section.tips-open .expert-tip-card {
        display: block;
    }
    .search-section.tips-open .tips-content {
        max-height: none !important;
        opacity: 1 !important;
    }
    .search-section.tips-open #tips-header {
        pointer-events: none;
    }
    .search-section.tips-open #tips-chevron {
        display: none;
    }
    /* Close X: revealed only in the popover; pointer-events re-enabled under
       the header's pointer-events:none (the mobile accordion header must
       stay inert while the popover is open, but its X must not). */
    .search-section .expert-tip-card .tips-close {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        width: 1.5rem;
        height: 1.5rem;
        border-radius: 0.375rem;
        color: #6b7280;
    }
    .search-section.tips-open .expert-tip-card .tips-close {
        pointer-events: auto;
    }
    .search-section .expert-tip-card .tips-close:hover {
        background: rgba(99, 102, 241, 0.12);
        color: #374151;
    }
    .dark .search-section .expert-tip-card {
        background-color: #1f2937;
        border-color: #374151;
    }
    .dark .search-section .expert-tip-card .tips-close {
        color: #9ca3af;
    }
    .dark .search-section .expert-tip-card .tips-close:hover {
        color: #e5e7eb;
    }
    /* WIDE band: the tips card opens inline under the form and pushes the
       results down — the column has the room, and nothing floats over the
       grid. The .tips-open toggling above is unchanged. */
    main.tt-page.tt-wide .search-section .expert-tip-card {
        position: static;
        width: auto;
        max-width: none;
        margin: 0;
        box-shadow: none;
    }

    /* ----------------------------------------------------------------------
       12h/24h in the grid's corner cell — the empty cell above the time
       gutter, left of Monday (canvas x 1-75, y 1-30: Monday's header sits at
       left:85px minus its 10px margin, the header row is 30px tall).
       layout-slots.js prepends the button into the grid card; it is placed
       over the corner from OUTSIDE the scroll container, so it never scrolls
       — matching the sticky header row. The corner itself has no background
       (#schDays is zero-height; only the headers paint white), so the
       toggle brings the grid's own surface. z-index 11 sits above #schDays
       (10) inside the section's isolated context; no course block can reach
       x < 100. The two-cell .tf-switch is the whole visible control.
       It starts AT the scroll container's edge (card p-6 = 24px), one pixel
       outside the canvas border, and redraws that border corner itself
       (#ddd = layout.css's #schTimes border): a scrolled time label is
       clipped at the container edge, and a toggle that began one pixel
       lower left a hairline of it visible above the switch.
       ---------------------------------------------------------------------- */
    .timetable-section > .bg-white {
        position: relative;
    }
    .timetable-section > .bg-white > #time-format-btn {
        position: absolute;
        top: 24px;
        left: 24px;
        width: 76px;               /* 1px border + the 75px corner cell */
        height: 31px;              /* 1px border + the 30px header row */
        z-index: 11;
        justify-content: center;
        padding: 0;
        border: 0;
        border-top: 1px solid #ddd;
        border-left: 1px solid #ddd;
        border-right: 1px solid var(--color-border);
        border-radius: 0;
        background: white;
    }
    .timetable-section > .bg-white > #time-format-btn:hover {
        background: #f3f4f6;
    }
    .timetable-section > .bg-white > #time-format-btn .fa-clock {
        display: none;
    }
    .dark .timetable-section > .bg-white > #time-format-btn {
        background: #1f2937;
    }
    .dark .timetable-section > .bg-white > #time-format-btn:hover {
        background: #111827;
    }

    /* ----------------------------------------------------------------------
       Grid blocks: AA-contrast text (--block-fg set per block by
       course-block.js from the block's own background), section+room meta,
       and a PERSISTENT conflict treatment — outline plus hatching, never
       color alone. Scoped to #schTimes in this live-page-only file, so the
       print/shared/static-grid renderings are untouched.
       ---------------------------------------------------------------------- */
    #schTimes .course-block {
        color: var(--block-fg, white);
    }
    .course-block .block-meta {
        display: block;
        font-size: 0.62rem;
        font-weight: 500;
        line-height: 1.2;
        margin-top: 2px;
        opacity: 0.85;
    }
    #schTimes .course-block.tt-conflict {
        outline: 2px solid var(--color-danger);
        outline-offset: -2px;
        background-image: repeating-linear-gradient(
            45deg,
            rgba(220, 38, 38, 0.18) 0 6px,
            transparent 6px 12px
        );
    }

    /* ----------------------------------------------------------------------
       Right column: Course Information + Favorites read as ONE card with two
       collapsible sections (disclosure.js toggles .panel-collapsed).

       HEIGHT MODEL — Favorites has the floor and Course Information yields,
       not the other way round. Before this, Course Information won every
       time: its min-height resolves to `auto` (= min-content, its overflow
       being visible), so a selected course pinned the card at its full 467px
       while Favorites — `flex-1 overflow-hidden`, min-height 0 — absorbed
       the whole shortfall. Measured at 1440x900 that left the list 97px
       against a 111px row: not one row fully visible, and 0px at 1440x768.
       So:
         · card 1 gets min-height:0 (it may now shrink below its content) and
           becomes a flex column whose ONE scroller is #courseInfo;
         · card 2 gets `flex: 1 1 0` with a 220px floor — the flex algorithm
           freezes it there and takes the rest out of card 1, which is the
           inversion this block is for. 220 − 48 padding − 48 header = 124px
           of list, so its first row is always whole;
         · the list's inline height (calc(100% − 80px), a pre-panel-head
           chain that also over-subtracts by 32) is replaced by flex sizing;
         · a COLLAPSED card must not hold the floor — see the collapsed rule
           below.
       At 1440x900 (column 694) card 1 keeps its 467 and Favorites takes 227;
       at 1440x768 (562) Favorites freezes at 220 and card 1 shrinks to 342,
       the one case where #courseInfo scrolls.
       ---------------------------------------------------------------------- */
    .right-panel > .panel-card:first-child {
        border-bottom-left-radius: 0;
        border-bottom-right-radius: 0;
        display: flex;
        flex-direction: column;
        flex: 0 1 auto;
        min-height: 0;
        overflow: hidden;
    }
    .right-panel > .panel-card:first-child > #courseInfo {
        flex: 1 1 auto;
        min-height: 0;
        overflow-y: auto;
    }
    .right-panel > .panel-card + .panel-card {
        margin-top: 0;
        border-top-left-radius: 0;
        border-top-right-radius: 0;
        border-top: 1px solid var(--color-border);
        display: flex;
        flex-direction: column;
        flex: 1 1 0;
        min-height: 220px;
    }
    .right-panel .panel-head {
        cursor: pointer;
    }
    .right-panel .panel-chevron {
        display: inline-block;
        margin-left: 0.5rem;
        transition: transform 0.2s ease;
    }
    .right-panel .panel-card.panel-collapsed .panel-chevron {
        transform: rotate(-90deg);
    }
    .right-panel .panel-card.panel-collapsed > :not(.panel-head) {
        display: none;
    }
    .right-panel .panel-card.panel-collapsed {
        flex: 0 0 auto;
        /* Releases Favorites' 220px floor — a collapsed card is its header. */
        min-height: 0;
    }
    .right-panel .panel-card.panel-collapsed .panel-head {
        margin-bottom: 0;
    }
    .dark .right-panel > .panel-card + .panel-card {
        border-top-color: #374151;
    }

    /* Empty states: one compact line instead of the icon poster — "zero
       favorites" and "nothing selected" don't deserve a third of the column.
       Covers both emitters of the same markup (the template's inline states
       and ui/dom.js createEmptyState('favorites' | 'course-info')). Course
       Information keeps its card and header: hovering any search row fills
       it and it never empties again until a draft switch, so a card that
       vanished while empty would jump back within seconds of the first
       search. Its line reads "Hover over a course" (the poster's first
       subtitle); the Favorites line keeps its title. Mobile keeps both
       posters. */
    .right-panel #favoritesList .empty-state,
    .right-panel #courseInfo .empty-state {
        display: flex;
        align-items: center;
        justify-content: center;
        gap: 0.5rem;
        padding: 0.75rem 0;
    }
    .right-panel #favoritesList .empty-state .empty-state-icon i,
    .right-panel #courseInfo .empty-state .empty-state-icon i {
        font-size: 1rem;
        margin-bottom: 0;
    }
    .right-panel #favoritesList .empty-state h4 {
        margin-bottom: 0;
    }
    .right-panel #favoritesList .empty-state p,
    .right-panel #courseInfo .empty-state h4,
    .right-panel #courseInfo .empty-state p:last-of-type {
        display: none;
    }
    .right-panel #courseInfo .empty-state p:first-of-type {
        margin: 0;
    }

    /* Favorites takes the rest of the column and scrolls internally — the
       ONE scroller in that card. Both of the list's inline styles are for
       mobile and have to stay inline, so !important is the only way past
       them here (same precedent as the tips-content overrides above):
       max-height:400px caps the list on phones, and height:calc(100% − 80px)
       is a pre-panel-head chain that over-subtracts by 32px and can't see
       the card's flex sizing at all. */
    .right-panel #favoritesList {
        max-height: none !important;
        height: auto !important;
        flex: 1 1 0;
        min-height: 0;
    }

    /* Course Information: a compact header band, then the metadata as a
       hairline-divided list (small caps label over the value, the icon
       column kept), then the capacity block. Styled via the inert ci-* /
       cap-* hook classes course-info.js adds (its Tailwind classes are
       untouched — that component renders on mobile too). The space-y-4
       sibling margins are neutralised at matching specificity (Tailwind
       emits them as `> :not([hidden]) ~ :not([hidden])`).

       The metadata is a TWO-COLUMN grid: the short fields pair up so the
       card is five rows instead of eight, which is what buys Favorites its
       room back (the taller the panel, the less the column has left). DOM
       order is untouched — `order` does the pairing, so the mobile panel
       still reads top to bottom in source order:

           Schedule            | Section        (row of two)
           Instructor                           (full width)
           Room                | Credits        (row of two)
           capacity                             (full width)

       A full-width item forces a new grid row, so a missing field just
       closes up. column-gap is 0 with the right-hand cells padded instead,
       so the rows' bottom hairlines meet as one continuous line; items
       stretch, so the two cells of a pair share a baseline for it. */
    .right-panel #courseInfo .space-y-4 > :not([hidden]) ~ :not([hidden]) {
        margin-top: 0;
    }
    .right-panel #courseInfo .space-y-4 {
        display: grid;
        grid-template-columns: 1fr 1fr;
        column-gap: 0;
    }
    .right-panel #courseInfo .ci-head,
    .right-panel #courseInfo .ci-instructor,
    .right-panel #courseInfo .cap-block {
        grid-column: 1 / -1;
    }
    .right-panel #courseInfo .ci-schedule {
        order: 1;
    }
    .right-panel #courseInfo .ci-section {
        order: 2;
    }
    .right-panel #courseInfo .ci-instructor {
        order: 3;
    }
    .right-panel #courseInfo .ci-room {
        order: 4;
    }
    .right-panel #courseInfo .ci-credits {
        order: 5;
        display: flex;   /* revealed: hidden at every width by the block above */
    }
    .right-panel #courseInfo .cap-block {
        order: 6;
    }
    .right-panel #courseInfo .ci-section,
    .right-panel #courseInfo .ci-credits {
        padding-left: 0.75rem;
    }

    /* Schedule row: the desktop label ("Schedule" — the mobile one stays
       "Day/Time" and is hidden here) over ONE LINE PER MEETING PATTERN, so a
       lecture + lab course shows both meetings instead of the raw
       slash-separated SIS string. The raw value is kept in the DOM and
       hidden only when there are pattern lines to replace it. */
    .right-panel #courseInfo .ci-label-alt {
        display: block;
    }
    .right-panel #courseInfo .ci-schedule .ci-label:not(.ci-label-alt) {
        display: none;
    }
    .right-panel #courseInfo .ci-patterns {
        display: block;
    }
    .right-panel #courseInfo .ci-pattern {
        display: block;
        font-size: 0.82rem;
        line-height: 1.4;
        color: #1f2937;
    }
    .right-panel #courseInfo .ci-row.has-patterns > div > .ci-value {
        display: none;
    }
    .dark .right-panel #courseInfo .ci-pattern {
        color: #e5e7eb;
    }
    .right-panel #courseInfo .ci-head {
        padding: 0.6rem 0.75rem;
        margin-bottom: 0.35rem;
        border-bottom-width: 1px;
    }
    .right-panel #courseInfo .ci-head h3 {
        font-size: 1.05rem;
        line-height: 1.3;
        margin-bottom: 0.15rem;
    }
    .right-panel #courseInfo .ci-head p {
        font-size: 0.8rem;
        line-height: 1.4;
    }
    .right-panel #courseInfo .ci-row {
        padding: 0.45rem 0.25rem;
        border-bottom: 1px solid var(--color-border);
    }
    .right-panel #courseInfo .ci-label {
        font-size: 0.66rem;
        font-weight: 600;
        letter-spacing: 0.04em;
        text-transform: uppercase;
        color: #6b7280;
    }
    .right-panel #courseInfo .ci-value {
        font-size: 0.82rem;
        line-height: 1.4;
        color: #1f2937;
    }
    .dark .right-panel #courseInfo .ci-row {
        border-bottom-color: #374151;
    }
    .dark .right-panel #courseInfo .ci-label {
        color: #9ca3af;
    }
    .dark .right-panel #courseInfo .ci-value {
        color: #e5e7eb;
    }

    /* Capacity is the field students act on — give the seats-left figure
       and its bar more weight than the metadata rows, on TWO lines instead
       of four: the figure and the enrolled count share line one with the
       status word appended to it, the bar sits under them. The "Capacity"
       caption goes: the figure says what it is. Every cell is placed
       explicitly (the status word comes last in the DOM but belongs on the
       first line, and auto-placement would push it below the bar). */
    .right-panel #courseInfo .cap-block {
        margin-top: 0.5rem;
        padding-top: 1rem;
        border-top-width: 2px;
        display: grid;
        grid-template-columns: 1fr auto;
        align-items: baseline;
        column-gap: 0.35rem;
        row-gap: 0.4rem;
    }
    .right-panel #courseInfo .cap-caption {
        display: none;
    }
    .right-panel #courseInfo .cap-head {
        grid-row: 1;
        grid-column: 1;
        margin-bottom: 0;
    }
    .right-panel #courseInfo .cap-status {
        grid-row: 1;
        grid-column: 2;
    }
    .right-panel #courseInfo .cap-status::before {
        content: '·';
        margin-right: 0.35rem;
        color: #9ca3af;
    }
    .right-panel #courseInfo .cap-bar {
        grid-row: 2;
        grid-column: 1 / -1;
        height: 0.875rem;
        margin-bottom: 0;
    }
    /* The no-enrolment-data fallback replaces all three. */
    .right-panel #courseInfo .cap-unknown {
        grid-column: 1 / -1;
    }
    .right-panel #courseInfo .cap-seats {
        font-size: 1.25rem;
        font-weight: 800;
        line-height: 1.2;
    }

    /* Accent tie-in: the selected course's block color marks the Course
       Information card, so the panel visibly connects to the block that was
       clicked. Class + --course-accent are set together on the SAME card by
       render/regions/course-info.js (never a positional selector, so they
       can't drift apart). A border carries no text — no contrast concerns. */
    .right-panel .panel-card.accent-on {
        border-left: 4px solid var(--course-accent);
    }

    /* Dark mode for the restyled row controls (dark-mode.css predates this
       file and only knows the base .tb-action-btn). */
    .dark main.tt-page #clear-all-btn {
        color: #f87171;
        border-color: transparent;
    }
    .dark main.tt-page #clear-all-btn:hover {
        background: #450a0a;
        border-color: #7f1d1d;
    }
    .dark main.tt-page #clear-all-btn.confirming {
        background: #450a0a;
        border-color: #991b1b;
        color: #f87171;
    }
    .dark main.tt-page #credit-counter-btn {
        background: #1f2937;
        border-color: #4338ca;
        color: #a5b4fc;
    }
    .dark main.tt-page #credit-counter-btn:hover {
        background: #312e81;
    }
    .dark main.tt-page #credit-counter-btn #credit-count {
        color: #9ca3af;
    }
    .dark main.tt-page #time-format-btn .tf-switch {
        background: #111827;
        border-color: #374151;
    }
    .dark main.tt-page #time-format-btn .tf-seg {
        color: #6b7280;
    }
    .dark main.tt-page #time-format-btn .tf-seg.tf-active {
        background: var(--color-primary);
        color: white;
    }
}
