:root {
    --sidebar-width: 260px;
    /* There is no fixed header: titlebar height is 0, so every calc() that
       subtracted it reclaims the space, and the #htmx-progress bar falls to
       the viewport top. The mobile strip has its own height. */
    --titlebar-height: 0px;
    --mobile-bar-height: 48px;
}

/* A page rendered without the side nav, the CMS embed chrome among them, gives its width back. */
:root.no-main-nav {
    --sidebar-width: 0;
}

html,
body {
    height: 100%;
}

body {
    max-width: 100%;
    margin: 0 auto;
}

/* Skip-to-content link (WCAG 2.4.1 Bypass Blocks): the shell's first
   focusable element. Off-screen until :focus, then drops into view above
   every other fixed layer (mobile bar, nav drawer, alert stack). Colour/
   shape come from the existing .button.primary component — this class
   only adds the position + reveal behaviour. */
.skip-link {
    position: fixed;
    top: var(--space-sm);
    left: var(--space-sm);
    z-index: 2000;
    transform: translateY(-150%);
    transition: var(--transition-style);
}

.skip-link:focus {
    transform: translateY(0);
}

/* Mobile-only top app bar: hamburger · current-app context · avatar (the
   desktop header is gone). Shown only under the mobile breakpoint. */
.mobile-bar {
    display: none;
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    height: var(--mobile-bar-height);
    z-index: 30;
    align-items: center;
    gap: var(--space-sm);
    padding: 0 var(--space-sm);

    button.mainNavToggle,
    button.mobile-profile-trigger {
        display: flex;
        align-items: center;
        justify-content: center;
        border: none;
        background: none;
        padding: 0;
        cursor: pointer;
    }

    button.mainNavToggle {
        width: 40px;
        height: 40px;
        font-size: var(--font-size-lg);
    }

    .mobile-bar-context {
        flex: 1;
        min-width: 0;
        display: flex;
        align-items: center;
        gap: var(--space-sm);
    }

    .mobile-bar-logo {
        flex-shrink: 0;
        width: 26px;
        height: 26px;
        border-radius: var(--radius-sm);
        overflow: hidden;
        display: flex;

        img {
            width: 100%;
            height: 100%;
            object-fit: cover;
        }
    }

    .mobile-bar-app {
        font-weight: var(--typography-fontWeightMedium);
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
    }

    button.mobile-profile-trigger {
        flex-shrink: 0;
        width: 32px;
        height: 32px;
        border-radius: 50%;
        overflow: hidden;

        img {
            width: 100%;
            height: 100%;
            object-fit: cover;
        }
    }
}

/* Scrim behind the open mobile drawer (shown only under the breakpoint). */
.nav-scrim {
    display: none;
}

