/*
 * features.css — Layer 8: FEATURE COMPOSITIONS.
 *
 * Per-feature styling that composes the system vocabulary
 * (components.css, layer 7) and the token/color layers. Sections here
 * should be short: existing classes in the markup, a handful of
 * structural rules, zero new colors (coloring lives in colors.css).
 * If a rule here re-implements a system pattern, it is drift — compose
 * or extend the system instead. Loads after components.css so feature
 * refinements win ties; sanctioned divergences go to overrides.css with
 * a justification, not here.
 */

/* User anchor + attached menu skin. Geometry/reveal/positioning
   live in layout.css; this layer is just the skin and item composition. */
div.profile-menu {
    .profile-anchor {
        display: flex;
        align-items: center;
        gap: var(--space-sm);
        width: 100%;
        margin: 0;
        padding: var(--space-sm) var(--space-md);
        border: none;
        border-radius: 0;
        box-shadow: none;
        background: none;
        cursor: pointer;
        text-align: left;
        font: inherit;
    }

    .profile-avatar {
        flex-shrink: 0;
        width: 34px;
        height: 34px;
        border-radius: 50%;
        overflow: hidden;
        display: flex;
        align-items: center;
        justify-content: center;

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

    .profile-id {
        flex: 1;
        min-width: 0;
        display: flex;
        flex-direction: column;
        line-height: 1.25;
    }

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

    .profile-caret {
        flex-shrink: 0;
        font-size: var(--font-size-sm);
    }

    .menu {
        padding: var(--space-sm);
        border-radius: var(--radius-lg);
        border-width: 1px;
        border-style: solid;

        .profile-roles {
            display: flex;
            flex-wrap: wrap;
            gap: var(--space-xs);
            padding: var(--space-sm) var(--space-ms);
        }

        ul {
            margin: 0;
            padding-inline-start: 0;
        }

        li {
            list-style: none;
            margin: 0;
            display: flex;
            align-items: center;

            a,
            button.about-open {
                display: flex;
                align-items: center;
                width: 100%;
                padding: var(--space-sm) var(--space-ms);
                border: none;
                background: none;
                cursor: pointer;
                font: inherit;
                text-align: left;
                text-transform: none;
                font-weight: var(--typography-fontWeightMedium);
                border-radius: var(--radius-md);
            }
        }
    }

    .menu-debug {
        margin-top: var(--space-xs);
        padding: var(--space-sm) var(--space-ms);
        border-top-width: 1px;
        border-top-style: solid;
        font-size: var(--font-size-xs);
    }

    .about-dialog {
        border-width: 1px;
        border-style: solid;
        border-radius: var(--radius-lg);
        padding: var(--space-lg);
        max-width: 320px;

        h3 {
            margin: 0 0 var(--space-sm);
        }

        p {
            margin: var(--space-xs) 0;
        }
    }
}

/* Profile switcher overlay — composes the .modal baseline; only the
   per-profile row layout is feature-specific. */
.profile-switch-modal {
    .modal-footer {
        justify-content: space-between;
    }

    .profile-switch-list {
        margin: var(--space-md) 0 0;
        padding-inline-start: 0;
        display: flex;
        flex-direction: column;
        gap: var(--space-sm);
    }

    .profile-switch-row {
        list-style: none;
        display: flex;
        align-items: center;
        justify-content: space-between;
        gap: var(--space-md);
        padding: var(--space-sm) 0;
        border-top-width: 1px;
        border-top-style: solid;

        form {
            margin: 0;
        }
    }

    .profile-switch-identity {
        display: flex;
        align-items: center;
        gap: var(--space-sm);
        min-width: 0;
    }

    .profile-switch-avatar {
        flex-shrink: 0;
        width: 34px;
        height: 34px;

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

    .profile-switch-names {
        display: flex;
        flex-direction: column;
        line-height: 1.25;
        min-width: 0;
    }

    .profile-switch-roles {
        font-size: var(--font-size-xs);
    }
}

/* Mobile: the user menu (reached via the app-bar avatar) flies in from the top
   and covers the app bar, rather than the desktop upward popover.
   In features.css so it wins the cascade over the desktop .menu position. */
@media screen and (max-width: 768px) {
    /* Mobile top-sheet skin: square the top corners. The top-sheet positioning
       itself lives in layout.css. */
    div.profile-menu .menu {
        border-radius: 0 0 var(--radius-lg) var(--radius-lg);
    }
}

nav.main-nav {
    overflow: hidden;

    a,
    a:visited {
        font-size: var(--font-size-sm);
        font-weight: var(--typography-fontWeightRegular);
        cursor: pointer;
        /* Fill the row, don't hard-code the sidebar's px width — a
           fixed width ignored the row's own 8px inset (the li's padding)
           and overhung the nav's overflow:hidden clip edge by that same
           8px at any --sidebar-width. border-box makes the lvl-N
           padding-left values eat into the 100%, not add to it. Stays
           inline-block — width:100% still resolves against the li's
           content box, so this is a geometry fix only, not a row-height one. */
        width: 100%;
        box-sizing: border-box;
        display: inline-block;

        &.lvl-0 {
            padding-left: 0;
        }

        &.lvl-1 {
            padding-left: .9rem;
        }

        &.lvl-2 {
            padding-left: 1.8rem;
        }

        &.lvl-3 {
            padding-left: 2.7rem;
        }

        &.lvl-4 {
            padding-left: 3.6rem;
        }
    }

    div {
        margin: 0;

        i {
            width: 1.3rem;
            text-align: center;
            padding-right: var(--space-sm);
        }
    }

    /* One divider under the identity/tab zone, and one under the season
       chooser. Replaces the old blanket `div { border-bottom }`, which
       drew a stray line under every nav div (the doubled line under the tabs).
       Colours live in colors.css. */
    .nav-identity,
    .season-chooser {
        border-bottom-width: 1px;
        border-bottom-style: solid;
    }

    menu {
        list-style: none;
        padding: 0;
        margin: 0;

        li {
            padding: 0.125rem var(--space-sm);

            span {
                display: block;
                padding: 0.6rem 0;
            }

            i {
                width: 1.3rem;
                text-align: center;
                padding-right: var(--space-sm);
            }
        }

        li.compactMenuLink {
            margin-top: 0;
            padding-top: 0;
            padding-bottom: 0;
        }

        /* Empty-state / creation CTA inside a nav section.
           Renders as a regular list item — no border, no button shape,
           same padding/density — with the plus glyph sitting in the text
           column (not the icon column) so it reads as a child of the
           section rather than a peer. CTA-ness is carried by the visible
           glyph + secondary accent color (colors in colors.css). */
        li.cta-menu-link a.cta-link {
            text-decoration: none;
            /* Shift the link's content into the text column so the +
               glyph aligns with where sibling-item text begins (regular
               items reserve the icon column via li > i width: 1.3rem +
               space-sm). */
            padding-left: calc(1.3rem + var(--space-sm));
        }

        li.cta-menu-link a.cta-link i {
            /* Don't reserve the icon column — the glyph lives inline with
               the text it precedes. --space-sm (vs the xs gap regular
               items use) gives the + a small but visible separation
               from "Create New..." without breaking the line. */
            width: auto;
            padding-right: var(--space-sm);
        }

        li:has(> span) {
            margin-top: .6rem;
        }

        li:last-child {
            margin-bottom: .7rem;
        }

        /* Site group (Site Discovery Phase 2): a collapsible per-site tier.
           padding:0 so the nested menu rows keep the standard 20px indent and
           the active stripe (colors.css) sits flush at the sidebar edge;
           li:has(> span) still grants the group the .6rem heading top-margin,
           balancing it with the Pages heading. Active/open is applied
           client-side from localStorage (warhol-apps.js). */
        li.site {
            padding: 0;
        }

        li.site > .siteheading {
            padding-left: var(--space-sm);
            cursor: pointer;
            /* Was inheriting the large default — bring it in line with the rest
               of the nav (the icon sizes with the font). */
            font-size: var(--font-size-sm);
            font-weight: var(--typography-fontWeightRegular);
        }

        li.site:not(.open) .sitebody {
            display: none;
        }
    }
}

/* Lighter-icon nav treatment: render the main + personal nav
   icons at the regular (outline) weight so the heavy fa-solid items match the
   personal band's fa-regular heart/profile icons. FA Pro selects the variant by
   font-weight, so this lightens every glyph without editing the per-item icon
   classes server-side. (Scoped to `menu` — the app tabs are not
   touched.) Text is already --typography-fontWeightLight on the links. */
nav.main-nav menu i {
    font-weight: var(--typography-fontWeightRegular);
}

/* Section heading labels: the in-app sections stay, but read
   as quiet small labels rather than the retired heavy grey blocks. Overrides the
   generic `menu li span` (block, 0.6rem). A hairline above every heading but the
   first separates the groups (colour in colors.css). Indentation/page-tree
   (lvl-N) and site headings (.siteheading) are untouched. */
nav.main-nav menu li > span.nav-heading {
    padding: var(--space-sm) 0 var(--space-xs);
    font-size: var(--font-size-sm);
    /* Regular, not Medium — carries the lighter style to the heading text so it
       matches its now-regular-weight icon (the grey colour still sets it apart
       as a label). */
    font-weight: var(--typography-fontWeightRegular);
}

nav.main-nav menu li:has(> span.nav-heading):not(:first-child) {
    border-top-width: 1px;
    border-top-style: solid;
    margin-top: var(--space-sm);
    padding-top: var(--space-xs);
}

/* Personal band: a divider above the pinned Favourites + My
   profile cluster — no section label. The generic nav `menu` reset zeroes
   padding; restore a little vertical breathing room so the rows aren't flush
   against the divider and the anchor below. Divider colour is in colors.css. */
nav.main-nav menu.nav-personal {
    border-top-width: 1px;
    border-top-style: solid;
    padding-block: var(--space-xs);
}

main {
    padding-left: 4%;
    padding-right: 4%;

    /* Description text under a page title wraps at the same half-fluid measure
       as forms and lists — full-bleed paragraphs read as overflow next to the
       bounded cards below them. Deliberately paragraphs only:
       tables, grids, and report/dashboard content keep their own widths. */
    & > p {
        width: var(--content-measure);
        max-width: var(--form-measure-max);
        box-sizing: border-box;
    }
    /* End-of-page air: without it the last element (most visibly a form's
       bottom action bar) sits flush against the viewport edge and reads as if
       more content should scroll. 2rem because last elements carry ~1rem
       margins of their own — 1rem alone still reads tight. */
    padding-bottom: var(--space-xl);

    table {
        border-spacing: 0;
        border-style: solid;
        border-width: 1px;
        border-radius: var(--radius-lg);

        thead td,
        thead th {
            font-weight: var(--typography-fontWeightMedium);
            text-align: start;
        }

        td, th {
            margin: 0;
            padding: var(--space-sm) var(--space-ms);
        }

        thead th {
            border-bottom-style: solid;
            border-bottom-width: 1px;
        }

        tbody td {
            border-bottom-style: solid;
            border-bottom-width: 1px;
        }
    }

    aside.ensembleHomeQuickLinks {
        border-spacing: 0;
        border-style: solid;
        border-width: 1px;
        border-radius: var(--radius-lg);
        padding: 0 var(--space-md) var(--space-md) var(--space-md);

        a.button {
            width: 100%;
        }
    }
}

/* Standalone page shell — error pages and the login-failure page render
   outside the app shell (no side nav, and possibly no session at all), so
   they carry their own org bar and push the shared footer to the bottom. */
.standalone-page {
    display: flex;
    flex-direction: column;
    min-height: 100%;
}

.standalone-page > main {
    flex-grow: 1;
    padding-top: var(--space-lg);
}

.standalone-bar {
    display: flex;
    align-items: center;
    gap: var(--space-md);
    padding: var(--space-ms) var(--space-md);
}

.standalone-bar-org {
    display: flex;
    align-items: center;
    gap: var(--space-ms);
    min-width: 0;
    text-decoration: none;
}

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

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

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

.standalone-bar-app {
    font-size: var(--font-size-sm);
}

/* The error card carries the page's only <h1>, so the heading outline stays
   flat; it needs the margin reset the card's usual h3 title already has. */
.errorpage {
    max-width: var(--form-measure-wide);

    .card-title h1 {
        margin: 0;
    }
}

footer {
    padding-top: var(--space-md);
    display: flex;
    flex-direction: column;
    justify-content: center;

    nav,
    div {
        padding: 0.2rem;
        margin: 0 auto;
        justify-content: center;
    }

    nav {
        menu {
            list-style-type: none;
            justify-content: center;
            margin-block-start: 0;
            margin-block-end: 0;
            padding-inline-start: 0;
            overflow: hidden;

            & > li {
                float: left;
                margin: 0 var(--space-sm);
            }
        }
    }

    section {
        margin: var(--space-sm) auto;
    }
}

.render-attributes {
    display: flex;
    flex-direction: column;

    label {
        display: block;
        margin: 0.75em 0 0 0;

        .name {
            padding-right: 0.2em;
        }

        .value {
            font-weight: var(--typography-fontWeightBold);
        }
    }
}

.avatar-edit-ui {
    a {
        display: block;
        width: 100%;
        text-align: center;
    }
}

.avatar-samples {
    display: flex;
    flex-direction: row;
    justify-content: space-evenly;
    align-items: center;
    margin: var(--space-md) 0;
    width: 100%;

    img {
        display: block;
        padding: var(--space-ms);
        border-style: solid;
        border-width: 1px;
        /* Pin the box so a broken upstream image (Google rate-limit, dead URL)
         * renders the broken-image glyph inside a stable rectangle instead of
         * collapsing to glyph+alt-text dimensions and reflowing the form. The
         * recommended source size is 122x122 (see profilePicture macro help). */
        width: 122px;
        height: 122px;
        object-fit: cover;
    }
}

form.switch-profile-form {
    display: block;
}

.copyIcon {
    cursor: pointer;
    width: 20px;
    height: 20px;
    display: inline-block;
    margin-bottom: -4px;
}

.rte-toolbar {
    margin: 0.6rem 0 0 0;
    padding: 0.4rem;
    border-radius: var(--radius-lg);

    .rte-toolbar-row {
        margin: 0;
        padding: 0 0 0.2rem 0;
    }

    section {
        display: inline;
        margin: 0;
        padding: 0 0.4rem;
        border-right-style: solid;
        border-right-width: thin;
    }

    span.rte-btn {
        margin: var(--space-xs) 0;
        padding: var(--space-xs) var(--space-sm);
        border-radius: var(--radius-sm);
        cursor: pointer;
    }

    /* Block-format buttons carry a text label (P / H1 / H2 / H3) instead of an
       icon — keep them legible and evenly sized next to the icon buttons. */
    span.rte-btn.rte-block {
        min-width: 1.8em;
        text-align: center;
        font-weight: var(--typography-fontWeightMedium);
        font-size: var(--font-size-sm);
    }

    /* External-image button: an image glyph with a small globe
       badge in the corner, signalling a web-hosted (non-entity) image. */
    .rte-ext-img {
        position: relative;
        display: inline-block;
    }

    .rte-ext-img-badge {
        position: absolute;
        right: -0.4em;
        bottom: -0.3em;
        font-size: 0.6em;
    }

    .space {
        padding: 0 0.2rem;
    }

    span.space {
        padding: 0 0.4rem;
    }
}

/* Instrument band heading — a light, hairline-underlined divider spanning the
   roster grid (positional rules live in layout.css). */
.roster-section-title {
    margin: var(--space-md) 0 var(--space-sm);
    padding-bottom: var(--space-xs);
    /* A size above the member-name titles (font-size-lg) so the instrument band
       header clearly outranks the names beneath it in both views. */
    font-size: var(--font-size-xl);
    font-weight: var(--typography-fontWeightSemiBold);
    border-bottom-width: 1px;
    border-bottom-style: solid;
}

.rosterDisplay {
    .profileCard {
        img {
            width: 100px;
            height: 100px;
            margin-right: 8px;
            border-radius: var(--radius-md);
        }
    }
}

/* ===== Roster member: Show Photos / Compact ===== */
/* Compact view shows the bare name; Show Photos shows the TSO-style card. The grid
   carries .compact-roster in compact view (warhol-apps.js); default markup is Show
   Photos. NB: the modifier is deliberately NOT named .compact — the global .compact
   typography utility applies a 0.75 horizontal scale that would squish the grid. */
.roster-grid .roster-compact-name {
    display: none;
}
.roster-grid.compact-roster .roster-compact-name {
    display: block;
    padding: var(--space-xs) 0;
}
.roster-grid.compact-roster .roster-card {
    display: none;
}

/* TSO-style card: a square headshot bleeds to the card edges (the .card component's
   overflow rounds the top), then a body with the name as title, instrument +
   section-lead flag beneath, and the contact/role detail block. */
.roster-card {
    display: flex;
    flex-direction: column;
}
.roster-card-photo {
    aspect-ratio: 1 / 1;
    width: 100%;
}
.roster-card-photo .avatar-square {
    width: 100%;
    height: 100%;
    border-radius: 0;
    object-fit: cover;
    display: block;
}
.roster-card-body {
    display: flex;
    flex-direction: column;
    gap: var(--space-xs);
    padding: var(--space-md);
}
.roster-card-head {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    gap: var(--space-sm);
}
.roster-card-name {
    margin: 0;
    font-size: var(--font-size-lg);
    font-weight: var(--typography-fontWeightSemiBold);
    line-height: 1.2;
}
.roster-card-actions {
    display: flex;
    flex-shrink: 0;
    gap: var(--space-sm);
}
.roster-card-subtitle {
    margin: 0;
    font-size: var(--font-size-sm);
}
.roster-card-meta {
    display: flex;
    flex-direction: column;
    gap: var(--space-xs);
    margin-top: var(--space-sm);
    padding-top: var(--space-sm);
    border-top-width: 1px;
    border-top-style: solid;
    font-size: var(--font-size-sm);
}
.roster-card-meta-row {
    display: flex;
    align-items: baseline;
    gap: var(--space-sm);
    min-width: 0;
}
.roster-card-meta-row i {
    width: 1rem;
    flex-shrink: 0;
    text-align: center;
}
.roster-card-meta-row a,
.roster-card-meta-row span {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.attendanceTable {
    margin-top: var(--space-md);
}

.attendance-expected-yes,
.attendance-expected-maybe,
.attendance-expected-no {
    blockquote {
        padding: 0 var(--space-xl);
    }

    button {
        line-height: 1;
        box-shadow: none;
        border: none;
    }
}

.attendance-track-buttons button {
    width: 5.5rem;
}

/* ============= WEBSOCKET ATTENDANCE STATUS ============= */

/* The connection pill is a system badge (badge badge-pill + semantic variant,
   set by the [ws-connect] status listener in warhol-apps.js per state); only the
   placement margin and the per-state dot animations live here. The dot is the
   system .status-dot, tinted by the badge variant's text color via currentColor. */
.ws-status {
    margin-bottom: var(--space-md);
}

.ws-status-connected .status-dot {
    animation: wsPulse 2s infinite;
}

.ws-status-connecting .status-dot {
    animation: wsBlink 1s infinite;
}

@keyframes wsPulse {
    0%, 100% {
        transform: scale(1);
        opacity: 1;
    }
    50% {
        transform: scale(1.2);
        opacity: 0.7;
    }
}

@keyframes wsBlink {
    0%, 100% {
        opacity: 1;
    }
    50% {
        opacity: 0.3;
    }
}

/* Row update animation, added by the delegated htmx:oobAfterSwap listener. */
tr.ws-updated {
    animation: wsRowUpdate 1s ease-out;
}

@keyframes wsRowUpdate {
    0% {
        background-color: rgba(76, 175, 80, 0.3);
    }
    100% {
        background-color: transparent;
    }
}

/* ============= END WEBSOCKET ATTENDANCE STATUS ============= */

/* ============= ATTENDANCE GRID ============= */

/* Filter bar: event-type + date-range controls above the grid.
   Each field is a single line (label inline with its control); the three fields
   sit abreast on wide screens, then wrap and finally stack as the viewport
   narrows. */
.att-filter {
    display: flex;
    /* Override the global `form { flex-direction: column }` (forms.css) that
       would otherwise stack these fields vertically. */
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-sm) var(--space-md);
    margin-bottom: var(--space-md);
}

.att-filter-field {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    flex: 1 1 14rem;
    /* Neutralise the global `label { width: 100%; margin: var(--space-md) }`
       (forms.css) — full-width labels would re-stack the row and the margin is
       what blew up the vertical spacing. */
    width: auto;
    margin: 0;
    min-width: 0;
    font-weight: var(--typography-fontWeightMedium);
}

.att-filter-field > span {
    white-space: nowrap;
}

.att-filter-field select,
.att-filter-field input {
    flex: 1 1 auto;
    min-width: 0;
}

.att-filter-actions {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    flex: 0 0 auto;
}

/* Bounded scroll region that fills the visible main pane. Its height is set
   precisely by warhol-apps.js; the calc here is the pre-JS paint fallback.
   The grid scrolls inside this frame so the page title and the sticky header,
   left columns and footer stay put. */
.attendance-grid-scroll {
    max-height: calc(100dvh - 12rem);
    overflow: auto;
    border-style: solid;
    border-width: 1px;
    border-radius: var(--radius-lg);
}

.attendance-grid {
    border: none;
    border-radius: 0;
}

.attendance-grid th,
.attendance-grid td {
    padding: var(--space-xs) var(--space-sm);
    text-align: center;
    white-space: nowrap;
}

/* Two frozen left columns: member name (left:0) and that member's tallies
   (offset by the name-column width). Both stay visible as the date columns
   scroll. The totals column's left offset must equal the name width. */
.attendance-grid .att-name-col {
    position: sticky;
    left: 0;
    z-index: 9;
    width: 12rem;
    max-width: 12rem;
    overflow: hidden;
    text-align: start;
    text-overflow: ellipsis;
    font-weight: var(--typography-fontWeightMedium);
}

.attendance-grid .att-totals-col {
    position: sticky;
    left: 12rem;
    z-index: 9;
    width: 5rem;
    min-width: 5rem;
}

/* Sticky header row; corners pin to both edges so they out-rank the columns. */
.attendance-grid thead th {
    position: sticky;
    top: 0;
    z-index: 10;
}

.attendance-grid thead th.att-corner {
    z-index: 12;
    vertical-align: bottom;
}

.attendance-grid thead th.att-corner.att-totals-col {
    left: 12rem;
}

/* Rotated date headers. The cell is a fixed one-line-height-wide box; the label
   is taken out of flow and rotated about its bottom-left corner, so the column
   never widens and there is no negative-margin alignment fight. */
.attendance-grid th.att-col-head {
    /* sticky (not relative) so the date headers pin to the top while still
       acting as the positioning context for the absolutely-placed label;
       relative here would out-specify the thead sticky rule and scroll away. */
    position: sticky;
    top: 0;
    z-index: 10;
    width: 1.9rem;
    min-width: 1.9rem;
    height: 7rem;
    padding: 0;
    vertical-align: bottom;
    font-weight: var(--typography-fontWeightMedium);
}

.attendance-grid th.att-col-head > span {
    position: absolute;
    bottom: var(--space-sm);
    left: 50%;
    transform-origin: left bottom;
    transform: rotate(-90deg);
    white-space: nowrap;
    font-size: var(--font-size-xs);
}

/* Instrument band: label pinned left so it stays visible while scrolling. */
.attendance-grid .att-instrument {
    position: sticky;
    left: 0;
    z-index: 9;
    text-align: start;
    font-weight: var(--typography-fontWeightBlack);
    font-size: var(--font-size-sm);
}

.attendance-grid .att-cell {
    width: 1.9rem;
    min-width: 1.9rem;
}

/* Tallies: three compact counts. The per-person column lays them inline; the
   narrow per-event footer column stacks them. */
.att-tally {
    display: inline-block;
    min-width: 1.4rem;
    padding: 0 var(--space-xs);
    font-size: var(--font-size-xs);
    font-weight: var(--typography-fontWeightMedium);
    text-align: center;
}

.attendance-grid tfoot th,
.attendance-grid tfoot td {
    position: sticky;
    bottom: 0;
    z-index: 10;
}

.attendance-grid tfoot .att-foot-label {
    left: 0;
    z-index: 12;
    text-align: start;
    font-weight: var(--typography-fontWeightMedium);
}

.attendance-grid .att-foot-cell {
    padding: var(--space-xs) 0;
}

.attendance-grid .att-foot-cell .att-tally {
    display: block;
    min-width: 0;
}

/* ============= END ATTENDANCE GRID ============= */

table.dues-admin td {
    td {
        padding: var(--space-sm);
    }
}

section.dues-summary,
section.dues-form {
    h3 {
        font-size: 1.3rem;
        text-align: left;
        margin: var(--space-sm) 0 var(--space-sm) 0;
    }

    section.dues-payment {
        margin: var(--space-md);
        padding: var(--space-md);
    }

}

.profileDisplay {
    .profileNameOnCard {
        font-size: var(--font-size-lg);
        font-weight: var(--typography-fontWeightMedium);
    }

    .roster-avatar {
        width: 100px;
        height: 100px;
    }

    .details {
        margin-left: var(--space-lg);
        width: 100%;

        /* Pin the mailto link's bounding box to a fixed width so the
         * visual-test mask covers it identically across runs. The link
         * text contains a random ephemeral email per provisioning
         * (`<initial>.<surname><idx>@<domain>`), so the rendered width
         * varies by 80+ px between runs. The previous workaround
         * (padding-right) only absorbed ~40 px and left a sliver of
         * pixel diff for the longer emails.
         * `overflow: hidden` + `white-space: nowrap` keep any
         * unusually long email from spilling out of the box. */
        a[href^="mailto:"] {
            display: inline-block;
            width: 16rem;
            overflow: hidden;
            white-space: nowrap;
            vertical-align: middle;
        }
    }
}

.registrationCommandButtons {
    ul.registrationCommandOptions {
        list-style-type: none;
        padding-inline-start: 0;

        li {
            display: inline-block;
        }
    }
}

div.scrollable-table {
    overflow-x: auto;
    overflow-y: auto;
    width: 100%;
}

/* When a scrollable table is bound to the half-fluid measure (@kdf.listTable),
   the measure must beat this rule's width: 100% (same layer, higher specificity).
   The registration roster's bare .scrollable-table stays deliberately full-width. */
div.scrollable-table.content-measure {
    width: var(--content-measure);
}

.registrationProfileTable {
    .registrationCommandButtons {
        ul.registrationCommandOptions {
            border-radius: var(--radius-lg);
            padding: 10px;
            width: 200px;
            transition: var(--transition-style);
            box-shadow: var(--shadow-4);

            margin-left: -181px;

            li {
                display: block;
                padding: 8px 10px 8px 20px;
                font-weight: var(--typography-fontWeightMedium);
            }

            li.closeMenu {
                padding: 0;
                float: right;
                text-transform: uppercase;
                font-size: smaller;
            }
        }
    }

    th {
        padding-top: 0.8rem;
        text-wrap: nowrap;
    }

    /* Re-center the colspan'd group labels in the first thead row
       (Profile / Registration / Basic Roles / Advanced Roles / Power Roles).
       The default left-justify rule breaks the visual
       association between each group label and its child columns; the
       second row holds vertical-lr leaf headers where horizontal text-align
       has no visible effect, so this exception only changes group labels. */
    thead tr:first-child th {
        text-align: center;
    }

    th span {
        writing-mode: vertical-lr;
    }

    th.reg-tbl-pid span,
    th.reg-tbl-handle span,
    th.reg-tbl-leave span {
        writing-mode: horizontal-tb;
    }

    .reg-tbl-pid {
        width: 40px;
        min-width: 40px;
        max-width: 40px;
    }

    .reg-tbl-handle {
        min-width: 160px;
        width: 160px;
        max-width: 160px;
        white-space: nowrap;
        overflow: scroll;
        scrollbar-width: none;
    }

    .reg-tbl-started {
        border-left-style: solid;
        border-left-width: 5px;
    }

    .reg-tbl-started,
    .reg-tbl-profile,
    .reg-tbl-emerg,
    .reg-tbl-agree,
    .reg-tbl-status,
    .reg-tbl-actions {
        width: 20px;
        max-width: 20px;
        min-width: 20px;
    }

    /* The leave chip is a word, unlike every other registration cell's glyph. */
    .reg-tbl-leave {
        width: 120px;
        min-width: 120px;
        max-width: 120px;
        white-space: nowrap;
    }

    .reg-tbl-actions,
    .reg-tbl-alumni,
    .reg-tbl-first-aid {
        border-right-style: solid;
        border-right-width: 5px;
    }
}

/* Registration controls, WCAG 2.5.5 44px touch target at phone width. */
@media screen and (max-width: 768px) {
    .reg-start-button,
    #registrationForm .form-actions .button {
        min-height: 2.75rem;
    }

    .reg-cancel-link,
    .registration-progress td a,
    .registrationCommandOptions li a {
        display: inline-flex;
        align-items: center;
        min-height: 2.75rem;
    }

    .registrationProfileTable .reg-tbl-actions {
        width: 2.75rem;
        min-width: 2.75rem;
        max-width: 2.75rem;
    }

    .registrationCommandButtons .openMenu {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        min-width: 2.75rem;
        min-height: 2.75rem;
    }

    /* The 44px entries make a 7-item menu 464px tall, which is taller than a phone
       viewport in landscape and taller than the room below its trigger in portrait.
       Bound the open menu to the viewport and let it scroll, rather than trailing
       entries off the bottom with no scroller of their own. */
    .registrationProfileTable ul.registrationCommandOptions {
        max-height: 70vh;
        overflow-y: auto;
    }
}

.email-attendees-button {
    align-self: start;
    justify-self: center;
}

.role-grid-rolename-heading {
    writing-mode: vertical-rl;
    rotate: 210deg;
    border: none;
}

.page-info {
    padding: var(--space-md);

    h2 {
        margin-top: 0;
        margin-bottom: var(--space-md);
    }

    h3 {
        margin-top: var(--space-lg);
        margin-bottom: var(--space-sm);
    }
}

/* ============= CMS INFO PAGE ============= */

.info-page {
    max-width: 800px;
}

.info-page .info-section {
    margin-bottom: var(--space-xl);
    padding-bottom: var(--space-lg);
    border-bottom-width: 1px;
    border-bottom-style: solid;

    &:last-child {
        border-bottom: none;
    }

    h3 {
        margin-top: 0;
        margin-bottom: var(--space-md);
        font-size: var(--font-size-xl);
        font-weight: var(--typography-fontWeightMedium);

        i {
            margin-right: var(--space-sm);
            opacity: 0.7;
        }
    }

    h4 {
        margin-top: var(--space-lg);
        margin-bottom: var(--space-ms);
        font-size: var(--font-size-base);
        font-weight: var(--typography-fontWeightMedium);
    }
}

.info-page .status-description {
    margin: var(--space-md) 0;
    padding: var(--space-ms) 1rem;
    border-radius: var(--radius-md);

    p {
        margin: 0;
        font-size: 0.9rem;
    }
}

.info-page .publishing-controls {
    margin-top: var(--space-lg);

    .help-text {
        font-size: var(--font-size-sm);
        margin: 0;

        i {
            margin-right: var(--space-xs);
        }
    }
}

/* Publishing action rows: independent semantic actions
   (Approve / Publish now / Cancel...), NOT a segmented control — plain
   buttons in a wrapping row. `.pubset-actions` is the publish-set detail
   screen's row; `.entity-panel-actions` is the per-entity publishing
   panel's — same shape, separate classes. */
.pubset-actions,
.entity-panel-actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-sm);
    margin-bottom: var(--space-ms);
}