nav.main-nav {
    position: fixed;
    top: 0;
    left: 0;
    height: 100vh;
    width: var(--sidebar-width);
    display: flex;
    flex-direction: column;
    overflow: hidden;

    /* ---- Identity band: org brand zone + app switcher ---- */
    .nav-identity {
        flex-shrink: 0;
    }

    .nav-org {
        display: flex;
        align-items: center;
        gap: var(--space-ms);
        padding: var(--space-ms) var(--space-md);
        text-decoration: none;
        /* It's an <a>, so it'd otherwise inherit the links' row rule (now
           width: 100%, was a hard-coded --sidebar-width px value that
           didn't track the actual box on the wide mobile drawer). auto
           still resolves to the same full-band fill via normal block
           sizing, kept explicit since this band has no li/lvl-N inset
           padding for a percentage width to subtract via border-box. */
        width: auto;
        border-radius: 0;
    }

    .nav-org-logo {
        flex-shrink: 0;
        width: 36px;
        height: 36px;
        border-radius: var(--radius-md);
        overflow: hidden;
        display: flex;

        img {
            width: 100%;
            height: 100%;
            object-fit: cover;
        }
    }

    .nav-org-name {
        font-size: var(--font-size-lg);
        font-weight: var(--typography-fontWeightMedium);
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
    }

    .nav-apptabs {
        display: flex;
        gap: var(--space-xs);
        /* Symmetric so the gap above the tab row equals the gap below it. */
        padding: var(--space-sm);
    }

    .nav-apptab {
        flex: 1;
        display: flex;
        flex-direction: column;
        align-items: center;
        gap: var(--space-xs);
        /* Symmetric vertical padding: icon-to-top equals text-to-bottom (+2px
           top/bottom over --space-xs for a touch more height). */
        padding: calc(var(--space-xs) + 2px) var(--space-xs);
        border-radius: var(--radius-md);
        font-size: var(--font-size-xs);
        /* line-height:1 on the label too (icon already has it) so the text's
           line-box slack doesn't make the bottom gap exceed the top. */
        line-height: 1;
        text-align: center;
        text-decoration: none;

        i {
            font-size: var(--font-size-base);
            /* display:block so width/text-align actually apply (an inline <i>
               ignores them) — the glyph then centres in the full tab column,
               incl. the wider CMS globe. line-height:1 removes glyph slack.
               padding:0 overrides the generic `nav div i { padding-right }`,
               which otherwise shrinks the centring box and shifts glyphs left. */
            display: block;
            width: 100%;
            padding: 0;
            line-height: 1;
            text-align: center;
        }
    }

    /* ---- Ensemble season chooser band: docked under the identity block,
       above the scrolling sections. A single horizontal row —
       calendar glyph, the season name (the select flex-grows to fill), and a
       trailing caret — all centred. flex-shrink:0 keeps it pinned while the
       sections scroll. ---- */
    .season-chooser {
        flex-shrink: 0;
        position: relative;
        display: flex;
        align-items: center;
        /* Match `menu li`'s horizontal inset so the glyph + season name line up
           with the nav items below. */
        padding: 0 var(--space-sm);
        margin-bottom: 0;

        /* The select spans the whole row; the glyphs overlay it (pointer-events
           off), so a click anywhere — caret included — opens the picker. */
        select {
            flex: 1 1 auto;
            min-width: 0;
        }
    }

    /* ---- Scrollable accordion sections ---- */
    .nav-scroll {
        flex: 1;
        min-height: 0;
        overflow-y: auto;
        display: flex;
        flex-direction: column;
    }

    /* CSS-only dropdown navigation */
    .nav-dropdown-container {
        position: relative;
        padding: 0.175rem var(--space-lg);
    }

    .nav-dropdown-toggle {
        display: none;
    }

    .nav-dropdown-label {
        cursor: pointer;
        display: block;
        padding: 0.6rem 0;
        margin: 0;

        span {
            display: block;

            i {
                width: 1.3rem;
                text-align: center;
                padding-right: var(--space-sm);
                transition: transform 0.2s ease;
            }
        }
    }

    .nav-dropdown-toggle:checked ~ .nav-dropdown-label span i {
        transform: translateX(-.5rem) rotate(180deg);
    }

    .nav-dropdown {
        max-height: 0;
        overflow: hidden;
        transition: max-height 0.3s ease, opacity 0.3s ease;
        opacity: 0;
        list-style: none;
        padding: 0;
        margin: 0;

        li {
            padding: 0.175rem 0;

            a {
                padding-left: 0;
            }
        }
    }

    .nav-dropdown-toggle:checked ~ .nav-dropdown-label::after {
        transform: rotate(180deg);
    }

    .nav-dropdown-toggle:checked ~ .nav-dropdown {
        max-height: 500px;
        opacity: 1;
    }

    /* ---- Personal band: pinned Favourites + My profile.
       flex-shrink:0 docks it above the user anchor while the sections above
       scroll. Divider + skin live in features.css/colors.css. ---- */
    .nav-personal {
        flex-shrink: 0;
    }

    /* The user anchor pins above the wordmark. */
    .profile-menu {
        flex-shrink: 0;
    }

    /* ---- Product wordmark foot ---- */
    .nav-wordmark {
        flex-shrink: 0;
        display: flex;
        align-items: center;
        gap: var(--space-sm);
        flex-wrap: wrap;
        padding: var(--space-sm) var(--space-md);
        font-size: var(--font-size-xs);
    }

    /* The colour de-emphasis moved from --text-disabled to --text-secondary
       (contrast fix) — the same colour .nav-wordmark above already uses,
       so weight now carries the demotion instead. */
    .nav-wordmark-build {
        width: 100%;
        /* One line box at any stamp length, so a long version can't wrap and
           shift the foot below it. Untruncated stamp: the About dialog. */
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
        font-size: var(--font-size-xs);
        font-weight: var(--typography-fontWeightLight);
    }
}