.info-page .version-info {
    ul {
        margin: var(--space-sm) 0 var(--space-lg) 0;
        padding-left: var(--space-lg);

        li {
            margin-bottom: var(--space-sm);
            line-height: 1.5;
        }
    }
}

/* ============= CMS PAGE TREE NAVIGATION ============= */

nav.main-nav menu li.page-tree-item {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto;
    align-items: center;
    position: relative;

    /* 24px gap between same-level sibling rows (WCAG 2.5.8 target-size
       spacing). One-sided so it collapses cleanly with the previous
       row's zero margin-bottom instead of summing; must stay listed in
       both collapse-state transitions below. Every page-tree row also
       carries .compactMenuLink, whose `margin-top: 0` (above) has
       identical specificity — this wins only by coming later in the
       file; don't reorder these two blocks. */
    margin-top: var(--space-lg);

    /* The collapse/expand transition animates this grid's row track
       (0fr <-> 1fr) rather than max-height, so every direct child
       needs its own height transition to move in lockstep. */
    > * {
        min-height: 0;
        transition: padding-block 250ms ease, height 250ms ease;
    }

    > a, > .page-tree-phantom {
        overflow: hidden;
    }

    /* Toggle button for pages with children */

    .page-tree-toggle {
        width: 1.2rem;
        height: 1.2rem;
        padding: 0;
        margin: 0;
        border: none;
        box-shadow: none;
        outline: none;
        background: transparent;
        cursor: pointer;
        display: flex;
        align-items: center;
        justify-content: center;
        /* The toggle's own height/padding-block transitions beat the
           generic `> *` rule above on specificity, so they have to be
           repeated here or the toggle's height snaps instead of
           animating with the rest of a toggle-bearing collapsed row. */
        transition: transform 250ms ease, height 250ms ease, padding-block 250ms ease;

        i {
            font-size: 0.7rem;
            padding: 0;
            width: auto;
        }

        &:hover {
            opacity: 0.7;
        }

        /* outline: none above had no replacement. */
        &:focus-visible {
            outline-width: 2px;
            outline-style: solid;
            outline-offset: 2px;
        }

        &[aria-expanded="true"] {
            transform: rotate(90deg);
        }
    }

    /* Placeholder for pages without children - maintains consistent indentation */

    .page-tree-toggle-placeholder {
        width: 1.2rem;
        height: .1rem;
        margin: 0 0.1rem 0 0;
        display: inline-block;
    }

    /* Phantom path segments — URL prefixes that have descendant pages
       but no page at the prefix itself (e.g. /events when only
       /events/<slug> pages exist). Rendered by the builder as a plain
       span carrying the same lvl-N class an anchor would. Style to
       match anchor siblings so the label aligns; the muted colour
       (colors.css) + default cursor signal the absence of a click
       target. */
    .page-tree-phantom {
        /* Match the leaf links so folder path-segments and links read as one
           scale (was --font-size-lg, which looked oversized). */
        font-size: var(--font-size-sm);
        font-weight: var(--typography-fontWeightRegular);
        display: inline-block;
        cursor: default;
    }

    /* The link itself (not its <li>) is the click target, and it was sized
       by line-height alone (~21px) — under the WCAG 2.2 24x24px minimum.
       Padding-block grows the anchor to ~29px. The phantom span goes the
       other way: this selector's specificity beats the generic
       `menu li span { padding: 0.6rem 0 }` it used to inherit (~40px), so
       it drops to the same ~29px. First time link rows and phantom rows
       land at the same height. */
    a, .page-tree-phantom {
        padding-block: var(--space-xs);
    }

    /* Page-tree links don't render a per-page <i> glyph. lvl-0 is offset
       by --page-tree-base so Home aligns with non-tree top-level items
       like the Administration entries (the chevron button is 0.6rem
       narrower than the icon column those items use). Each deeper level
       adds --page-tree-step. Phantom spans share the same indent ladder
       via the doubled selector. */
    --page-tree-base: 0.6rem;
    --page-tree-step: 0.55rem;
    a.lvl-0, .page-tree-phantom.lvl-0 { padding-left: var(--page-tree-base); }
    a.lvl-1, .page-tree-phantom.lvl-1 { padding-left: calc(var(--page-tree-base) + 1 * var(--page-tree-step)); }
    a.lvl-2, .page-tree-phantom.lvl-2 { padding-left: calc(var(--page-tree-base) + 2 * var(--page-tree-step)); }
    a.lvl-3, .page-tree-phantom.lvl-3 { padding-left: calc(var(--page-tree-base) + 3 * var(--page-tree-step)); }
    a.lvl-4, .page-tree-phantom.lvl-4 { padding-left: calc(var(--page-tree-base) + 4 * var(--page-tree-step)); }
    a.lvl-5, .page-tree-phantom.lvl-5 { padding-left: calc(var(--page-tree-base) + 5 * var(--page-tree-step)); }
}

/* The sibling-row margin-top above also lands on the first page-tree-item
   right after a section heading (e.g. "Pages"), pushing it as far from its
   heading as any two ordinary rows sit from each other — the heading no
   longer visually "owns" its first item (compare the tight Library ->
   Article spacing elsewhere in the same nav). :not(:has(a)) keeps this to
   non-interactive headings only — AppNav.heading has a clickable-href
   overload (dynamic-main-nav.ftlh wraps it in the same span.nav-heading),
   and a clickable heading is itself a target that still needs the 24px. */
nav.main-nav menu li:has(> span.nav-heading):not(:has(a)) + li.page-tree-item {
    margin-top: 0;
}

/* Hidden state for collapsed children */
nav.main-nav menu li.page-tree-item.page-tree-hidden {
    grid-template-rows: 0fr;
    opacity: 0;
    padding-top: 0;
    padding-bottom: 0;
    margin-top: 0;
    /* overflow:hidden lives here, not on the base li rule — the
       visible state's focus ring (anchor and toggle) needs to paint
       outside the row during expand/collapse without the li's own box
       clipping it; only the collapsed state needs to clip. */
    overflow: hidden;
    transition: grid-template-rows 250ms ease, opacity 200ms ease, padding 250ms ease, margin 250ms ease;

    /* A bare 0fr track is still floored by its children's
       intrinsic minimums (the anchor's padding-block, the toggle/
       placeholder's fixed heights), leaving a ~19px sliver. Zero them
       too, via transition (not an instant snap) so the collapse
       direction animates in step with the row-track collapse — an
       instant zero here made expand animate but collapse pop. This
       beats the base `a, .page-tree-phantom` padding-block rule only by
       source order (same specificity) — keep this block after it. */
    > * {
        padding-block: 0;
    }

    > .page-tree-toggle, > .page-tree-toggle-placeholder {
        height: 0;
    }
}

/* Visible state for expanded children. No height clamp — a wrapped
   two-line row measures ~71px, past the old 64px cap, so the row
   animates via grid-template-rows instead (see the base rule above). */
nav.main-nav menu li.page-tree-item:not(.page-tree-hidden) {
    grid-template-rows: 1fr;
    opacity: 1;
    transition: grid-template-rows 250ms ease, opacity 200ms ease, padding 250ms ease, margin 250ms ease;
}

/* ============= END CMS PAGE TREE NAVIGATION ============= */

/* ============= FAVOURITE BUTTON ============= */

/* The standalone favourite toggle is a system icon-button; the .favourited
   heart colors live in colors.css. */

/* ============= END FAVOURITE BUTTON ============= */

/* ============= CMS PREVIEW COMPONENTS ============= */

.preview-container {
    display: flex;
    flex-direction: column;
    height: calc(100vh - var(--titlebar-height) - 120px);
    min-height: 400px;
    /* Reserve a strip below the iframe so its box-shadow has somewhere
     * to land. Without this, the left/right edges of the shadow render
     * (they trail off into open layout space on the sides) but the
     * bottom edge sits flush against the CMS footer and gets clipped,
     * making the pane look unbordered along the bottom. */
    padding-bottom: 1rem;
}

.preview-iframe {
    flex: 1;
    width: 100%;
    border: 1px solid var(--divider);
    border-radius: var(--radius-lg);
}

/* Content preview (metadata display) */

.content-preview {
    padding: var(--space-md);
}

.content-preview-info {
    padding: var(--space-md);
    border-radius: var(--radius-lg);
    margin-bottom: var(--space-lg);
    border-style: solid;
    border-width: 1px;

    p {
        margin: 0;

        i {
            margin-right: var(--space-sm);
        }
    }
}

.content-preview-metadata,
.content-preview-attributes {
    margin-bottom: var(--space-lg);

    h3 {
        margin-top: 0;
        margin-bottom: var(--space-md);
        font-size: var(--font-size-xl);
    }

    /* The key/value grids are system info-lists (info-list info-list-lg). */
    dl dd code {
        padding: 0.1rem 0.3rem;
        border-radius: var(--radius-sm);
    }
}

/* ============= END CMS PREVIEW COMPONENTS ============= */

/* ============= COMPOSE ACTIONS ============= */

dl.compose-actions {
    display: grid;
    /* Fixed first-column width so the descriptions in column 2 don't shift
     * when a button label changes width (notably "Send feedback to {orgName}",
     * whose width varies with the org name across visual-test runs). */
    grid-template-columns: 32rem 1fr;
    gap: var(--space-ms) 1.5rem;
    margin: var(--space-md) 0;
    align-items: center;

    dt {
        margin: 0;

        /* Buttons fill the fixed first column with a left-aligned label,
         * so row-to-row jitter from variable label lengths (org-name
         * embed, privilege-gated buttons coming and going) is eliminated.
         * The button's bounding box is stable regardless of label content,
         * so the Playwright mask aligns cleanly without absorbing reflow. */
        a.button {
            width: 100%;
            text-align: left;
        }

        a i {
            margin-right: var(--space-sm);
        }
    }

    dd {
        margin: 0;
    }
}

/* Collapse to a single column on tablet as well as mobile. The desktop
 * grid (32rem + 1fr) doesn't fit alongside the persistent left nav at
 * tablet widths (iPad Pro 11 portrait = 834px); the second column ends
 * up so narrow the descriptions wrap to many lines and the first column
 * overflows. 1024px covers iPad portrait through iPad Pro 12.9 portrait
 * inclusive; landscape and proper desktops keep the two-column grid. */
@media screen and (max-width: 1024px) {
    dl.compose-actions {
        grid-template-columns: 1fr;
        gap: var(--space-xs);

        dt {
            margin-top: var(--space-ms);
        }

        dd {
            margin-bottom: var(--space-sm);
        }
    }
}

/* ============= END COMPOSE ACTIONS ============= */

/* ============= WEBMAIL ============= */

.webmail {
    display: grid;
    grid-template-columns: 180px 1fr;
    gap: 0;
    border-width: 1px;
    border-style: solid;
    border-radius: var(--radius-lg);
    /* No max-height: a long inbox list or a long open message extends the
       page and the document itself scrolls. Simpler than nested-scroll
       containers — and avoids the macOS trackpad case where wheel events
       on the inner iframe weren't reaching the outer column. */
    min-height: 400px;
}