/* Drag-to-resize handle: a thin grab strip on the nav's right edge. The nav
   is position:fixed, so this absolutely-positions to it. Width is
   driven by --sidebar-width (set inline on :root by warhol-apps.js, clamped
   260–520px); everything that references --sidebar-width follows. Hidden under
   the mobile breakpoint (the nav is an off-canvas drawer there). */
nav.main-nav .nav-resize-handle {
    position: absolute;
    top: 0;
    right: 0;
    width: 6px;
    height: 100%;
    cursor: col-resize;
    z-index: 41;
    touch-action: none;
}

/* The handle sits flush with the nav's right (and top/bottom) edge by
   design, so the global outward-offset focus ring (tokens.css) would
   overhang the nav's own overflow:hidden clip. Pull the ring inward instead
   of widening the handle. */
nav.main-nav .nav-resize-handle:focus-visible {
    outline-offset: -2px;
}

/* While dragging, lock the cursor + suppress text selection document-wide. The
   org zone is an <a> (global `a { transition: all }`), so its width would animate
   and lag behind the handle; kill nav transitions during the drag so the whole
   nav tracks the pointer instantly. */
body.nav-resizing {
    cursor: col-resize;
    user-select: none;
}

body.nav-resizing nav.main-nav a {
    transition: none;
}

@media (max-width: 768px) {
    nav.main-nav .nav-resize-handle {
        display: none;
    }
}

/* User-menu popover geometry + reveal — positional, so it lives in the
   layout layer (skin is in features.css). Desktop opens upward off the
   anchor; the mobile top-sheet override is in the breakpoint below, so both
   positions share one layer and the cascade resolves by source order. */
div.profile-menu {
    position: relative;
}

div.profile-menu .menu {
    position: absolute;
    left: var(--space-sm);
    right: var(--space-sm);
    bottom: calc(100% + var(--space-xs));
    max-height: 70vh;
    overflow-y: auto;
    z-index: 40;
    visibility: hidden;
    opacity: 0;
    transition: var(--transition-style);
    /* The token animates `all`, and `visibility` holds `visible` for the whole
       run when it is one of the endpoints — so a dismissed menu stays clickable
       and focusable until the transition ends. Animate only the continuous
       properties; the discrete one then flips with the class. */
    transition-property: opacity, transform;
}

div.profile-menu .menu.active {
    visibility: visible;
    opacity: 1;
}

section.main {
    display: flex;
    flex-direction: column;
    min-height: 100vh;
    padding: 0 0 0 var(--sidebar-width);

    main {
        flex-grow: 1;
    }

    section#alert {
        z-index: 100;
        position: fixed;
        bottom: var(--space-lg);
        right: var(--space-lg);
        top: auto;
        left: auto;
        width: min(360px, calc(100vw - 2 * var(--space-lg)));
        margin: 0;
        /* Toast stack: newest nearest the corner without touching the afterbegin
           OOB swap order — column-reverse renders the first DOM child at the bottom. */
        display: flex;
        flex-direction: column-reverse;
        gap: var(--space-sm);

        /* Warnings and errors never expire, so the stack can outgrow the viewport
           and push close glyphs out of reach. Past the four-toast cap it scrolls
           instead. Scoped to that case because scrolling clips the cards' drop
           shadows; column-reverse leaves the newest toast in view. */
        &:has(> section:nth-child(5)) {
            max-height: calc(100vh - 2 * var(--space-lg));
            overflow-y: auto;
        }
    }

    aside.ensembleHomeQuickLinks {
        float: right;
        max-width: var(--sidebar-width);
        margin-left: var(--space-md);
        margin-bottom: var(--space-md);
    }
}

/* .rosterDisplay's column flow reaches neither page that carries the class: both
   also carry .roster-grid below, which wins on the same element in either view.
   Left alone here — untangling it is not this rule's business. */
.rosterDisplay {
    column-count: 3;
    column-gap: var(--space-md);
}