.webmail-folders {
    display: flex;
    flex-direction: column;
    border-right-width: 1px;
    border-right-style: solid;
    padding: var(--space-sm) 0;
}

.webmail-folder {
    display: flex;
    align-items: center;
    gap: var(--space-ms);
    padding: var(--space-ms) 1rem;
    border: none;
    border-radius: 0;
    box-shadow: none;
    text-align: left;
    cursor: pointer;
    transition: background-color 0.15s ease;

    i {
        width: 1.25rem;
        text-align: center;
        opacity: 0.7;
    }

    /* The action-required count sits at the folder's right edge. */
    .count-chip {
        margin-left: auto;
    }

    &:hover {
        box-shadow: none;
    }

    &.active {
        font-weight: var(--typography-fontWeightMedium);

        i {
            opacity: 1;
        }
    }
}

.webmail-content {
    display: flex;
    flex-direction: column;
    overflow-y: auto;
}

.webmail-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    height: 100%;
    min-height: 200px;
    gap: var(--space-md);

    i {
        font-size: 3rem;
        opacity: 0.4;
    }

    p {
        margin: 0;
        font-size: 0.95rem;
    }
}

/* Message List */
.message-list {
    display: flex;
    flex-direction: column;
}

.message-row {
    display: flex;
    align-items: flex-start;
    gap: var(--space-md);
    padding: 0.875rem 1rem;
    border-bottom-width: 1px;
    border-bottom-style: solid;
    cursor: pointer;
    transition: background-color 0.15s ease;

    &:last-child {
        border-bottom: none;
    }
}

.message-row-icon {
    flex-shrink: 0;
    width: 2rem;
    height: 2rem;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;

    i {
        font-size: var(--font-size-sm);
    }
}

.message-row-main {
    flex: 1;
    min-width: 0;
}

.message-row-header {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: var(--space-md);
    margin-bottom: var(--space-xs);
}

.message-row-sender {
    font-weight: var(--typography-fontWeightMedium);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.message-row-date {
    flex-shrink: 0;
    font-size: 0.8rem;
}

.message-row-subject {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    font-size: 0.9rem;
}

.message-list .pagination-sentinel {
    padding: var(--space-ms) var(--space-md);
    font-size: var(--font-size-sm);
}

/* Message View — height grows with content; the outer .webmail-content
   column handles scrolling. Avoids a nested-iframe scrollbar where wheel
   events fight the parent for focus. flex-shrink: 0 keeps the view from
   collapsing when .webmail-content (a flex column) tries to fit it inside
   .webmail's max-height — the natural-height view is what makes the parent
   overflow and reveal its scrollbar. */
.message-view {
    display: flex;
    flex-direction: column;
    flex-shrink: 0;
}

.message-view-toolbar {
    display: flex;
    align-items: center;
    padding: var(--space-ms) 1rem;
    border-bottom-width: 1px;
    border-bottom-style: solid;
}

.message-view-back {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    padding: var(--space-sm) var(--space-md);
    font-size: var(--font-size-sm);

    i {
        font-size: var(--font-size-xs);
    }
}

.message-view-header {
    padding: 1.25rem;
    border-bottom-width: 1px;
    border-bottom-style: solid;
}

.message-view-subject {
    margin: 0 0 var(--space-md) 0;
    font-size: 1.35rem;
    font-weight: var(--typography-fontWeightMedium);
    line-height: 1.3;
}

.message-view-meta {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
    font-size: 0.9rem;
}

.message-view-body {
    /* horizontal only */
    padding: 0 var(--space-md);

    iframe {
        display: block;
        width: 100%;
        /* Height is set by initMessageBodyAutoFit() in warhol-apps.js to the
           iframe document's scrollHeight after load. Trackpad / wheel scroll
           then drives the outer .webmail-content column naturally — no inner
           iframe scrollbar to fight for focus. The 300px min-height is the
           pre-fit fallback while the iframe document is still loading. */
        border: none;
        min-height: 300px;
    }
}

@media screen and (max-width: 768px) {
    .webmail {
        grid-template-columns: 1fr;
        grid-template-rows: auto 1fr;
        max-height: none;
    }

    .webmail-folders {
        flex-direction: row;
        flex-wrap: wrap;
        border-right: none;
        border-bottom-width: 1px;
        border-bottom-style: solid;
        padding: var(--space-sm);
        gap: var(--space-xs);
    }

    .webmail-folder {
        padding: 0.5rem var(--space-ms);
        font-size: var(--font-size-sm);
        border-radius: var(--radius-md);

        i {
            width: 1rem;
        }
    }

    .message-row {
        padding: var(--space-ms);
    }

    .message-row-icon {
        width: 1.75rem;
        height: 1.75rem;
    }

    .message-view-header {
        padding: var(--space-md);
    }

    .message-view-subject {
        font-size: 1.15rem;
    }
}

/*
 * Per-message color-scheme toggle.
 * Sits in the .message-view-toolbar next to the Back button. Affects the
 * iframe that renders the email body (which has style isolation via
 * `sandbox="allow-same-origin"`); the webmail UI itself follows the
 * browser-level prefers-color-scheme preference like the rest of the app.
 */
.message-view-toolbar .message-color-scheme-toggle {
    margin-left: auto;
    border-width: 1px;
    border-style: solid;
    border-radius: var(--radius-pill);
    padding: 0.2rem 0.6rem;
    font-size: var(--font-size-sm);
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
}

/* ----- Merged Communications page ----- */

/* Page action bar: title with its primary action right-aligned beside it,
   replacing a list of compose buttons that pushed the messages a viewport down. */
.page-toolbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-md);
    margin-bottom: var(--space-md);

    h1 {
        margin: 0;
    }
}

.page-toolbar-actions {
    display: flex;
    gap: var(--space-sm);
}

/* Compose menu — a <details> disclosure, so it is keyboard-operable with no script. */
.compose-menu {
    position: relative;

    summary {
        list-style: none;
        cursor: pointer;

        &::-webkit-details-marker {
            display: none;
        }
    }
}

.compose-menu-panel {
    top: calc(100% + var(--space-xs));
    right: 0;
    width: min(24rem, 90vw);
    max-height: 60vh;
    overflow-y: auto;
}

.compose-menu-item {
    display: block;
    padding: var(--space-sm) var(--space-ms);
    border-radius: var(--radius-md);
    text-decoration: none;
}

.compose-menu-label {
    display: block;
}

.compose-menu-hint {
    display: block;
    font-size: var(--font-size-sm);
}

/* The rail widens: "Needs you" plus its count chip does not fit 180px under the
   folder button's treatment, and wrapping puts the chip on its own line. */
.webmail.communications {
    grid-template-columns: 200px 1fr;
}

/* Inset the filter bar from the rail's border. Padding rather than margin so a
   wrapped second row of chips stays aligned with the first. */
.communications .webmail-content .chip-bar {
    padding-left: var(--space-sm);
}

/* Section label separating the mailbox folders from role-gated virtual scopes. */
.webmail-folder-divider {
    margin: var(--space-sm) var(--space-md) var(--space-xs);
    padding-top: var(--space-sm);
    border-top-width: 1px;
    border-top-style: solid;
    font-size: var(--font-size-xs);
    text-transform: uppercase;
    letter-spacing: 0.08em;
}

/* Unread marker. A dot rather than bolding the row: it survives the subject's
   ellipsis truncation and does not compete with the sender/subject hierarchy.
   Read rows keep its width so subjects stay aligned down the list. */
.message-row-unread {
    flex-shrink: 0;
    width: var(--space-sm);
    height: var(--space-sm);
    margin-top: var(--space-sm);
    border-radius: 50%;

    &.is-read {
        visibility: hidden;
    }
}

.message-row-recipients {
    display: block;
    font-size: var(--font-size-sm);
}

/* Per-row read and filing actions. Revealed on hover or keyboard focus so the row
   grammar stays subject-first; a reserved column keeps subjects from reflowing. */
.message-row-actions {
    display: flex;
    flex-shrink: 0;
    gap: var(--space-xs);
    visibility: hidden;

    .message-row:hover &,
    .message-row:focus-within & {
        visibility: visible;
    }
}

/* A badge with nothing to say still occupies its id, so an out-of-band swap has
   something to replace when the count comes back. */
.count-chip.is-zero {
    display: none;
}

/* Virtual scopes are views over sent mail, not mailboxes; the pane says so. */
.webmail-scope-note {
    margin: 0 var(--space-md) var(--space-sm);
    padding: var(--space-sm) var(--space-ms);
    border-radius: var(--radius-md);
    font-size: var(--font-size-sm);
}

/* ============= END WEBMAIL ============= */

/* ============= DRAG AND DROP ============= */

.drag-container {
    display: flex;
    gap: var(--space-md);
    flex-wrap: wrap;
}

.drag-sources {
    display: flex;
    flex-direction: column;
    gap: var(--space-md);
    flex: 1;
    min-width: 250px;
}

.drag-source-panel,
.drag-target-panel {
    flex: 1;
    min-width: 250px;
}

.drag-source-panel > label,
.drag-target-panel > label {
    display: block;
    font-weight: bold;
    margin-bottom: var(--space-sm);
}

.drag-source-list,
.drag-target-list {
    border-width: 2px;
    border-style: solid;
    border-radius: var(--radius-md);
    min-height: 120px;
    max-height: 300px;
    overflow-y: auto;
    padding: 0.25rem;
}

.drag-target-list {
    min-height: 200px;
}

/* Composes the system .row-item; only drag affordances remain. */
.drag-item {
    margin: 0.2rem 0;
    cursor: grab;
    user-select: none;
}

.drag-item.group-item {
    border-left-width: 3px;
    border-left-style: solid;
}

/* Insertion marker shown while a source-list item is being dragged over the
   target list. Intra-list reorder uses live repositioning
   of the dragged item itself, so the marker only appears for source -> target
   drags where there is no in-list ghost to follow the cursor. */
.drag-placeholder {
    height: 3px;
    margin: 0.2rem 0.25rem;
    border-radius: 2px;
    pointer-events: none;
}

.drag-handle {
    cursor: grab;
}

.drag-label {
    flex: 1;
}

.drag-item-actions {
    display: flex;
    gap: 0.2rem;
}

.drag-item-actions .button.small {
    padding: 0.15rem 0.3rem;
    font-size: var(--font-size-xs);
    min-width: auto;
}

/* Secondary utility icons inside a drag-item (info popover + open-in-new-tab).
   These are subordinate to the primary action buttons (move/remove) so they
   stay muted by default and only lift to full text color on hover. The
   :visited rule is needed to neutralize the global a:visited color, which
   would otherwise turn the link purple after first click. */
/* Info/open-link utilities in drag rows are system icon-buttons
   (icon-button icon-button-sm). */

/* ============= END DRAG AND DROP ============= */

/* ============= GROUP INFO POPOVER ============= */

/* The group-info dialog is a system anchored modal (modal modal-anchored);
   only its content styling remains below. */

.group-info-header {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--space-sm);
    margin-bottom: var(--space-sm);
}

.group-info-header h4 {
    margin: 0;
    font-size: var(--font-size-base);
}

.group-info-description {
    margin: 0 0 var(--space-ms) 0;
    font-size: var(--font-size-sm);
    white-space: pre-wrap;
}

.group-info-description.muted,
.group-info-attrs li.muted {
    font-style: italic;
}

.group-info-attrs-heading {
    margin: 0 0 var(--space-xs) 0;
    font-size: var(--font-size-xs);
    font-weight: var(--typography-fontWeightSemiBold);
    text-transform: uppercase;
    letter-spacing: 0.05em;
}

.group-info-attrs {
    margin: 0;
    padding: 0;
    list-style: none;
}

.group-info-attrs li {
    font-family: var(--typography-font-family-fixed), monospace;
    font-size: 0.8rem;
    padding: 0.15rem 0;
    border-bottom-width: 1px;
    border-bottom-style: solid;
}

.group-info-attrs li:last-child {
    border-bottom: none;
}

/* ============= TOGGLE-SWITCH SAVE GLOW =============
   `@kdf.toggleSwitch` and `@kdf.roleToggle` PATCH responses return the checkbox
   fragment with a `save-saved` (success) or `save-failed` (failure) class. The
   keyframes run once on settle to confirm the server got the change without
   needing a banner alert. Animation auto-cleans by reaching its final state —
   the class can stay on the element indefinitely once the animation completes. */
input[type=checkbox].save-saved {
    --glow-color: var(--success-main);
    animation: toggle-glow 800ms ease-out;
}

input[type=checkbox].save-failed {
    --glow-color: var(--error-main);
    animation: toggle-glow 800ms ease-out;
}

@keyframes toggle-glow {
    0%   { box-shadow: 0 0 0 0 color-mix(in srgb, var(--glow-color), transparent 100%); }
    25%  { box-shadow: 0 0 8px 2px color-mix(in srgb, var(--glow-color), transparent 30%); }
    100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--glow-color), transparent 100%); }
}

/* ============= END TOGGLE-SWITCH SAVE GLOW ============= */
/* ============= EXTENDED PRIVILEGES — role help text =============
   The role help text is a sibling of its <label>, not a child, so that reading
   what a privilege exposes can never be a click that grants it. Out there it
   loses the indent the label used to give it (the label's own margin plus the
   nested `label div.help-text` rule in forms.css), so restate it once. */
.role-help-text {
    margin-left: var(--space-xl);
}

/* ============= CMS MEDIA — file-dropzone ============= */
/*
 * Drop target for /cms/media/add/{subtypeId} Page 1. Pairs a visible
 * Browse button with the drop target so the form stays accessible
 * without mouse drag. JS hookup lives in warhol-apps.js
 * (initFileDropzone). Class namespace is .file-dropzone — the bare
 * .dropzone namespace is reserved for an unrelated feature.
 */
.file-dropzone {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-ms);
  padding: 2.5rem 1.5rem;
  border-width: 2px;
  border-style: dashed;
  border-radius: 0.5rem;
  cursor: pointer;
  transition: border-color 0.15s ease, background 0.15s ease;
  text-align: center;
}
.file-dropzone-hint {
  font-size: 1.05rem;
}
.file-dropzone-or {
  font-size: 0.9rem;
}
.file-dropzone-browse {
  /* re-use the base .button typography by composition; this class only
     adds dropzone-local spacing. */
  pointer-events: none; /* the parent <label> handles the click */
}
.file-dropzone-filename {
  font-weight: 500;
  word-break: break-all;
}
.file-dropzone-error {
  font-size: 0.9rem;
}
.file-dropzone-size-hint {
  font-size: 0.85rem;
}