/* Show Photos view — a responsive grid sized for photo cards. Compact view
   packs the bare names into narrower columns. The Show Photos / Compact toggle adds
   or removes .compact (warhol-apps.js rosterViewToggle). */
.roster-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr));
    gap: var(--space-lg);
    align-items: start;
}

/* Compact view is a multi-column block: names run down a column, and the roster
   wraps into as many 12rem columns as fit, so narrow viewports reach one with no
   breakpoint. The `columns` shorthand is load-bearing — it resets the column-count
   .rosterDisplay sets, which would otherwise pin this at three columns. */
.roster-grid.compact-roster {
    display: block;
    columns: 12rem;
    column-gap: var(--space-lg);
}

/* Each instrument's heading spans the full grid so its members start a fresh
   band below it; the band wrapper and the members block dissolve into the grid
   (display: contents) so members become direct grid items. scroll-margin gives the
   jump-to anchors breathing room above the heading (and clears the fixed mobile
   bar below). */
.roster-section-title {
    grid-column: 1 / -1;
    scroll-margin-top: var(--space-lg);
}

/* Jump-to-section index: a full-width chip row above the bands. Spanning all
   columns lets the Compact view hide it via the grid's own .compact-roster
   class — it's a Photos-view affordance only. */
.roster-jump {
    grid-column: 1 / -1;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-sm);
}

.roster-grid.compact-roster .roster-jump {
    display: none;
}

@media screen and (max-width: 768px) {
    .roster-section-title {
        scroll-margin-top: calc(var(--mobile-bar-height) + var(--space-sm));
    }
}

.roster-section,
.instrumentMembers {
    display: contents;
}

/* ---- Compact: a band may break across a column boundary only where three names
   land on each side, and never straight after the heading. Two rules below look
   removable and are not: orphans/widows count LINE BOXES, so each name must be a
   full-width line box or both are inert; and they are relaxed when unsatisfiable,
   so the under-six floor has to be stated on its own. Firefox implements only that
   floor, so a longer band may split anywhere there — accepted, no fallback. ---- */
.roster-grid.compact-roster .roster-section {
    display: block;
    break-inside: auto;
    margin-bottom: var(--space-md);
}

.roster-grid.compact-roster .roster-section:not(:has(.roster-member:nth-child(6))) {
    break-inside: avoid;
}

.roster-grid.compact-roster .roster-section-title {
    break-after: avoid;
}

.roster-grid.compact-roster .instrumentMembers {
    display: block;
    orphans: 3;
    widows: 3;
}

.roster-grid.compact-roster .roster-member {
    display: inline;
}

.roster-grid.compact-roster .roster-compact-name {
    display: inline-block;
    width: 100%;
}

/* Let cells shrink rather than force a wide column (long names / emails). */
.roster-member {
    min-width: 0;
}

.roster-toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--space-md);
    margin-bottom: var(--space-md);
}

.attendanceTable {
    min-width: 450px;
}

.row {
    display: flex;
    flex-direction: row;
    justify-content: space-between;
    align-items: center;

    & * {
        align-self: flex-start;
    }
}

/* My Section's multi-column flow narrows on smaller viewports. The roster grid
   (.roster-grid) is intrinsically responsive via auto-fill and needs no breakpoint. */
@media screen and (max-width: 1250px) {
    .rosterDisplay {
        column-count: 2;
    }
}