/* Media-list no-title column: a button-sized thumbnail that, on hover, opens
   the same tile card as the entity chooser. The small swatch is sized to match
   the Inspect/Edit row buttons; the popover reuses .entity-chooser-tile-popover
   but anchors left (this is a near-left-edge column) so the 220px card extends
   rightward into the table instead of off-screen. */
.media-thumb {
  position: relative;
  display: inline-block;
}
.media-thumb-img,
.media-thumb-img img {
  width: 1.75rem;
  height: 1.75rem;
  border-radius: var(--radius-md);
  object-fit: cover;
}
.media-thumb-img {
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.9rem;
  overflow: hidden;
}
.media-thumb-popover.entity-chooser-tile-popover {
  left: 0;
  right: auto;
  line-height: normal;
}

/* ============= CMS Enhanced Entity Chooser ============= */

:root {
  --entity-chooser-width: 420px;
  --entity-chooser-collapsed-width: 48px;
  --entity-chooser-strip-actions-min-height: 56px;
}

/* Large iframe "Add" modal. Composes the system .modal
   (components.css) but goes near-fullscreen with a thin frame. Each nested level
   insets a bit more (--modal-add-depth, set inline by warhol-apps.js) so a stack
   reads as depth without wasting space. Rendered in the dialog top layer, so it
   sits above the chooser sidebar regardless of z-index. */
.modal-add {
  --modal-add-depth: 0;
  --modal-add-inset: calc((var(--modal-add-depth) + 1) * var(--space-lg));
  max-width: none;
  max-height: none;
  width: calc(100vw - 2 * var(--modal-add-inset));
  height: calc(100vh - 2 * var(--modal-add-inset));
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.modal-add-frame {
  flex: 1 1 auto;
  width: 100%;
  border: 0;
}

.modal-add-close {
  position: absolute;
  top: var(--space-xs);
  right: var(--space-xs);
  z-index: 1;
}

/* The host element lives once in layout.ftlh and stays empty until an
   editor's Choose… button HTMX-swaps a sidebar partial into it. */
.entity-chooser-host {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  width: var(--entity-chooser-width);
  border-left-width: 1px;
  border-left-style: solid;
  z-index: 950;
  overflow: hidden;
  transform: translateX(100%);
  transition: transform 0.18s ease, width 0.12s ease;
}
body[data-chooser-open] .entity-chooser-host {
  transform: translateX(0);
  box-shadow: var(--shadow-12);
}
body[data-chooser-open] {
  margin-right: var(--entity-chooser-width);
  transition: margin-right 0.18s ease;
}
body.entity-chooser-resizing {
  user-select: none;
  cursor: ew-resize;
}
body.entity-chooser-resizing,
body.entity-chooser-resizing .entity-chooser-host,
body.entity-chooser-resizing.entity-chooser-host {
  transition: none !important;
}

.entity-chooser-sidebar {
  position: relative;
  display: flex;
  flex-direction: column;
  height: 100%;
  width: 100%;
}

.entity-chooser-resize-handle {
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 6px;
  cursor: ew-resize;
  z-index: 1;
}
/* Resize-handle hover/active tint lives in colors.css. */

.entity-chooser-header {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: var(--space-ms) 1rem;
  border-bottom-width: 1px;
  border-bottom-style: solid;
}
.entity-chooser-title {
  flex: 1 1 auto;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin: 0;
  font-size: 1rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.entity-chooser-pills {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
  margin: 0;
  padding: 0;
  width: auto;
}
.entity-chooser-pills:empty {
  display: none;
}
/* Filter pills are system chips (chip chip-sm in a chip-group chip-bar);
   nothing chooser-specific remains at this layer. */

.entity-chooser-controls {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 0.5rem;
  padding: 0.5rem 1rem;
  margin: 0;
  width: auto;
  border-bottom-width: 1px;
  border-bottom-style: solid;
}
.entity-chooser-controls > input,
.entity-chooser-controls > select {
  box-sizing: border-box;
  min-width: 0;
  max-width: 100%;
}
.entity-chooser-search { width: 100%; }
/* Filters: a toggle that expands a per-dimension grid — label + value control
   per row, 1-up when narrow and 2-up when wide (auto-fit). */
.entity-chooser-filter-toggle {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  width: 100%;
  padding: 0.35rem 0.6rem;
  border-width: 1px;
  border-style: solid;
  border-radius: 0.3rem;
  cursor: pointer;
  font-size: 0.9rem;
}
.entity-chooser-filter-caret {
  margin-left: auto;
  font-size: var(--font-size-xs);
  transition: transform 0.15s ease;
}
.entity-chooser-filter-toggle.is-open .entity-chooser-filter-caret {
  transform: rotate(180deg);
}
.entity-chooser-filter-panel {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 0.4rem 0.85rem;
  padding: 0.15rem 0.1rem 0.25rem;
}
.entity-chooser-filter-panel[hidden] {
  display: none;
}
.entity-chooser-filter-row {
  display: grid;
  grid-template-columns: minmax(4.5rem, max-content) 1fr;
  align-items: center;
  gap: 0.5rem;
}
.entity-chooser-filter-label {
  font-size: 0.85rem;
  white-space: nowrap;
}
.entity-chooser-filter-select {
  width: 100%;
  min-width: 0;
}
.entity-chooser-filter-fav {
  justify-self: start;
  font-size: 1.05rem;
}

/* Sort row: icon + label + select. */
.entity-chooser-sort-row {
  display: flex;
  align-items: center;
  gap: 0.4rem;
}
.entity-chooser-sort-icon,
.entity-chooser-sort-label {
  font-size: 0.85rem;
}
.entity-chooser-sort {
  flex: 1 1 auto;
  min-width: 0;
}

.entity-chooser-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  grid-auto-rows: min-content;
  align-content: start;
  gap: var(--space-ms);
  padding: var(--space-ms) 1rem;
  margin: 0;
  width: auto;
  overflow-y: auto;
  flex: 1 1 auto;
}
.entity-chooser-loading,
.entity-chooser-empty,
.entity-chooser-error {
  grid-column: 1 / -1;
  text-align: center;
  padding: 1.5rem;
}

/* Deliberately NOT a .card: the 2px transparent border is the selection
   grammar (selected = primary border), incompatible with card chrome. */
.entity-chooser-tile {
  position: relative;
  border: 2px solid transparent;
  border-radius: var(--radius-md);
  padding: 0.4rem;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  align-items: stretch;
  align-self: start;
  text-align: left;
  transition: border-color 0.15s ease, background 0.15s ease;
}
/* Hover/selected border + background tints live in colors.css. */
/* Thumbnail hero: fixed 4:3 box (no layout shift vs the icon variant),
   anchoring context for the .badge-overlay media-type glyph (#687).
   .thumb-fallback is applied by the broken-image handler, which swaps the
   img for the subtype icon inside the same box. */
.entity-chooser-tile-thumb-box {
  position: relative;
  display: block;
  width: 100%;
  aspect-ratio: 4 / 3;
  border-radius: 0.25rem;
  overflow: hidden;
}
.entity-chooser-tile-thumb-box img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.entity-chooser-tile-thumb-box.thumb-fallback {
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 2.5rem;
}
.entity-chooser-tile-icon {
  width: 100%;
  aspect-ratio: 4 / 3;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 0.25rem;
  font-size: 2.5rem;
}
.entity-chooser-tile-name {
  font-size: 0.9rem;
  font-weight: 500;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ===== CMS Search additions ===== */

/* Favourite heart toggle. On a tile it overlays the top-right corner
   (absolute — the tile keeps its size); in a multi-row it sits inline among
   the row actions. */
/* The favourite toggle is a system icon-button (icon-button icon-button-sm
   icon-button-danger); only chooser-specific placement remains below. */
.entity-chooser-tile > .ec-fav-toggle {
  position: absolute;
  top: 0.35rem;
  right: 0.35rem;
  z-index: 2;
  width: 1.6rem;
  height: 1.6rem;
  border-radius: 50%;
  box-shadow: var(--shadow-1);
}

/* "Favourited" chip renders as a heart, not a dim:value label (heart
   color in colors.css). */
.chip-favourite {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
}

/* CMS toolbar location bar acting as a browser-style omnibox: click anywhere
   in the bar to start typing a search term (text cursor cues this); the input
   replaces the path display while searching and blends into the bar chrome. */
#cms-toolbar-location-bar {
  cursor: text;
}
.cms-toolbar-search-input {
  flex: 1 1 auto;
  min-width: 8rem;
  border: 0;
  background: transparent;
  font: inherit;
  color: inherit;
  outline: none;
  padding: 0;
}
/* The outline: none above had no replacement. */
.cms-toolbar-search-input:focus-visible {
  outline-width: 2px;
  outline-style: solid;
  outline-offset: 2px;
}
/* Reveal the input at the same box height as the static location text so the
   bar doesn't grow when it appears. The global `.search-bar input` rule
   out-specifies the class above and adds 0.25rem vertical padding + a 0.9rem
   font the #cms-toolbar-location span doesn't have, pushing the input ~2-4px
   taller (visible on Brave/Chromium). Match the location text's box at id
   specificity. */
#cms-toolbar-search-input {
  padding: 0;
  border: 0;
  font-size: small;
}
.search-bar.searching #cms-toolbar-location {
  display: none;
}
#cms-toolbar-search {
  cursor: pointer;
}

.entity-chooser-footer {
  display: flex;
  gap: 0.5rem;
  justify-content: flex-end;
  padding: var(--space-ms) 1rem;
  border-top-width: 1px;
  border-top-style: solid;
}

/* Collapsed state: hide everything but the icon strip. */
.entity-chooser-sidebar.collapsed > :not(.entity-chooser-strip):not(.entity-chooser-resize-handle) {
  display: none;
}
body[data-chooser-collapsed][data-chooser-open] {
  margin-right: var(--entity-chooser-collapsed-width);
}
body[data-chooser-collapsed][data-chooser-open] .entity-chooser-host {
  width: var(--entity-chooser-collapsed-width);
}
.entity-chooser-strip {
  display: none;
  flex-direction: column;
  justify-content: space-between;
  height: 100%;
  align-items: center;
  padding: 0.5rem 0;
}
.entity-chooser-sidebar.collapsed .entity-chooser-strip {
  display: flex;
}
.entity-chooser-strip-identity {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.4rem;
  background: none;
  border: 0;
  padding: 0.5rem 0;
  cursor: pointer;
  position: relative;
}
.entity-chooser-strip-identity .chevron {
  font-size: 0.85rem;
}
.entity-chooser-strip-pill-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  position: absolute;
  top: 0.25rem;
  right: 0.25rem;
  display: none;
}
.entity-chooser-sidebar.has-pills .entity-chooser-strip-pill-dot {
  display: block;
}
.entity-chooser-strip-actions {
  min-height: var(--entity-chooser-strip-actions-min-height);
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
}