@media screen and (max-width: 768px) {
    html, body {
        overflow-x: hidden;
    }

    .mobile-bar {
        display: flex;
    }

    nav.main-nav {
        left: calc(-1 * var(--sidebar-width));
        top: 0;
        height: 100vh;
        transition: var(--transition-style);
        /* `all` interpolates z-index too, so the 20 -> 40 open ramp spends most
           of its run below the scrim's 35 and taps land on the scrim instead of
           the drawer. Animate only the property that actually moves. */
        transition-property: left;
        /* Below the app bar (30) by default, so the user-menu top-sheet (a fixed
           child of the nav) emerges from BEHIND the bar rather than over it. */
        z-index: 20;
    }

    nav.main-nav.open {
        left: 0;
        /* Only when the drawer is open does it rise above the bar to cover it. */
        z-index: 40;
    }

    /* Scrim dims content AND the bar behind the open drawer; tapping it closes. */
    .nav-scrim {
        display: block;
        position: fixed;
        inset: 0;
        z-index: 35;
        opacity: 0;
        pointer-events: none;
        transition: opacity var(--transition-style);
    }

    nav.main-nav.open ~ .nav-scrim {
        opacity: 1;
        pointer-events: auto;
    }

    /* Mobile: the in-drawer anchor is hidden (the menu opens from the app-bar
       avatar), and the menu flies in from the top as a sheet below the bar. */
    div.profile-menu .profile-anchor {
        display: none;
    }

    div.profile-menu .menu {
        position: fixed;
        top: var(--mobile-bar-height);
        left: 0;
        right: 0;
        bottom: auto;
        max-height: calc(100vh - var(--mobile-bar-height));
        transform: translateY(-100%);
        z-index: 18;
    }

    div.profile-menu .menu.active {
        transform: translateY(0);
    }

    section.main {
        padding-left: 0;
        padding-top: var(--mobile-bar-height);
        overflow-x: hidden;

        section#alert {
            left: var(--space-md);
            right: var(--space-md);
            bottom: var(--space-md);
            width: auto;
            margin: 0;
        }
    }

    .rosterDisplay {
        column-count: 1;
    }

    .attendanceTable {
        min-width: 200px;
    }

    .row {
        margin: 0;
        padding: 0;
        display: flex;
        flex-direction: column;
        justify-content: space-between;
        align-items: center;

    }
}

/* Mobile drawer width: on screens no wider than the nav's max width (520px =
   the resize cap SIDEBAR_MAX), snap the drawer to the FULL screen
   width — it can never exceed the max here, since the screen itself is ≤ max.
   Above 520px (up to the 768px mobile breakpoint) the normal --sidebar-width
   drawer is retained. Placed after the 768px block so it wins the cascade. */
@media screen and (max-width: 520px) {
    nav.main-nav {
        width: 100vw;
        left: -100vw;
    }

    nav.main-nav.open {
        left: 0;
    }

    /* Full-bleed drawer: no content column for the band to be softened against. */
    nav.main-nav .nav-org {
        border-radius: 0;
    }
}

code {
    padding: .13rem;
}

main section#cms-toolbar-2 {
    margin: 0.6rem 0;
    width: 100%;
    display: flex;
    align-items: center;
    gap: var(--space-ms);
    /* Make the toolbar its own size-query container so the label-collapse
       below keys off the toolbar's ACTUAL width, not the viewport. The
       open entity-chooser sidebar shrinks <main> via body margin-right,
       and it's user-resizable via a drag handle, so the viewport width no
       longer reflects how much room the toolbar actually has. */
    container-type: inline-size;
    container-name: cms-toolbar;

    h4 {
        margin: 0;
    }

    #cms-toolbar-location-bar {
        flex: 1 1 auto;
        min-width: 0;
    }

    #cms-toolbar-location {
        white-space: nowrap;
        overflow-x: auto;
        font-size: small;
    }

    #cms-toolbar-search {
        margin-left: auto;
    }

    #cms-toolbar-search-button {
        display: none;
    }
}

/* Collapse the button labels to icon-only when the toolbar's own available
   width gets tight. Keyed on the toolbar's width (container query), not the
   viewport, so it accounts for the left nav, the open chooser sidebar, and
   the chooser's resizable width all at once. 1015px ≈ the previous 1200px
   viewport breakpoint minus the 260px side nav (= 940px), plus 75px so the
   labels drop a touch sooner; with the chooser open at its 420px default
   this fires at a ~1695px viewport, and it tracks the drag-handle as the
   operator resizes the pane. */
@container cms-toolbar (max-width: 1015px) {
    main section#cms-toolbar-2 .toolbar-label {
        display: none;
    }
}

@media screen and (max-width: 550px) {
    main section#cms-toolbar-2 {
        flex-wrap: wrap;

        #cms-toolbar-location-bar {
            display: none;
        }

        #cms-toolbar-search-button {
            display: block;
        }
    }
}

/* Drop low-priority list-table columns on phone-portrait widths so the
   essential columns and the Commands actions fit without horizontal scroll.
   Apply to BOTH the <th> and every matching <td>. */
@media screen and (max-width: 480px) {
    .hide-narrow {
        display: none;
    }
}