/* ============= Entity chooser editor field ============= */
.entity-chooser-field,
.entity-chooser-display {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  flex-wrap: wrap;
}
.entity-chooser-selection {
  flex: 1 1 12rem;
  min-width: 0;
  min-height: 2rem;
  padding: 0.35rem 0.6rem;
  border-width: 1px;
  border-style: solid;
  border-radius: 0.25rem;
  font-size: 0.9rem;
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
}
.entity-chooser-selection:empty::before {
  content: "(none selected)";
  font-style: italic;
}
.entity-chooser-selection-name {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.entity-chooser-selection-actions,
.entity-chooser-display-actions {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  margin-left: auto;
}
.entity-chooser-display-label {
  font-weight: 500;
}
.entity-chooser-display-name {
  font-weight: 500;
}
/* "Tile view" preview popover — the same compact card shape used by
   chooser-sidebar tiles, surfaced beside an EntityChooserDisplay chip
   so authors can see a summary of what's being included via the
   reference. See feature-enhanced-entity-chooser.md § Tiles. */
/* Composes the system .popover; placement + width only. */
.entity-chooser-tile-popover {
  top: calc(100% + 0.25rem);
  right: 0;
  width: 220px;
}
.entity-chooser-tile-popover[hidden] {
  display: none;
}
/* Preview-flavour border/background neutralisation lives in colors.css
   (it is part of the tile selection-grammar color set). */
.entity-chooser-tile.entity-chooser-tile-preview {
  cursor: default;
}

/* Mobile bottom sheet: instead of a right dock, the sidebar slides up
   from the bottom and covers the lower portion of the viewport. */
@media (max-width: 640px) {
  body[data-chooser-open] {
    margin-right: 0;
  }
  .entity-chooser-host {
    top: auto;
    left: 0;
    right: 0;
    bottom: 0;
    width: 100%;
    height: 75vh;
    border-left: 0;
    border-top-width: 1px;
    border-top-style: solid;
    transform: translateY(100%);
    transition: transform 0.18s ease;
  }
  body[data-chooser-open] .entity-chooser-host {
    transform: translateY(0);
  }
  .entity-chooser-resize-handle { display: none; }
  body[data-chooser-collapsed][data-chooser-open] .entity-chooser-host {
    height: var(--entity-chooser-collapsed-width);
    width: 100%;
  }

  /* Collapsed mobile bottom sheet: lay the strip out horizontally with
     a centred grab-handle bar at the top to cue "drag/tap to expand".
     Without this the desktop vertical icon column would just be squished
     into 48px of height with no clear affordance. */
  .entity-chooser-sidebar.collapsed .entity-chooser-strip {
    position: relative;
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
    padding: var(--space-ms) 1rem 0;
  }
  .entity-chooser-sidebar.collapsed .entity-chooser-strip::before {
    content: "";
    position: absolute;
    top: 0.35rem;
    left: 50%;
    transform: translateX(-50%);
    width: 36px;
    height: 4px;
    border-radius: 2px;
  }
  .entity-chooser-sidebar.collapsed .entity-chooser-strip-identity {
    flex-direction: row;
    gap: 0.5rem;
    padding: 0;
  }
  .entity-chooser-sidebar.collapsed .entity-chooser-strip-actions {
    min-height: 0;
    width: auto;
    flex-direction: row;
  }
}

/* ============= WEB-SAFE COLOR EDITOR ============= */
/* Admin-app editor for STRING colour attributes. Offers a 216-swatch
 * grid (web-safe palette) plus a manual hex input that accepts any
 * valid #rgb / #rrggbb. JS sync lives in static-assets/js/warhol-apps.js
 * under the "Web-Safe Color editor" section. */
.web-safe-color-field {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}
.web-safe-color-controls {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
.web-safe-color-hex {
  font-family: var(--typography-font-family-fixed);
  width: 10ch;
  text-transform: lowercase;
}
.web-safe-color-grid {
  display: grid;
  grid-template-columns: repeat(36, 1fr);
  gap: 1px;
  max-width: 540px;
}
.web-safe-color-swatch {
  width: 14px;
  height: 14px;
  border-width: 1px;
  border-style: solid;
  padding: 0;
  cursor: pointer;
  box-sizing: border-box;
}
.web-safe-color-swatch[aria-selected="true"] {
  outline-width: 2px;
  outline-style: solid;
  outline-offset: 1px;
  z-index: 1;
}
.web-safe-color-swatch:focus-visible {
  outline-width: 2px;
  outline-style: solid;
  outline-offset: 1px;
  z-index: 1;
}
.web-safe-color-swatch-readonly {
  display: inline-block;
  width: 1.5rem;
  height: 1.5rem;
  vertical-align: middle;
  border-radius: 0.25rem;
  border-width: 1px;
  border-style: solid;
}
.web-safe-color-readonly {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
}
.web-safe-color-readonly-hex {
  font-family: var(--typography-font-family-fixed);
}
/* ============= END WEB-SAFE COLOR EDITOR ============= */

/* ============= MULTI-VALUE ENTITY CHOOSER =============
 * The unified row design used on both the form-side ordered list of
 * selected entities AND inside the chooser sidebar in multi mode.
 * Single-mode chooser keeps its tile-grid layout — these styles apply
 * only to multi-mode rows and to form-side selected lists.
 */
/* Composes the system .row-list. */
.entity-chooser-list {
  margin-top: 0.4rem;
}
.entity-chooser-list:empty {
  margin-top: 0;
}

/* Composes the system .row-item. */
.ec-row {
  min-height: 0;
}

.ec-row-drag-handle {
  flex: 0 0 auto;
  cursor: grab;
  padding: 0.2rem;
  line-height: 1;
}
.ec-row-drag-handle:active {
  cursor: grabbing;
}

.ec-row-thumb {
  position: relative; /* anchors the media-type .badge-overlay (#687) */
  flex: 0 0 28px;
  width: 28px;
  height: 28px;
  border-radius: 4px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 0.9rem;
  overflow: hidden;
}
.ec-row-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* Display-chip thumb box (#687): same fixed square as the multi-row thumb;
   placeholder icon until the chooser index paints the resolved thumbnail. */
.entity-chooser-display-thumb {
  position: relative;
  flex: 0 0 28px;
  width: 28px;
  height: 28px;
  border-radius: 4px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 0.9rem;
  overflow: hidden;
}
.entity-chooser-display-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* Compact overlay sizing on the small fixed thumbs (row + chip): the glyph
   shrinks to corner-chip scale so the poster stays visible. */
.ec-row-thumb .badge-overlay,
.entity-chooser-display-thumb .badge-overlay {
  bottom: 1px;
  left: 1px;
  padding: 0.1rem 0.15rem;
  font-size: 0.55rem;
  border-radius: 0.2rem;
}

.ec-row-body {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  line-height: 1.25;
}
.ec-row-name {
  font-weight: 500;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 0.9rem;
}
.ec-row-meta {
  font-size: 0.72rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ec-row-meta:empty {
  display: none;
}

.ec-row-actions {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: 0.1rem;
}
/* Inert row actions (e.g. Move up/down at a list end) read as genuinely
   unavailable: greyed via the button:disabled colour rule, plus a no-action
   cursor here. */
.ec-row-actions .button-icon:disabled,
.ec-row-actions .button-icon[aria-disabled="true"] {
  cursor: not-allowed;
}

/* Navigation tree editor. Reuses the chooser .ec-row look for
   each node; nesting + indentation come from nested <ul>. */
.nav-editor-header {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.6rem;
}
.nav-tree-toolbar {
  margin: 0.6rem 0;
}
ul.nav-tree {
  list-style: none;
  margin: 0;
  padding: 0;
}
ul.nav-tree-children {
  list-style: none;
  margin: 0.35rem 0 0.35rem 0;
  padding-left: 1.5rem;
  border-left-width: 1px;
  border-left-style: solid;
}
.nav-tree-row {
  margin-top: 0.35rem;
}
.nav-tree-warning {
  font-weight: 500;
}
.nav-action-panel {
  margin: 0.4rem 0 0.8rem;
  padding: 0.8rem;
  border-width: 1px;
  border-style: solid;
  border-radius: var(--radius-md);
}
.nav-root-list {
  max-width: 40rem;
}

/* Drag state. The row being dragged dims; drop-target rows get a thin
 * primary-coloured indicator on top (drop-before) or bottom (drop-after). */
/* Drag states are the system grammar (.dragging/.drop-before/.drop-after/
   .drop-into on .row-item). */

/* Multi-mode Selected list: the ranked picks below the pool
 * grid. The pool keeps the default tile layout (no multi-mode override). The
 * selected rows reuse the form-side .ec-row, made comfortably tall so the
 * thumb + action icons aren't crammed. */
.entity-chooser-selected {
  flex: 0 0 auto;
  max-height: 45%;
  overflow-y: auto;
  border-top-width: 1px;
  border-top-style: solid;
  padding: 0.5rem 1rem var(--space-ms);
}
.entity-chooser-selected[hidden] {
  display: none;
}
.entity-chooser-selected-head {
  font-size: 0.78rem;
  font-weight: var(--typography-fontWeightSemiBold);
  letter-spacing: 0.03em;
  text-transform: uppercase;
  margin-bottom: 0.45rem;
}
.entity-chooser-selected-head [data-cms-chooser-selected-count] {
  font-weight: 500;
}
/* Composes the system .row-list. */
.entity-chooser-selected .ec-row {
  padding: 0.55rem 0.65rem;
  min-height: 3rem;
}
.entity-chooser-selected .ec-row-thumb {
  flex: 0 0 36px;
  width: 36px;
  height: 36px;
}

/* Done button sits to the LEFT of collapse/close in the header, with a
 * bit of breathing room. */
.entity-chooser-sidebar.ec-multi .entity-chooser-header [data-cms-chooser-done] {
  margin-right: 0.25rem;
}
/* ============= END MULTI-VALUE ENTITY CHOOSER ============= */

/* ============= CONTENT MODEL VISUALIZATION =============
   Layout for the subtype visualizer page (GET /core/subtype/visualize).
   Rebuilt on the system vocabulary (ensembler 684 Phase C): pills compose
   .badge, interactive pills compose .chip, cards compose .card, key/value
   lists compose .info-list, drawer rows compose .row-item. Only
   visualizer-specific layout, density tweaks, and data-viz hooks live
   here; color tokens, focus/dim states, and dark-mode overrides live in
   colors.css. The page is bootstrapped by a DOMContentLoaded handler in
   warhol-apps.js that mounts on [data-content-model-viz]. */

.cmv-tab-bar {
  display: flex;
  gap: 0;
  border-bottom: 1px solid;
  margin-bottom: var(--space-md);
}
.cmv-tab-bar a {
  padding: 0.6rem 1rem;
  text-decoration: none;
  font-weight: var(--typography-fontWeightMedium);
  border-bottom: 2px solid transparent;
  transition: color 150ms, border-color 150ms;
}

/* Restore the [hidden] attribute's intent inside the visualizer. The
   UA's `[hidden] { display: none }` has lower specificity than the
   system `display: inline-flex` rules (.badge/.chip), so without this
   an element we hide via JS still renders. Scoped to .cmv-root to keep
   blast radius zero. */
.cmv-root [hidden] {
  display: none !important;
}

.cmv-toolbar {
  display: flex;
  align-items: center;
  gap: var(--space-ms);
  flex-wrap: wrap;
  margin-bottom: var(--space-ms);
}
.cmv-toolbar .cmv-search {
  position: relative;
  flex: 1;
  min-width: 240px;
}
.cmv-toolbar .cmv-search i {
  position: absolute; left: var(--space-ms); top: 50%; transform: translateY(-50%);
  font-size: 0.95em;
  pointer-events: none;
}
.cmv-toolbar .cmv-search input[type="search"] {
  width: 100%;
  padding: var(--space-sm) var(--space-ms) var(--space-sm) 2.3em;
  border: 1px solid;
  border-radius: var(--radius-lg);
  font: inherit;
  font-size: 0.95em;
  outline: none;
}

.cmv-source-segment {
  display: inline-flex;
  border: 1px solid;
  border-radius: var(--radius-lg);
  overflow: hidden;
}
.cmv-source-segment button {
  border: 0;
  padding: var(--space-sm) var(--space-md);
  font: inherit;
  font-size: 0.95em;
  font-weight: var(--typography-fontWeightMedium);
  cursor: pointer;
}
.cmv-source-segment button + button { border-left: 1px solid; }

/* The visualizer's interactive pills are system chips, but they are
   <span>s driven by [data-action] click delegation rather than
   a/button/label elements (the global button action rules would fight
   the chip surface). Opt them into the interactive affordance the
   element-based system rule keys off. */
.cmv-root span.chip[data-action] {
  cursor: pointer;
  user-select: none;
}
.cmv-root span.chip[data-action]:hover {
  transform: translateY(-1px);
  box-shadow: var(--shadow-2);
}

/* Count pill nested inside a chip (class filters, reuse heatmap).
   Colors derive from currentColor in colors.css so the selected state
   adapts for free. */
.cmv-root .chip .cmv-count {
  border-radius: var(--radius-pill);
  padding: 0.1em 0.55em;
  font-size: 0.85em;
  font-weight: var(--typography-fontWeightSemiBold);
  min-width: 1.9em;
  text-align: center;
}
.cmv-root .chip .cmv-mono {
  font-family: var(--typography-font-family-fixed), monospace;
  font-size: 0.8em;
}
.cmv-unused-chip {
  border-style: dashed;
  font-style: italic;
}
.cmv-unused-chip.selected { border-style: solid; }

.cmv-heatmap, .cmv-unused {
  margin: var(--space-sm) 0 var(--space-ms);
}
.cmv-heatmap .cmv-label,
.cmv-unused  .cmv-label {
  font-size: 0.8em; text-transform: uppercase; letter-spacing: 0.05em;
  margin-bottom: var(--space-xs);
  display: flex; align-items: center; gap: var(--space-sm);
}
.cmv-heatmap .cmv-label-hint,
.cmv-unused  .cmv-label-hint {
  text-transform: none; letter-spacing: 0; font-weight: var(--typography-fontWeightRegular);
  margin-left: var(--space-xs);
}

.cmv-type-legend {
  display: flex; align-items: center; gap: var(--space-md); flex-wrap: wrap;
  font-size: 0.86em;
  margin-bottom: var(--space-ms);
  padding-bottom: var(--space-sm);
  border-bottom: 1px dashed;
}
.cmv-type-legend .cmv-legend-item { display: inline-flex; align-items: center; gap: var(--space-xs); }
.cmv-type-legend .cmv-legend-meta { margin-left: auto; }

/* Attribute-type swatch — data-viz glyph box (colors in colors.css). */
.cmv-type-icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 18px; height: 18px;
  border-radius: var(--radius-sm);
  font-size: 0.71em;
  flex-shrink: 0;
}
/* Undo the system .chip i treatment when the swatch sits in a chip. */
.cmv-root .chip .cmv-type-icon i {
  font-size: inherit;
  opacity: 1;
}

.cmv-layout {
  display: grid;
  grid-template-columns: 1fr 420px;
  gap: 0;
  min-height: 60vh;
}
.cmv-grid-pane {
  padding-right: var(--space-md);
}
.cmv-drawer {
  border-left: 1px solid;
  position: sticky;
  top: 0;
  align-self: start;
  max-height: 90vh;
  overflow: auto;
}
.cmv-drawer-inner { padding: var(--space-md) var(--space-lg) var(--space-xl); position: relative; }

.cmv-class-section { margin-top: var(--space-lg); }
.cmv-class-section:first-of-type { margin-top: var(--space-xs); }
.cmv-class-section h2 {
  font-size: 0.86em;
  font-weight: var(--typography-fontWeightSemiBold);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  margin: 0 0 var(--space-ms) var(--space-xs);
  display: flex; align-items: center; gap: var(--space-sm);
}
.cmv-class-section h2::after {
  content: ""; flex: 1; height: 1px;
}

/* Subtype cards compose the system .card / .card-grid; only the dense
   header, the dim/highlight transition, and the footer are local. */
.cmv-card {
  transition: box-shadow 0.2s ease, opacity 200ms, filter 200ms;
}
.cmv-card-header {
  padding: var(--space-ms) var(--space-md) var(--space-sm);
  align-items: flex-start;
  gap: var(--space-ms);
  cursor: pointer;
}
.cmv-card-name {
  font-size: 1em; font-weight: var(--typography-fontWeightSemiBold);
  line-height: 1.2;
  display: flex; align-items: center; gap: var(--space-sm);
  flex-wrap: wrap;
}
.cmv-card-code {
  font-family: var(--typography-font-family-fixed), monospace;
  font-size: 0.8em;
  margin-top: 0.2em;
  word-break: break-all;
}
.cmv-card-desc {
  font-size: 0.86em;
  margin-top: 0.5em;
  line-height: var(--typography-lineHeightCompact);
}

/* Classification badge — composes .badge; the per-entity-type palette
   (colors.css) keys off data-class. Sized down for in-card density. */
.cmv-class-badge {
  font-size: 0.68em;
  padding: 0.25em 0.6em;
  border-width: 1px;
  border-style: solid;
  letter-spacing: 0.05em;
}

.cmv-attr-list {
  list-style: none; margin: 0; padding: var(--space-xs) 0;
  max-height: 420px; overflow: auto;
}
.cmv-group {
  border-top: 1px solid;
  border-bottom: 1px solid;
  margin: var(--space-xs) 0;
}
.cmv-group-header {
  padding: var(--space-xs) var(--space-md);
  font-size: 0.8em;
  font-weight: var(--typography-fontWeightSemiBold);
  letter-spacing: 0.07em;
  text-transform: uppercase;
  display: flex; align-items: center; gap: var(--space-sm);
}
.cmv-group-header .cmv-group-code {
  font-family: var(--typography-font-family-fixed), monospace;
  font-size: 0.9em;
  text-transform: none;
  letter-spacing: 0;
  margin-left: auto;
}
.cmv-group-attrs { list-style: none; margin: 0; padding: 0; }

.cmv-attr-row {
  display: grid;
  grid-template-columns: 22px 1fr auto;
  align-items: center;
  gap: var(--space-sm);
  padding: var(--space-xs) var(--space-md);
  cursor: pointer;
  transition: background 100ms;
  border-left: 3px solid transparent;
}
.cmv-attr-label {
  font-size: 0.95em;
  line-height: 1.3;
  display: flex; align-items: baseline; gap: 0.4em;
  min-width: 0;
}
.cmv-attr-label .cmv-label-cluster { min-width: 0; overflow: hidden; }
.cmv-attr-label .cmv-label-text {
  display: block;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.cmv-attr-mono {
  font-family: var(--typography-font-family-fixed), monospace;
  font-size: 0.75em;
  display: block;
  margin-top: 0.1em;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.cmv-meta {
  display: inline-flex; align-items: center; gap: 0.4em;
  flex-shrink: 0;
}

/* Meta pills (cardinality / reuse / value counts) are system badges
   squeezed for row density; the bordered look keeps the original dot
   read (border-color per state in colors.css). */
.cmv-meta .badge,
.cmv-legend-meta .badge {
  font-size: 0.75em;
  padding: 0.15em 0.5em;
  min-width: 1.7em;
  justify-content: center;
  border-width: 1px;
  border-style: solid;
}
.cmv-card-badge i { font-size: 0.85em; }
.cmv-req { font-weight: var(--typography-fontWeightBold); flex-shrink: 0; }

.cmv-card-footer {
  padding: var(--space-xs) var(--space-md) var(--space-sm);
  border-top: 1px solid;
  display: flex; align-items: center; gap: var(--space-sm);
  font-size: 0.8em;
}

.cmv-drawer .cmv-empty { padding: 4em var(--space-lg); text-align: center; font-size: 0.95em; }
.cmv-drawer .cmv-empty i { font-size: 2em; opacity: 0.4; margin-bottom: var(--space-ms); display: block; }
.cmv-drawer h3.cmv-kind {
  font-size: 0.75em; font-weight: var(--typography-fontWeightSemiBold); letter-spacing: 0.12em;
  text-transform: uppercase;
  margin: 0 0 var(--space-xs);
}
.cmv-drawer h2.cmv-entity-title {
  font-size: 1.3em; font-weight: var(--typography-fontWeightSemiBold); margin: 0 0 var(--space-xs);
  display: flex; align-items: center; gap: var(--space-sm);
}
.cmv-drawer .cmv-icon-circle {
  width: 32px; height: 32px;
  border-radius: var(--radius-sm);
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 0.8em;
  flex-shrink: 0;
}
.cmv-drawer .cmv-entity-code {
  font-family: var(--typography-font-family-fixed), monospace;
  font-size: 0.86em;
  margin-bottom: var(--space-md);
  word-break: break-all;
  padding: var(--space-xs) var(--space-sm);
  border-radius: var(--radius-sm);
  display: inline-block;
  border: 1px solid;
}
.cmv-drawer .cmv-entity-help {
  border-left: 3px solid;
  border-top: 1px solid; border-right: 1px solid; border-bottom: 1px solid;
  padding: var(--space-sm) var(--space-md);
  font-size: 0.9em;
  line-height: 1.55;
  border-radius: var(--radius-sm);
  margin: 0 0 var(--space-md);
}
.cmv-drawer h4 {
  font-size: 0.8em; font-weight: var(--typography-fontWeightSemiBold); letter-spacing: 0.08em;
  text-transform: uppercase;
  margin: var(--space-md) 0 var(--space-sm);
  display: flex; align-items: center; gap: var(--space-sm);
}
.cmv-drawer h4::after { content: ""; flex: 1; height: 1px; }

/* Key/value schema list — composes .info-list .info-list-left, tightened. */
.cmv-drawer .cmv-kv {
  gap: var(--space-xs) var(--space-ms);
  font-size: 0.95em;
  margin-bottom: var(--space-sm);
}
.cmv-drawer .cmv-kv dt { font-size: 0.9em; }
.cmv-drawer .cmv-kv dd code {
  font-family: var(--typography-font-family-fixed), monospace;
  padding: 0.1em 0.5em;
  border-radius: var(--radius-sm);
  font-size: 0.85em;
}

/* Used-by jump list — .row-list of clickable .row-items. */
.cmv-drawer .cmv-used-by-list .row-item {
  cursor: pointer;
  gap: var(--space-sm);
  transition: background 120ms, border-color 120ms;
}
.cmv-drawer .cmv-used-by-list .cmv-via {
  font-size: 0.8em;
}
.cmv-drawer .cmv-used-by-list .cmv-jump-code {
  font-family: var(--typography-font-family-fixed), monospace;
  font-size: 0.75em;
  margin-left: auto;
}
/* Close affordance is a system icon-button pinned to the drawer corner
   (cmv-close-x is the position hook only). */
.cmv-drawer .cmv-close-x {
  position: absolute; top: var(--space-ms); right: var(--space-md);
}

/* Per-attribute jump row in the subtype drawer — composes .row-item. */
.cmv-drawer .cmv-attr-fullrow {
  cursor: pointer;
  margin-bottom: var(--space-xs);
  transition: background 120ms, border-color 120ms;
}
.cmv-drawer .cmv-attr-fullrow .cmv-label-cluster { flex: 1; min-width: 0; }
.cmv-drawer .cmv-attr-fullrow .cmv-label-text { font-weight: var(--typography-fontWeightMedium); font-size: 0.95em; }

.cmv-no-results {
  padding: 4em var(--space-lg); text-align: center;
  font-size: 1em;
}

/* Jump-to-card pulse — same parameterized pattern as toggle-glow:
   the ring color rides --glow-color (assigned from the palette here,
   mixed to transparency in the keyframes). */
.cmv-pulse {
  --glow-color: var(--purple-700);
  animation: cmv-pulse 1.4s ease-out 2;
}
@keyframes cmv-pulse {
  0%   { box-shadow: 0 0 0 0    color-mix(in srgb, var(--glow-color), transparent 50%),  var(--shadow-2); }
  70%  { box-shadow: 0 0 0 14px color-mix(in srgb, var(--glow-color), transparent 100%), var(--shadow-2); }
  100% { box-shadow: 0 0 0 0    color-mix(in srgb, var(--glow-color), transparent 100%), var(--shadow-2); }
}

.cmv-status-stats {
  padding: var(--space-ms) 0 var(--space-lg);
  font-size: 0.86em;
  display: flex; gap: var(--space-lg); flex-wrap: wrap;
  border-top: 1px solid;
  margin-top: var(--space-md);
}
.cmv-status-stats .cmv-stat { display: inline-flex; align-items: center; gap: var(--space-xs); }
.cmv-status-stats .cmv-stat strong { font-weight: var(--typography-fontWeightSemiBold); }

@media (max-width: 960px) {
  .cmv-layout { grid-template-columns: 1fr; }
  .cmv-drawer {
    position: fixed; inset: 0;
    max-height: none;
    z-index: 100;
    display: none;
  }
}
/* ============= END CONTENT MODEL VISUALIZATION ============= */

/* ============= PERFORMER PROGRAMME REPORT ============= */
.programme-event-title {
  margin-bottom: var(--space-xs);
}

.programme-event-when {
  margin: 0 0 var(--space-md);
}

/* Labelled in-row utilities (per-section copy) sit between the borderless
   glyph button and a chromed button: keep them light, just space the glyph
   from its label. */
.programme-section-header .icon-button {
  gap: var(--space-xs);
}

.programme-toolbar {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm);
  margin: var(--space-md) 0 var(--space-lg);
}

.programme-section {
  margin-bottom: var(--space-lg);
}

.programme-section-header {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--space-md);
  margin-bottom: var(--space-sm);
}

.programme-section-header h4 {
  margin: 0;
}

.programmeColumns {
  column-count: 3;
  column-gap: var(--space-md);
}

.programme-instrument-block {
  break-inside: avoid;
  page-break-inside: avoid;
  margin-bottom: var(--space-md);
}

.programme-instrument-block h5 {
  margin: 0 0 var(--space-sm) 0;
  font-weight: bold;
}

.programme-name-list {
  list-style: none;
  margin: 0 0 var(--space-md) 0;
  padding: 0;
}

.programme-name-list li {
  margin: 0;
  padding: 0;
}

.programme-event-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.programme-event-list li {
  margin: var(--space-sm) 0;
}

@media screen and (max-width: 1250px) {
  .programmeColumns {
    column-count: 2;
  }
}

@media screen and (max-width: 768px) {
  .programmeColumns {
    column-count: 1;
  }
}

.nav-link-trailing-icon {
  margin-left: 0.4rem;
  font-size: 0.85em;
  opacity: 0.7;
}

/* Compact count chip beside a label: the awaiting-approval
   count next to the sidebar "Registration Status" item and the per-section
   counts on the registration-status accordions. A small pill that reads as a
   count without the bulk of the design-system badge. Colour in colors.css.
   The nav.main-nav duplicate out-specifies the generic `menu li span {
   display: block }` rule that would otherwise drop it onto its own line. */
.count-chip,
nav.main-nav .count-chip {
  display: inline-block;
  margin-left: 0.4rem;
  min-width: 1.15rem;
  padding: 0 0.3rem;
  font-size: 0.7rem;
  font-weight: var(--typography-fontWeightMedium);
  line-height: 1.15rem;
  text-align: center;
  border-radius: var(--radius-pill);
  vertical-align: middle;
}

/* Restricted-status affordances on a CMS form.
   Layout/size only; colours live in colors.css. The form icon is larger
   than the side-nav glyph to highlight the status. */
.field-status {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  margin: 0.25rem 0 0 var(--space-md);
  font-size: 0.85rem;
}
.field-status-icon {
  font-size: 1.15rem;
}
/* ============= END PERFORMER PROGRAMME REPORT ============= */

/* ============= CMS DASHBOARD ============= */
/* Quick-reference widget grid shown on the CMS home, above the editable
   welcome content. Layout only; coloring lives in colors.css so the
   re-theme surface stays in one place. */
.dashboard-grid {
  display: grid;
  /* Wide widgets so long item names stay readable before truncating. */
  grid-template-columns: repeat(auto-fill, minmax(420px, 1fr));
  gap: var(--space-lg);
  margin-block: var(--space-lg);
}

/* Composes the system .card; widget density/layout only. */
.dashboard-widget {
  display: flex;
  flex-direction: column;
  padding: var(--space-md) var(--space-lg) var(--space-sm);
  min-width: 0;
}

.dashboard-widget-title {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  margin: 0 0 var(--space-sm);
  padding-bottom: var(--space-sm);
  border-bottom-width: 1px;
  border-bottom-style: solid;
  font-size: var(--font-size-base);
  font-weight: var(--typography-fontWeightMedium);
}

.dashboard-widget-title i {
  width: 1.1em;
  text-align: center;
}

.dashboard-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
}

.dashboard-row {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
}

.dashboard-row + .dashboard-row {
  border-top-width: 1px;
  border-top-style: solid;
}

.dashboard-row-link {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  padding: var(--space-sm) var(--space-xs);
  border-radius: var(--radius-sm);
  text-decoration: none;
  transition: var(--transition-style);
}

.dashboard-row-icon {
  flex: 0 0 1.2em;
  text-align: center;
}

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

.dashboard-row-date {
  flex: 0 0 auto;
  font-size: var(--font-size-xs);
  white-space: nowrap;
}

/* Count is a system badge (badge badge-pill badge-neutral); only the
   row-layout constraints live here. */
.dashboard-row-count {
  flex: 0 0 auto;
  min-width: 1.6em;
  justify-content: center;
}

.dashboard-empty {
  margin: 0;
  padding: var(--space-sm) var(--space-xs);
  font-size: var(--font-size-sm);
  font-style: italic;
}

/* Last-modified-by avatar: small inline picture, larger hover/focus tooltip. */
.dashboard-row-avatar {
  position: relative;
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  cursor: default;
}

.dashboard-avatar-img {
  width: 22px;
  height: 22px;
  border-radius: var(--radius-full);
  object-fit: cover;
  display: block;
  border-width: 1px;
  border-style: solid;
}

/* Composes the system .popover; placement + layout only. */
.dashboard-avatar-tip {
  bottom: calc(100% + 6px);
  right: 0;
  top: auto;
  display: none;
  flex-direction: column;
  align-items: center;
  gap: var(--space-xs);
  white-space: nowrap;
}

.dashboard-row-avatar:hover .dashboard-avatar-tip,
.dashboard-row-avatar:focus .dashboard-avatar-tip,
.dashboard-row-avatar:focus-within .dashboard-avatar-tip {
  display: flex;
}

.dashboard-avatar-photo {
  width: 50px;
  height: 50px;
  border-radius: var(--radius-full);
  object-fit: cover;
}

.dashboard-avatar-name {
  font-size: var(--font-size-xs);
}
/* ============= END CMS DASHBOARD ============= */

/* ============= ENSEMBLE HOME DASHBOARD ============= */
/* Column-distribution layout: each .dashboard-col stacks its items; the columns
   sit side by side when there's room and wrap to a single reading-order stack
   when narrow. Reuses the .dashboard-widget / .dashboard-row grammar for cards;
   a tile may be a link (.dashboard-stat-link). Coloring in colors.css. */
.dashboard-columns {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: var(--space-lg);
  margin-block: var(--space-lg);
}

.dashboard-col {
  flex: 1 1 360px;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
}

.dashboard-stat {
  display: grid;
  grid-template-columns: auto 1fr;
  grid-template-rows: auto auto;
  align-items: center;
  column-gap: var(--space-md);
  padding: var(--space-md) var(--space-lg);
  border-radius: var(--radius-lg);
  border-width: 1px;
  border-style: solid;
  min-width: 0;
}

.dashboard-stat-link {
  text-decoration: none;
}

.dashboard-stat-icon {
  grid-row: 1 / 3;
  font-size: var(--font-size-xl);
}

.dashboard-stat-figure {
  font-size: var(--font-size-xl);
  font-weight: var(--typography-fontWeightMedium);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.dashboard-stat-label {
  font-size: var(--font-size-sm);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Trailing chevron telegraphs "click → go somewhere" on action rows. */
.dashboard-action-go {
  flex: 0 0 auto;
  font-size: var(--font-size-xs);
}

/* Coming-up rows stack the event name over its time. */
.dashboard-event-name {
  display: block;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.dashboard-event-when {
  display: block;
  font-size: var(--font-size-xs);
}

.dashboard-row-rsvp {
  flex: 0 0 auto;
}
/* ============= END ENSEMBLE HOME DASHBOARD ============= */

/* ============= ACTIVE-SEASON CHOOSER & OVERRIDE BANNER ============= */
/* Season picker skin: reads as a single nav menu row — a
   calendar glyph in the icon column, the active season name, and a trailing
   up/down caret marking it as a picker. Glyphs are decorative overlays
   (pointer-events off); the select beneath spans the row and takes the click.
   Row arrangement lives in layout.css. */
.season-chooser .season-chooser-icon,
.season-chooser .season-chooser-caret {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  pointer-events: none;
}

.season-chooser .season-chooser-icon {
  left: var(--space-sm);
  width: 1.3rem;
  text-align: center;
}

.season-chooser .season-chooser-caret {
  right: var(--space-sm);
  font-size: var(--font-size-xs);
}

.season-chooser select {
  appearance: none;
  border: none;
  background: transparent;
  font-size: var(--font-size-sm);
  font-weight: var(--typography-fontWeightRegular);
  /* Left padding clears the calendar glyph and lands the name on the nav text
     column; right padding clears the caret. */
  padding: 0.6rem calc(var(--space-sm) + var(--space-md)) 0.6rem calc(1.3rem + var(--space-sm));
  cursor: pointer;
}

/* The override banner and the maintenance-mode banner are siblings of <main>, so
   they don't inherit <main>'s 4% horizontal inset — without this they sit flush
   against the navbar and sidebar. Match the content inset on the sides and add
   room up top. */
.season-banner,
.maintenance-banner,
.migration-task-banner {
  padding: var(--space-md) 4% 0;
}

/* The override banner reuses the standard alert chrome; only the inline clear
   form needs spacing so the button sits below the explanatory text. */
.season-banner-clear {
  margin-top: var(--space-sm);
}
/* ============= END ACTIVE-SEASON CHOOSER & OVERRIDE BANNER ============= */

/* ============= INQUIRIES — inbox + thread layout ============= */
.inquiry-badges {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-ms);
  margin: var(--space-sm) 0 var(--space-md);
}
/* Inquiry count pills are system badges (badge badge-pill badge-outline);
   .inquiry-badge remains only as the per-type icon-color hook (colors.css). */
.inquiry-dot i { font-size: 0.7rem; }

/* Inquiries hub pane: the shared inbox body sits inside the mailbox
   content column, which has no padding of its own, so pad the pane to clear the folder
   column. The message list is pulled back to full-bleed so its row dividers still span
   the pane edge-to-edge — only the heading + filter bar gain the gap. */
.inquiries-pane {
  padding: var(--space-md);
}
.inquiries-pane .message-list {
  margin: 0 calc(-1 * var(--space-md)) calc(-1 * var(--space-md));
}

/* Inquiry thread mirrors the mailbox message-view: a bordered
   pane with a toolbar + header (reusing .message-view-*), the conversation as
   the body, and the composers below. */
.inquiry-thread {
  border: 1px solid var(--divider);
  border-radius: var(--radius-lg);
  overflow: hidden;
  margin-bottom: var(--space-md);
}
.inquiry-thread-body {
  padding: 1.25rem;
}
.inquiry-status {
  font-weight: var(--typography-fontWeightSemiBold);
}

/* Action toolbar between the header and the conversation: adopt
   + the status segmented toggle, one line, mirroring a mailbox toolbar strip. */
.inquiry-actions-bar {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  flex-wrap: wrap;
  padding: var(--space-ms) 1.25rem;
  border-bottom: 1px solid var(--divider);
}

.inquiry-conversation {
  list-style: none;
  padding: 0;
  margin: 0 0 var(--space-md);
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}
.inquiry-entry {
  border-width: 1px;
  border-style: solid;
  border-radius: var(--radius-md);
  padding: var(--space-sm) var(--space-md);
}
.inquiry-entry-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--space-md);
  font-size: 0.85rem;
}
.inquiry-entry-author { font-weight: var(--typography-fontWeightSemiBold); }
.inquiry-entry-tag {
  font-weight: 400;
  font-style: italic;
  font-size: 0.8rem;
}
.inquiry-entry-body { margin-top: 0.35rem; }
.inquiry-entry-actions { margin-top: 0.35rem; }

.inquiry-reply-form,
.inquiry-note-form {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  margin-bottom: var(--space-md);
}
.inquiry-reply-form textarea,
.inquiry-note-form textarea {
  width: 100%;
  box-sizing: border-box;
}
/* Compose footer — right-aligned Send/Add button, mirroring the email composer. */
.inquiry-compose-footer {
  display: flex;
  justify-content: flex-end;
  margin-top: var(--space-sm);
}

/* Mobile pass: the segmented status toggle is an inline-flex .button-group
   with overflow: hidden — on a 390px viewport its 7 segments overflow the
   available width and the tail (Closed Registered / Abandoned / Other) is
   clipped and unreachable, not just cramped. Wrapping turns the clip into
   extra rows instead of losing segments; overflow: hidden stays (the
   group's rounded-corner illusion depends on it clipping the square
   segment edges, components.css:871-886) — height is auto, so wrapping
   grows the box rather than clipping content. */
@media screen and (max-width: 768px) {
  .inquiry-status-toggle {
    flex-wrap: wrap;
  }

  .inquiry-actions-bar .button,
  .inquiry-status-toggle button,
  .inquiry-compose-footer button,
  .inquiry-entry-actions button {
    min-height: 2.75rem;
  }

  /* min-height alone leaves short labels (e.g. "All") narrower than 44px —
     the pill's inline-flex width tracks its text, not its height. */
  .inquiry-status-filter .chip-sm {
    min-height: 2.75rem;
    min-width: 2.75rem;
    justify-content: center;
  }
}
/* ============= END INQUIRIES ============= */

/* ============= PERF ANALYSIS VIEW ============= */
.perf-panel {
  margin: var(--space-lg) 0;
  padding: var(--space-md);
  border: 1px solid var(--divider);
  border-radius: var(--radius-md);
}
.perf-panel--alert {
  border-color: var(--warning-main);
}
.perf-hint {
  color: var(--text-secondary);
  font-size: 0.85em;
}
.perf-config {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--space-xs) var(--space-md);
  margin: var(--space-sm) 0 0;
}
.perf-config dt {
  color: var(--text-secondary);
}
.perf-config dd {
  margin: 0;
}
.perf-forcefull {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);
  flex-wrap: wrap;
}
.perf-forcefull-state--on {
  color: var(--warning-dark);
}
.perf-table {
  width: 100%;
  border-collapse: collapse;
  margin: var(--space-sm) 0 var(--space-md);
}
.perf-table th,
.perf-table td {
  text-align: left;
  padding: var(--space-xs) var(--space-sm);
  border-bottom: 1px solid var(--divider);
  vertical-align: top;
}
.perf-num {
  text-align: right;
  font-variant-numeric: tabular-nums;
}
.perf-uri {
  max-width: 28rem;
  overflow-wrap: anywhere;
}
.perf-row {
  cursor: pointer;
}
.perf-detail-row {
  display: none;
}
.perf-detail-row.is-open {
  display: table-row;
}
.perf-waterfall {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  padding: var(--space-sm) 0;
}
.perf-track-row {
  display: grid;
  grid-template-columns: 18rem 1fr 4rem;
  align-items: center;
  gap: var(--space-sm);
}
.perf-track-label {
  color: var(--text-secondary);
  font-size: 0.85em;
  overflow-wrap: anywhere;
}
.perf-track {
  position: relative;
  display: block;
  height: 0.9rem;
  background: var(--divider);
  border-radius: var(--radius-sm);
}
.perf-bar {
  --perf-l: 0%;
  --perf-w: 0%;
  position: absolute;
  top: 0;
  bottom: 0;
  left: var(--perf-l);
  width: var(--perf-w);
  min-width: 2px;
  border-radius: var(--radius-sm);
  background: var(--text-secondary);
}
.perf-bar--pre-commit {
  background: var(--info-main);
}
.perf-bar--post-commit {
  background: var(--success-main);
}
.perf-bar--mixed {
  background: var(--warning-main);
}
.perf-bar--ws {
  background: var(--text-secondary);
}
.perf-badge {
  display: inline-block;
  padding: 0 var(--space-xs);
  border-radius: var(--radius-pill);
  background: var(--warning-main);
  color: var(--text-titlebar);
  font-size: 0.75em;
}
.perf-mod {
  font-size: 0.85em;
  color: var(--text-secondary);
}
/* ============= END PERF ANALYSIS VIEW ============= */

/* ============= RICH-TEXT EMBED CHIP ============= */
/* Editor-side carrier for an <ec-item> marker; composes the system .chip.
   Colors live in colors.css. */
.ec-chip {
  cursor: pointer;
  user-select: none;
  white-space: nowrap;
}
.ec-chip.ec-chip-readonly {
  cursor: default;
}
.ec-chip .ec-chip-fmt {
  font-size: var(--font-size-xs);
}
.ec-chip.ec-chip-loading {
  opacity: 0.6;
}
/* A block-display chip stands alone: the embed it represents is a block, and a chip
   that looks like a word in a sentence would be promising a placement it cannot have. */
.ec-chip.ec-chip-block {
  display: flex;
  width: 100%;
  justify-content: flex-start;
}
/* Inspect link on a read-only chip; opens the embedded item's inspect view. */
.ec-chip-inspect {
  margin-left: var(--space-xs);
  cursor: pointer;
  text-decoration: none;
}
/* Embed format menu composes the system .popover; layout only. */
.ec-format-menu {
  display: flex;
  flex-direction: column;
  min-width: 9rem;
  padding: var(--space-xs);
  z-index: 1000;
}
.ec-format-menu-item {
  padding: var(--space-sm) var(--space-md);
  border: none;
  border-radius: var(--radius-sm);
  text-align: left;
  font: inherit;
  text-transform: uppercase;
  cursor: pointer;
}
.ec-format-menu-item i {
  width: 1.1em;
  margin-right: var(--space-sm);
  text-align: center;
}
.ec-format-menu-item.is-current {
  font-weight: var(--typography-fontWeightMedium);
}
/* A format the caret's position cannot take. Shown rather than hidden so the author
   sees the format exists and reads why it is unavailable. */
.ec-format-menu-item:disabled {
  cursor: not-allowed;
  opacity: 0.5;
}
.ec-format-menu-hint {
  padding: 0 var(--space-md) var(--space-xs);
  font-size: var(--font-size-xs);
}
/* The Inspect entry is an <a> (opens in a new window) but reads as a menu item. */
.ec-format-menu-inspect {
  display: block;
  color: inherit;
  text-decoration: none;
}
/* ============= END RICH-TEXT EMBED CHIP ============= */

/* ============= MEDIA CROP EDITOR ============= */
.media-crop-editor {
  margin-top: var(--space-lg);
  padding: var(--space-md);
  border: 1px solid var(--divider);
  border-radius: var(--radius-md);
  background: var(--background-secondary);
}
.media-crop-stage {
  position: relative;
  display: inline-block;
  max-width: 100%;
  margin: var(--space-sm) 0;
}
.media-crop-image {
  display: block;
  max-width: 100%;
  height: auto;
  cursor: crosshair;
  user-select: none;
}
.media-crop-overlay {
  position: absolute;
  top: 0;
  left: 0;
  box-sizing: border-box;
  border: 2px solid var(--primary-link);
  box-shadow: 0 0 0 100vmax var(--scrim);
  pointer-events: none;
}
.media-crop-form {
  display: flex;
  flex-wrap: wrap;
  align-items: end;
  gap: var(--space-sm);
}
.media-crop-form label {
  display: flex;
  flex-direction: column;
  font-size: var(--font-size-sm);
  color: var(--text-secondary);
}
.media-crop-form input {
  width: 6rem;
}
.media-crop-clear {
  margin-top: var(--space-sm);
}
.media-renditions {
  margin-top: var(--space-md);
}
.media-renditions-list {
  margin: var(--space-xs) 0 0;
  padding-left: var(--space-lg);
  font-size: var(--font-size-sm);
}
/* ============= END MEDIA CROP EDITOR ============= */

/* ============= COMMUNICATION CENTRE ATTACHMENTS ============= */
.attachments-strip {
  margin-top: var(--space-md);
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}
.attachments-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm);
  flex-wrap: wrap;
}
.attachments-total {
  font-size: var(--font-size-sm);
  opacity: 0.7;
}
.message-view-attachments {
  margin-top: var(--space-md);
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}
/* ============= END COMMUNICATION CENTRE ATTACHMENTS ============= */

/* ============= HELP TOC ============= */
.help-toc {
  max-width: 44rem;
}
.help-toc-actions {
  text-align: right;
}
/* ============= END HELP TOC ============= */

/* ============= ACCEPT WITH REDUCTION ============= */
/* Touch targets for the accept-with-reduction dialog; .modal already fits 390px. */
@media screen and (max-width: 768px) {
  .approve-with-reduction-modal .modal-footer button,
  .approve-with-reduction-modal .modal-header button {
    min-height: 2.75rem;
    min-width: 2.75rem;
    justify-content: center;
  }
}
/* ============= END ACCEPT WITH REDUCTION ============= */

/* ============= SEASON REGISTRATION FORM CHOICE ============= */
/* Layout for the season form's registration-form radios; the widths undo the field atoms' 100%. */
.registration-form-choice {
  display: flex;
  flex-direction: column;
  gap: var(--space-ms);
  margin: var(--space-md);
}
.registration-form-choice label.choice {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-ms);
  width: auto;
  margin: 0;
}
.registration-form-choice label.choice input[type="radio"] {
  width: auto;
  margin: 0;
}
.registration-form-choice label.choice select {
  width: auto;
  min-width: 14rem;
  max-width: 100%;
}
/* ============= END SEASON REGISTRATION FORM CHOICE ============= */
