/* ShowStack neutral surface palette — `neutral-bg-test` experiment.
 *
 * Backgrounds and borders ONLY. Buttons, accent colors, status colors and
 * link colors are deliberately not defined here; they stay where they were.
 *
 * Loaded once from templates/admin/base_site.html so every admin page can
 * reach these without redefining hexes per file.
 */
:root {
    --ss-bg: #16171a;           /* page background */
    --ss-surface: #1e1f23;      /* panels, cards, table body */
    --ss-surface-alt: #212227;  /* zebra stripe (even table rows) */
    --ss-raised: #26282d;       /* inputs, table headers, hover rows */
    --ss-border: #33353b;       /* borders, dividers */

    /* form controls */
    --ss-field-bg: #2a2c31;            /* text / number inputs, selects, textareas */
    --ss-field-border: #42454d;
    --ss-field-border-hover: #565a63;
    --ss-field-text: #e8e8ea;
    --ss-field-radius: 4px;
    --ss-placeholder: #6b6f78;         /* placeholder + empty "---------" selects */
    --ss-field-disabled-bg: #222428;
    --ss-field-disabled-border: var(--ss-border);

    /* focus ring -- the theme's accent violet */
    --ss-accent: #8b5cf6;
    --ss-accent-ring: rgba(139, 92, 246, 0.35);

    /* divider between inline-grid rows (replaces zebra striping) */
    --ss-row-divider: var(--ss-field-bg);

    /* muted foreground for secondary text and widget icons */
    --text-muted: #8b8f98;

    /* inline grid column sizing */
    --ss-col-num: 72px;        /* Dante #, Input Ch, Aux #, Matrix # */
    --ss-col-num-sm: 56px;     /* Group, DCA, Mute, Omni Out */
    --ss-col-port: 46px;       /* Direct Out, Omni In -- one port number */
    --ss-col-flex-max: 260px;  /* ceiling for the flexible Source / Name column */
    --ss-grid-max: 1400px;     /* ceiling for an inline grid table overall */
    --ss-col-check: 64px;      /* Multitrack, Delete? */

    /* Bus / signal type tints.
     *
     * Tokens rather than literals because the same two states turn up on
     * other type selects -- Mono/Stereo is the obvious next one -- and a
     * select wanting this treatment should need the class, not a new colour.
     * Deliberately low-luminance: these sit in a grid of 40-odd rows, and
     * the console page gets read at a dark FOH. */
    --bus-fixed: #5fd4c4;
    --bus-fixed-border: #2f6f68;
    --bus-fixed-bg: rgba(95, 212, 196, 0.08);

    --bus-variable: #e8b04a;
    --bus-variable-border: #7a5a22;
    --bus-variable-bg: rgba(232, 176, 74, 0.08);
}

/* Django admin's own surface variables.
 *
 * Django ships these in base.css / dark_mode.css and a lot of admin chrome
 * (.submit-row, .inline-related headers, selected cells, hairlines) reads them
 * directly, so repointing them here is cheaper and more complete than chasing
 * each rule. Accent, button, message and status variables are deliberately
 * left alone.
 *
 * The selector list matches dark_mode.css's own specificity (0,1,1) and loads
 * after it, so it wins without !important.
 */
:root,
html[data-theme="dark"],
html[data-theme="light"],
html[data-theme="auto"] {
    --body-bg: var(--ss-bg);
    --darkened-bg: var(--ss-surface);
    --selected-bg: var(--ss-raised);
    --border-color: var(--ss-border);
    --hairline-color: var(--ss-border);
}

/* ============================================================
 * color-scheme
 *
 * Everything this admin paints is dark, but the *browser* was never told
 * so, and the parts it draws itself kept their light defaults: scrollbars
 * above all. Any element that scrolls -- the inline grids on the console
 * page, a long changelist, a textarea -- got a white trough with a grey
 * thumb laid over a #16171a page.
 *
 * `color-scheme: dark` hands those UA-drawn surfaces to the dark set in
 * both engines, so a scrollbar that does appear matches the page instead
 * of announcing itself. It also darkens native select popups, date
 * pickers and the focus ring's default colour; the explicit field styling
 * below already covers the rest.
 *
 * Declared on the same selector list as the variables above because this
 * admin is dark in every theme state -- base_site.html paints the
 * surfaces unconditionally, so html[data-theme="light"] is dark too and
 * leaving it on a light color-scheme would only bring the white
 * scrollbars back.
 * ============================================================ */
:root,
html[data-theme="dark"],
html[data-theme="light"],
html[data-theme="auto"] {
    color-scheme: dark;
}

/* ============================================================
 * Form controls
 *
 * The single source of truth for input / select / textarea
 * appearance across the admin. Previously this was spread over four
 * competing !important blocks in base_site.html (two of which were
 * dead), plus a body.model-console special case that made console
 * fields look different from every other form. Those are gone; edit
 * this instead.
 * ============================================================ */

/* Unprefixed on purpose: the project switcher in #header and the help
 * modal's module picker live outside #content, and they are selects too. */
input[type="text"],
input[type="password"],
input[type="email"],
input[type="url"],
input[type="number"],
input[type="tel"],
input[type="search"],
input[type="date"],
input[type="time"],
input[type="datetime-local"],
select,
textarea {
    background-color: var(--ss-field-bg) !important;
    color: var(--ss-field-text) !important;
    border: 1px solid var(--ss-field-border) !important;
    border-radius: var(--ss-field-radius) !important;
}

input:not([type="checkbox"]):not([type="radio"]):hover:not(:disabled):not([readonly]),
select:hover:not(:disabled),
textarea:hover:not(:disabled):not([readonly]) {
    border-color: var(--ss-field-border-hover) !important;
}

/* box-shadow ring goes on before the default outline comes off, so the
 * focused field is never left with no focus indicator at all. */
input:not([type="checkbox"]):not([type="radio"]):focus,
select:focus,
textarea:focus,
input:not([type="checkbox"]):not([type="radio"]):focus-visible,
select:focus-visible,
textarea:focus-visible {
    border-color: var(--ss-accent) !important;
    box-shadow: 0 0 0 2px var(--ss-accent-ring) !important;
    outline: none !important;
}

::placeholder {
    color: var(--ss-placeholder) !important;
    opacity: 1;
}

input:not([type="checkbox"]):not([type="radio"]):disabled,
input:not([type="checkbox"]):not([type="radio"])[readonly],
select:disabled,
textarea:disabled,
textarea[readonly] {
    background-color: var(--ss-field-disabled-bg) !important;
    border-color: var(--ss-field-disabled-border) !important;
    color: var(--ss-placeholder) !important;
}

/* An unset select still shows Django's "---------" empty label. Grey it so a
 * blank field reads as blank at a glance, while the open list stays legible. */
select:has(> option[value=""]:checked) {
    color: var(--ss-placeholder) !important;
}
select option {
    color: var(--ss-field-text);
}

/* ============================================================
 * Inline grids: rows separated by a rule, not by zebra fill
 * ============================================================ */

#content .inline-group tr.form-row,
#content .inline-group tr.form-row:nth-child(odd),
#content .inline-group tr.form-row:nth-child(even),
#content .inline-related tr:nth-child(odd),
#content .inline-related tr:nth-child(even) {
    background: transparent !important;
    background-color: transparent !important;
}

#content .inline-group tr.form-row > td,
#content .inline-group tr.form-row > th {
    border-bottom: 1px solid var(--ss-row-divider) !important;
}

#content .inline-group tr.form-row:last-child > td,
#content .inline-group tr.form-row:last-child > th {
    border-bottom: none !important;
}

/* ============================================================
 * Inline panels: the 2px that put a scrollbar under every grid
 *
 * Django gives `.inline-related fieldset` `width: 100%` and no border
 * (forms.css), and the admin's default box model is content-box. Then
 * base_site.html paints every panel with
 *
 *     .module, fieldset, .inline-group { border: 1px solid ... !important }
 *
 * so the fieldset keeps a width of exactly 100% of its container and
 * gains 1px of border on each side on top of it -- 2px wider than the box
 * it sits in. `.inline-related` is a scroll container (see the overflow-x
 * safety net further down), so those 2px were enough to arm it: every
 * tabular inline on the console page reported scrollWidth 1061 against
 * clientWidth 1059 at a 1440 viewport and drew a horizontal scrollbar
 * under a grid that was not actually too wide for its column widths.
 *
 * border-box is the fix at the source: the declared 100% now *includes*
 * the border the panel was given, the fieldset matches its container
 * exactly, and the scrollbar is gone. The border stays visible -- the
 * panel still looks like a panel -- and the table inside, itself
 * width: 100%, simply gets 2px less to work with.
 *
 * Not fixed by dropping the border instead: these panels are meant to
 * read as cards (base_site.html gives them the radius and shadow too),
 * and the same width: 100% + border combination would still be waiting
 * for the next person who adds one.
 * ============================================================ */

.inline-related fieldset {
    box-sizing: border-box;
}

/* ============================================================
 * Inline section headers + History button
 *
 * Django gives collapsible <summary> headers and .historylink
 * var(--header-bg) (#417690 steel blue). Match the "Console
 * Information" fieldset header instead: panel fill, light text,
 * hairline border.
 * ============================================================ */

#content fieldset > h2,
#content fieldset .fieldset-heading,
#content fieldset .inline-heading,
#content .inline-group h2,
#content .collapse summary,
#content .inline-related h4,
#content .inline-related:not(.tabular) .collapse summary {
    background: var(--ss-raised) !important;
    background-image: none !important;
    color: #f0f0f0 !important;
    border: 1px solid var(--ss-border) !important;
}

/* The long selector list is historical and now belt-and-braces: the
 * *_buttons.css files used to carry `.object-tools li:not(.addlink) a` at
 * #417690 !important, which outranked a plain `.object-tools a.historylink`,
 * so these had to match its specificity. Those files now say
 * `a:not(.addlink):not(.historylink)` and exclude the History button by
 * name, so nothing is competing for it any more. Kept because any page
 * that still styles its toolbar inline -- the PA Cable changelist does --
 * can reintroduce the fight, and losing it would repaint History steel
 * blue on that page only. */
.object-tools li a.historylink,
.object-tools a.historylink,
a.historylink,
.historylink,
.submit-row .historylink,
.object-tools .historylink {
    background: var(--ss-raised) !important;
    background-image: none !important;
    color: #f0f0f0 !important;
    border: 1px solid var(--ss-border) !important;
    border-radius: var(--ss-field-radius) !important;
    text-shadow: none !important;
}

.object-tools li:not(.addlink) a.historylink:hover,
.object-tools li a.historylink:hover,
.object-tools a.historylink:hover,
a.historylink:hover,
.historylink:hover {
    background: var(--ss-field-bg) !important;
    border-color: var(--ss-field-border-hover) !important;
    color: #ffffff !important;
}

/* ============================================================
 * Inline grid controls (console inputs / aux / matrix / stereo)
 *
 * One height for every control in a grid row, so text inputs,
 * number inputs and selects line up. Previously these came from
 * three competing sources at 2px/4px, 8px/10px and 26px.
 * ============================================================ */

.inline-group table td,
.inline-group table th {
    vertical-align: middle !important;
    /* Column widths below are declared on the cells. Without border-box the
     * 6px of horizontal cell padding is added on top of every declared
     * width, so a 150px column measured 170px and the flexible Source
     * column was left with the difference (24px at 1280). */
    box-sizing: border-box !important;
}

.inline-group table input:not([type="checkbox"]):not([type="radio"]):not([type="hidden"]),
.inline-group table select {
    height: 36px !important;
    min-height: 36px !important;
    font-size: 14px !important;
    padding: 4px 8px !important;
    box-sizing: border-box !important;
    line-height: normal !important;
    vertical-align: middle !important;
}

/* ------------------------------------------------------------------
 * Checkboxes and radios
 *
 * These are deliberately NOT covered by the form-control rules above
 * (a 32px tall, --ss-field-bg filled "input" makes no sense for a
 * checkbox). base_site.html used to paint them with
 * `filter: invert(1) hue-rotate(180deg)`, which Safari applies to the
 * whole native control including its checked glyph -- the reason the
 * Delete? / Multitrack boxes looked washed out and off-centre there
 * but fine in Chrome. Style the native control instead: accent-color
 * paints the checked fill in both engines.
 *
 * --ss-accent, not Django's --accent: Django admin's base.css defines
 * --accent as #f5dd5d (yellow), which fights this theme's violet.
 * ------------------------------------------------------------------ */
input[type="checkbox"],
input[type="radio"] {
    width: 16px;
    height: 16px;
    min-width: 16px;
    min-height: 16px;
    margin: 0;
    padding: 0;
    accent-color: var(--ss-accent);
    cursor: pointer;
    vertical-align: middle;
    flex: none;
}

input[type="checkbox"]:disabled,
input[type="radio"]:disabled {
    cursor: not-allowed;
    opacity: .5;
}

/* "Is template" and friends sit next to their label in a normal fieldset */
.form-row input[type="checkbox"],
.form-row input[type="radio"] {
    position: relative;
    top: -1px;
}

/* In a grid the box is the whole cell's content: centre it */
.inline-group table td input[type="checkbox"],
.inline-group table td input[type="radio"] {
    display: block !important;
    margin: 0 auto !important;
    float: none !important;
}

/* Spinners on Dante / channel number fields steal width and only show on
 * hover-focus, which made otherwise identical rows look different. */
.inline-group table input[type="number"] {
    -moz-appearance: textfield;
    appearance: textfield;
}
.inline-group table input[type="number"]::-webkit-outer-spin-button,
.inline-group table input[type="number"]::-webkit-inner-spin-button {
    -webkit-appearance: none;
    appearance: none;
    margin: 0;
}

/* Even rows of the Aux / Matrix grids have no Mono/Stereo control: the odd row
 * above owns the setting for the stereo pair. Label the gap instead of
 * leaving an unexplained blank cell (see js/mono_stereo_handler.js). */
.inline-group table td .stereo-linked-note {
    color: var(--ss-placeholder);
    font-size: 12px;
    font-style: italic;
    white-space: nowrap;
}

/* ============================================================
 * Buttons: one filled action, the rest outlined
 *
 * The note at the top of this file says buttons are deliberately not
 * defined here. That held while every button was Django's steel blue;
 * it stopped holding once the submit row below was given the theme's
 * violet-filled / outlined pair, because that pair is the house button
 * style and nothing named it. The result was #417690 hardcoded in two
 * dozen *_buttons.css files and in inline `style=` attributes, each one
 * a separate place to edit and none of them agreeing.
 *
 * So the two treatments get names. The submit row consumes them below
 * (same values it already used -- this is a rename, not a restyle), and
 * anything else that needs a button can take the .ss-btn classes rather
 * than inventing a third blue.
 * ============================================================ */
:root {
    /* Primary: the one action a page is *for*. Filled violet. */
    --ss-btn-primary-bg: var(--ss-accent);
    --ss-btn-primary-fg: #ffffff;
    --ss-btn-primary-border: var(--ss-accent);

    /* Secondary: everything else. Outlined, transparent until hovered,
     * so a row of them does not compete with the primary. */
    --ss-btn-secondary-bg: transparent;
    --ss-btn-secondary-fg: var(--ss-field-text);
    --ss-btn-secondary-border: var(--ss-field-border);
    --ss-btn-secondary-bg-hover: var(--ss-accent-ring);
    --ss-btn-secondary-fg-hover: #ffffff;
    --ss-btn-secondary-border-hover: var(--ss-accent);

    --ss-btn-radius: var(--ss-field-radius);
}

/* ------------------------------------------------------------------
 * .ss-btn -- opt-in button classes
 *
 * Doubled class selectors (`.ss-btn.ss-btn--primary`, specificity 0,2,0)
 * rather than the single class they read as. base_site.html carries
 *
 *     .button, input[type=submit], a.button { background: #417690 !important }
 *
 * at (0,1,0)/(0,1,1) and loads after this file, and an !important
 * declaration is only beaten by another !important declaration that wins
 * on specificity -- a plain `.ss-btn--primary` would lose to it. Hence
 * both the doubled selector and the !important on the painted
 * properties. Geometry (padding, gap, radius) needs neither: nothing
 * declares it !important.
 *
 * Note these still lose to an *id* rule. `#content-main a { color: #fff
 * !important }` in base_site.html paints every link inside the content
 * column, so a .ss-btn placed in there has to restate its colours at id
 * specificity locally -- see console_list_buttons.css for the worked
 * example.
 * ------------------------------------------------------------------ */
.ss-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    padding: 8px 16px;
    font-weight: 600;
    line-height: 1;
    white-space: nowrap;
    text-decoration: none;
    cursor: pointer;
    border-radius: var(--ss-btn-radius) !important;
    background-image: none !important;
    box-shadow: none !important;
    text-shadow: none !important;
}

.ss-btn.ss-btn--primary {
    background: var(--ss-btn-primary-bg) !important;
    color: var(--ss-btn-primary-fg) !important;
    border: 1px solid var(--ss-btn-primary-border) !important;
}
.ss-btn.ss-btn--primary:hover {
    /* Brightening the token beats naming a second violet that would have
     * to be kept in step with the first. */
    filter: brightness(1.12);
}

.ss-btn.ss-btn--secondary {
    background: var(--ss-btn-secondary-bg) !important;
    color: var(--ss-btn-secondary-fg) !important;
    border: 1px solid var(--ss-btn-secondary-border) !important;
}
.ss-btn.ss-btn--secondary:hover {
    background: var(--ss-btn-secondary-bg-hover) !important;
    color: var(--ss-btn-secondary-fg-hover) !important;
    border-color: var(--ss-btn-secondary-border-hover) !important;
}

/* For buttons that live in a table cell rather than a toolbar. */
.ss-btn.ss-btn--compact {
    gap: 4px;
    padding: 5px 9px;
    font-size: 12px;
    font-weight: 500;
}

/* A row of buttons that must stay on one line. */
.ss-btn-row {
    display: flex;
    align-items: center;
    gap: 6px;
    white-space: nowrap;
}

/* --- the submit row, in those terms --- */

.submit-row input[type="submit"].default,
.submit-row input[type="submit"].default:hover {
    background: var(--ss-btn-primary-bg) !important;
    background-image: none !important;
    color: var(--ss-btn-primary-fg) !important;
    border: 1px solid var(--ss-btn-primary-border) !important;
    box-shadow: none !important;
}
.submit-row input[type="submit"].default:hover {
    filter: brightness(1.12);
}

.submit-row input[type="submit"]:not(.default),
.submit-row a.button:not(.deletelink) {
    background: var(--ss-btn-secondary-bg) !important;
    background-image: none !important;
    color: var(--ss-btn-secondary-fg) !important;
    border: 1px solid var(--ss-btn-secondary-border) !important;
    box-shadow: none !important;
}
.submit-row input[type="submit"]:not(.default):hover,
.submit-row a.button:not(.deletelink):hover {
    border-color: var(--ss-btn-secondary-border-hover) !important;
    color: var(--ss-btn-secondary-fg-hover) !important;
    background: var(--ss-btn-secondary-bg-hover) !important;
}

/* Delete stays red, pushed to the right edge */
.submit-row a.deletelink,
.submit-row .deletelink-box {
    margin-left: auto !important;
}

/* ============================================================
 * Inline grid column widths
 *
 * Django sizes inline columns from their header text, so "Dante
 * number Autofill" claimed ~280px to hold a 60px box while Name got
 * squeezed. Pin the numeric and checkbox columns, let the text
 * columns take the slack.
 * ============================================================ */

/* Safari's auto table algorithm sizes columns from content and treats
 * width / min-width on cells as a hint, so every `min-width` below was
 * simply discarded there while Chrome honoured it -- the whole reason
 * these grids rendered differently per browser. `table-layout: fixed`
 * takes the widths from the first row (the <thead> cells) and is
 * interpreted identically by both engines.
 *
 * Scoped to the grids this applies to rather than all of
 * `.inline-group table`: fixed layout on a table whose columns have no
 * declared width would split it into equal columns, which would change
 * PA Cable, Amp Channels, Comm and the Mic Tracker inlines. */
.inline-group table {
    width: 100% !important;
}

#consoleinput_set-group table,
#consoleauxoutput_set-group table,
#consolematrixoutput_set-group table,
#consolestereooutput_set-group table,
.inline-group table.dynamic-deviceinput_set,
.inline-group table.dynamic-deviceoutput_set {
    table-layout: fixed !important;
}

/* ============================================================
 * Table and flexible-column caps
 *
 * Without a ceiling these grids stretch to the full content area, and
 * on a 1920 display Source / Name ran to 800-1100px -- far more than a
 * channel label needs, and a long drag of the eye from the name to the
 * checkboxes at the right.
 *
 * `max-width` on a *cell* is ignored under table-layout: fixed (the
 * algorithm reads `width` from the first row and nothing else), so the
 * flexible column cannot be capped directly. Capping the table does it
 * instead: every other column has a declared width, so the flexible one
 * is whatever is left, and bounding the table bounds it to exactly
 * --ss-col-flex-max. It keeps `width: 100%`, so on a narrow window it
 * still gives way rather than forcing a scrollbar.
 * ============================================================ */

#consoleinput_set-group table,
#consoleauxoutput_set-group table,
#consolematrixoutput_set-group table,
#consolestereooutput_set-group table,
.inline-group table.dynamic-deviceinput_set,
.inline-group table.dynamic-deviceoutput_set {
    max-width: var(--ss-grid-max) !important;
}

/* Each grid's own ceiling: the sum of its pinned columns plus the cap on
 * the flexible one. Kept per grid because the pinned set differs.
 * --ss-patch-extra is the Console page's Patch column when the grid is wide
 * enough to show it beside everything else (planner/css/console_patch.css);
 * 0 otherwise, including on the add page, which has no such column. */
#consoleinput_set-group table {
    /* dante + input_ch + source_hardware + 3 narrow (group, DCA, mute)
     * + 2 port (direct out, omni in) + multitrack + delete.
     *
     * Item 4 moved Direct Out and Omni In off --ss-col-num-sm, so this sum
     * had to follow them. Left at "5 narrow" the ceiling would stay 16px
     * wider than the columns beneath it and Source would reach 16px past
     * the cap at 1920. */
    max-width: calc((var(--ss-col-num) * 2) + 150px + (var(--ss-col-num-sm) * 3)
                    + (var(--ss-col-port) * 2)
                    + 80px + var(--ss-col-check) + var(--ss-col-flex-max)
                    + var(--ss-patch-extra, 0px)) !important;
}
#consoleauxoutput_set-group table {
    /* dante + aux + mono_stereo + bus_type + omni_out + multitrack + delete */
    max-width: calc((var(--ss-col-num) * 2) + 150px + 150px + var(--ss-col-num-sm)
                    + 80px + var(--ss-col-check) + var(--ss-col-flex-max)
                    + var(--ss-patch-extra, 0px)) !important;
}
#consolematrixoutput_set-group table {
    /* dante + matrix + mono_stereo + omni_out + multitrack + delete */
    max-width: calc((var(--ss-col-num) * 2) + 150px + var(--ss-col-num-sm)
                    + 80px + var(--ss-col-check) + var(--ss-col-flex-max)
                    + var(--ss-patch-extra, 0px)) !important;
}
#consolestereooutput_set-group table {
    /* dante + stereo_type + omni_out + multitrack + delete */
    max-width: calc(var(--ss-col-num) + 150px + var(--ss-col-num-sm)
                    + 80px + var(--ss-col-check) + var(--ss-col-flex-max)
                    + var(--ss-patch-extra, 0px)) !important;
}

/* Safety net only. With the widths below the grids fit their container at
 * 1280px and wider, so no scrollbar appears; this keeps a very narrow
 * window scrolling the grid rather than mangling it.
 *
 * Note the A/B toggle (admin/js/console_ab_toggle.js) shows Source or
 * Source (B) but never both, so Console Inputs only ever renders one
 * source column + one source-hardware column. */
#consoleinput_set-group .inline-related,
#consoleauxoutput_set-group .inline-related,
#consolematrixoutput_set-group .inline-related,
#consolestereooutput_set-group .inline-related {
    overflow-x: auto;
}

/* Header cells: wrap at spaces, never mid-word.
 *
 * `word-break: break-word` used to split "Multitrack" as "Multitr /
 * ack" and "Dante number" mid-word. Safari and Chrome disagree on
 * where break-word lands, so the two browsers broke different headers. */
#consoleinput_set-group table thead th,
#consoleauxoutput_set-group table thead th,
#consolematrixoutput_set-group table thead th,
#consolestereooutput_set-group table thead th {
    word-break: normal !important;
    overflow-wrap: normal !important;
    hyphens: none !important;
    white-space: normal !important;
    vertical-align: bottom !important;
    line-height: 1.25 !important;
    /* Room under the label for whatever hangs below it (see next rule),
     * reserved on every header so they all share one text baseline --
     * 8px of original padding, a 16px control and a 2px gap. */
    position: relative;
    padding-bottom: 26px !important;
}

/* Header labels all sit on one line, with the Autofill button, the
 * "Manage list" link and the help icon hanging beneath it.
 *
 * These are in the markup after the label text, so in normal flow they
 * take the bottom line of the cell and push the label up: bottom-aligning
 * the cell then lined up the *badges* with the plain headers' text, one
 * line below those headers' labels. Taking them out of flow leaves the
 * label as the cell's only in-flow content, so vertical-align: bottom
 * puts every label -- plain or badged -- on the same line. Each one keeps
 * its own column's horizontal alignment. */
#consoleinput_set-group table thead th > .autofill-btn,
#consoleauxoutput_set-group table thead th > .autofill-btn,
#consolematrixoutput_set-group table thead th > .autofill-btn,
#consolestereooutput_set-group table thead th > .autofill-btn,
#consoleinput_set-group table thead th > .source-hw-manage-link,
#consoleauxoutput_set-group table thead th > .source-hw-manage-link,
#consolematrixoutput_set-group table thead th > .source-hw-manage-link,
#consolestereooutput_set-group table thead th > .source-hw-manage-link,
#consoleinput_set-group table thead th > img.help,
#consoleauxoutput_set-group table thead th > img.help,
#consolematrixoutput_set-group table thead th > img.help,
#consolestereooutput_set-group table thead th > img.help {
    position: absolute;
    bottom: 8px;
    margin: 0 !important;
}

/* left-aligned columns: sit on the cell's own text edge */
#consoleinput_set-group table thead th > .autofill-btn,
#consoleauxoutput_set-group table thead th > .autofill-btn,
#consolematrixoutput_set-group table thead th > .autofill-btn,
#consolestereooutput_set-group table thead th > .autofill-btn {
    left: 4px;
}
#consoleinput_set-group table thead th > .source-hw-manage-link,
#consoleauxoutput_set-group table thead th > .source-hw-manage-link,
#consolematrixoutput_set-group table thead th > .source-hw-manage-link,
#consolestereooutput_set-group table thead th > .source-hw-manage-link {
    left: 10px;
}

/* "MULTITRACK" is ten characters with no space to wrap at, so it cannot
 * be broken without breaking mid-word. At the admin's 11px uppercase
 * header font it is 71px wide -- 15px more than a 64px column leaves --
 * and th overflow is visible, so it slid under the Delete? cell, which
 * paints over it and swallowed the final K. Safari renders it wider than
 * Chrome, so trimming the font alone left no usable margin: give this one
 * column the width it needs and drop its header a point. Delete? still
 * fits a 64px column at the normal header size. */
#consoleinput_set-group table th.column-default_record,
#consoleinput_set-group table td.field-default_record,
#consoleauxoutput_set-group table th.column-default_record,
#consoleauxoutput_set-group table td.field-default_record,
#consolematrixoutput_set-group table th.column-default_record,
#consolematrixoutput_set-group table td.field-default_record,
#consolestereooutput_set-group table th.column-default_record,
#consolestereooutput_set-group table td.field-default_record {
    width: 80px !important;
    min-width: 80px !important;
    max-width: 80px !important;
}

#consoleinput_set-group table thead th.column-default_record,
#consoleauxoutput_set-group table thead th.column-default_record,
#consolematrixoutput_set-group table thead th.column-default_record,
#consolestereooutput_set-group table thead th.column-default_record {
    font-size: 10px !important;
    padding-left: 2px !important;
    padding-right: 2px !important;
}

/* the help icon belongs to the centred checkbox columns */
#consoleinput_set-group table thead th > img.help,
#consoleauxoutput_set-group table thead th > img.help,
#consolematrixoutput_set-group table thead th > img.help,
#consolestereooutput_set-group table thead th > img.help {
    left: 50%;
    transform: translateX(-50%);
}

/* --- numeric columns: fixed, input fills the cell --- */
.inline-group table th.column-dante_number,
.inline-group table td.field-dante_number,
.inline-group table th.column-input_ch,
.inline-group table td.field-input_ch,
.inline-group table th.column-aux_number,
.inline-group table td.field-aux_number,
.inline-group table th.column-matrix_number,
.inline-group table td.field-matrix_number {
    width: var(--ss-col-num) !important;
    min-width: var(--ss-col-num) !important;
    max-width: var(--ss-col-num) !important;
    box-sizing: border-box !important;
    padding-left: 4px !important;
    padding-right: 4px !important;
}

/* Assignment columns hold one or two characters (a group, DCA or omni
 * number), so they get the narrower size and hand the difference to
 * Source / Name. */
.inline-group table th.column-group,
.inline-group table td.field-group,
.inline-group table th.column-dca,
.inline-group table td.field-dca,
.inline-group table th.column-mute,
.inline-group table td.field-mute,
.inline-group table th.column-omni_out,
.inline-group table td.field-omni_out {
    width: var(--ss-col-num-sm) !important;
    min-width: var(--ss-col-num-sm) !important;
    max-width: var(--ss-col-num-sm) !important;
    box-sizing: border-box !important;
    padding-left: 4px !important;
    padding-right: 4px !important;
}

/* Item 4: Direct Out and Omni In are a single port number, so they take a
 * third and narrower size and hand the difference to Source, which is the
 * greedy column and collects all of it.
 *
 * Measured with the viewport at 1280 and the nav sidebar open, which leaves
 * the inline container 900px. Source is 294px minus these two columns, so at
 * 46px each it is 202px, up from 182px at the old 56px. (PR #78's note said
 * 198px at 1280 with the 56px columns; that figure must have been taken with
 * different chrome -- measured here it is 182px.)
 *
 * 46px is squeezed from both sides and there is no room to move: 47px would
 * drop Source to exactly 200.0px with nothing in hand, and 45px would not fit
 * the header (see the next rule for why Safari, not Chrome, sets that floor).
 * Verified in Chrome and Safari at both container widths. */
.inline-group table th.column-direct_out,
.inline-group table td.field-direct_out,
.inline-group table th.column-omni_in,
.inline-group table td.field-omni_in {
    width: var(--ss-col-port) !important;
    min-width: var(--ss-col-port) !important;
    max-width: var(--ss-col-port) !important;
    box-sizing: border-box !important;
    padding-left: 2px !important;
    padding-right: 2px !important;
}

/* "Direct out" wraps to DIRECT / OUT and DIRECT cannot be broken, so the
 * header has to fit the column or it slides under its neighbour the way
 * MULTITRACK once did -- th overflow is visible.
 *
 * Safari sets the floor here, not Chrome, and not by rendering wider: asked
 * for a 9, 10 or 11px font it renders 12px (6px and 8px it honours, so this
 * is not a plain minimum-font-size clamp). DIRECT is therefore 45.0px in
 * Safari against 37.1px in Chrome, and dropping the header another point --
 * the trick the Multitrack column uses -- buys exactly nothing there.
 *
 * So the column is sized for the 12px Safari actually paints: 46px with no
 * horizontal padding leaves 46px of text room against DIRECT's 45.0px, and
 * the slight negative tracking keeps a couple of pixels in hand on a machine
 * whose fallback font runs wider. All measured in both browsers, not
 * estimated. Centred because the inputs beneath them are centred. */
.inline-group table thead th.column-direct_out,
.inline-group table thead th.column-omni_in {
    font-size: 10px !important;
    letter-spacing: -0.2px !important;
    padding-left: 0 !important;
    padding-right: 0 !important;
    text-align: center !important;
}

/* The widths these inputs carry from forms.py / custom_admin.css (40px,
 * 100px, 3ch, max-width 48px) left them floating in an oversized cell. */
.inline-group table td.field-dante_number input,
.inline-group table td.field-input_ch input,
.inline-group table td.field-aux_number input,
.inline-group table td.field-matrix_number input,
.inline-group table td.field-group input,
.inline-group table td.field-dca input,
.inline-group table td.field-mute input,
.inline-group table td.field-direct_out input,
.inline-group table td.field-omni_in input,
.inline-group table td.field-omni_out input {
    width: 100% !important;
    min-width: 0 !important;
    max-width: 100% !important;
    text-align: center !important;
}

/* --- text / select columns: flexible, Name and Source widest --- */
.inline-group table th.column-name,
.inline-group table td.field-name,
.inline-group table th.column-source,
.inline-group table td.field-source,
.inline-group table th.column-source_b,
.inline-group table td.field-source_b {
    /* width: 100%, not auto.
     *
     * Under table-layout: fixed a `display: none` column is still a column
     * and is treated as auto-width, so it takes an equal share of whatever
     * the pinned columns leave over and paints nothing -- the `original`
     * spacer plus the two columns the A/B toggle hides soaked up three
     * quarters of the slack and opened gaps mid-grid. Declaring 100% here
     * makes this the greedy column: it claims the whole remainder, the
     * hidden columns collapse to 0, and the declared widths add up to the
     * table width exactly. */
    width: 100% !important;
}
.inline-group table th.column-source_hardware,
.inline-group table td.field-source_hardware,
.inline-group table th.column-source_hardware_b,
.inline-group table td.field-source_hardware_b,
.inline-group table th.column-mono_stereo,
.inline-group table td.field-mono_stereo,
.inline-group table th.column-stereo_type,
.inline-group table td.field-stereo_type {
    width: 150px !important;
}

/* Bus Type sits with the other selects at 150px. */
.inline-group table th.column-bus_type,
.inline-group table td.field-bus_type {
    width: 150px !important;
}
.inline-group table td.field-name input,
.inline-group table td.field-source input,
.inline-group table td.field-source_b input,
.inline-group table td.field-source_hardware select,
.inline-group table td.field-source_hardware_b select,
.inline-group table td.field-mono_stereo select,
.inline-group table td.field-bus_type select,
.inline-group table td.field-stereo_type select {
    width: 100% !important;
    max-width: 100% !important;
    min-width: 0 !important;
}

/* Name and Source read as labels, not numbers */
.inline-group table td.field-name input,
.inline-group table td.field-source input,
.inline-group table td.field-source_b input {
    text-align: left !important;
}

/* --- checkbox columns: narrow, header and box both centered --- */
/* The multitrack column's field is `default_record`; its label is set to
 * "Multitrack" in planner/forms.py. There is no include_in_multitrack
 * field -- selectors for one used to sit here and matched nothing. */
.inline-group table th.column-default_record,
.inline-group table td.field-default_record,
.inline-group table th.delete,
.inline-group table td.delete {
    width: var(--ss-col-check) !important;
    min-width: var(--ss-col-check) !important;
    max-width: var(--ss-col-check) !important;
    box-sizing: border-box !important;
    padding-left: 4px !important;
    padding-right: 4px !important;
    text-align: center !important;
}
/* header wrapping for these is handled by the grid thead rule above */

/* Django's delete header is a bare <th> with no class, so it cannot be
 * selected by name; :has() scopes this to tables that actually have a
 * delete column, leaving can_delete=False inlines alone. */
.inline-group table:has(td.delete) thead th:last-child {
    width: var(--ss-col-check) !important;
    min-width: var(--ss-col-check) !important;
    max-width: var(--ss-col-check) !important;
    box-sizing: border-box !important;
    padding-left: 4px !important;
    padding-right: 4px !important;
    text-align: center !important;
    white-space: normal !important;
}

/* The "original" spacer column Django emits for each row adds a gap we
 * do not use -- these grids show no object link. */
.inline-group table th.original,
.inline-group table td.original {
    width: 0 !important;
    padding: 0 !important;
}

/* ============================================================
 * Related-field widget icons (edit / add / delete / view)
 *
 * Django ships these as <img> SVGs with fill colors baked in
 * (amber, green, red, blue), which reads as four unrelated
 * controls. Mask the shape and paint it from a token instead, so
 * they are monochrome at rest and accent only on hover.
 * ============================================================ */

.related-widget-wrapper-link {
    display: inline-block;
    width: 16px;
    height: 16px;
    vertical-align: middle;
    background-color: var(--text-muted);
    -webkit-mask-repeat: no-repeat;
    mask-repeat: no-repeat;
    -webkit-mask-position: center;
    mask-position: center;
    -webkit-mask-size: 14px 14px;
    mask-size: 14px 14px;
    transition: background-color .15s ease;
    opacity: 1;
}
.related-widget-wrapper-link > img {
    visibility: hidden;   /* keep it in the a11y tree, hide the colored art */
    width: 14px;
    height: 14px;
}
.related-widget-wrapper-link:hover,
.related-widget-wrapper-link:focus-visible {
    background-color: var(--ss-accent);
}
.related-widget-wrapper-link.change-related {
    -webkit-mask-image: url("../admin/img/icon-changelink.7eddb320e61f.svg");
    mask-image: url("../admin/img/icon-changelink.7eddb320e61f.svg");
}
.related-widget-wrapper-link.add-related {
    -webkit-mask-image: url("../admin/img/icon-addlink.073aeb1feda7.svg");
    mask-image: url("../admin/img/icon-addlink.073aeb1feda7.svg");
}
.related-widget-wrapper-link.delete-related {
    -webkit-mask-image: url("../admin/img/icon-deletelink.564ef9dc3854.svg");
    mask-image: url("../admin/img/icon-deletelink.564ef9dc3854.svg");
}
.related-widget-wrapper-link.view-related {
    -webkit-mask-image: url("../admin/img/icon-viewlink.41eb31f7826e.svg");
    mask-image: url("../admin/img/icon-viewlink.41eb31f7826e.svg");
}


/* ============================================================
 * Bus Type (Aux outputs): neutral control
 *
 * This select used to be painted green for Fixed and red for Variable
 * (select.bus-type-fixed / .bus-type-variable in base_site.html).
 * Both values are ordinary valid states -- nothing about a Variable
 * bus is an error -- so a red field read as a validation failure. It
 * now uses the shared neutral field style like every other select and
 * carries no extra ornament; the value the select shows is the label.
 * ============================================================ */

/* ============================================================
 * I/O Devices input / output grids
 *
 * templates/admin/planner/device_{input,output}_grid.html lay 8 cells
 * per row at width: 12.5%. Under Safari's auto layout a long signal
 * name widened its own cell and pushed the rest out of alignment, so
 * the 8 columns stopped lining up row to row. Fixed layout pins them.
 * ============================================================ */

.inline-group table.dynamic-deviceinput_set td,
.inline-group table.dynamic-deviceoutput_set td {
    width: 12.5% !important;
    box-sizing: border-box !important;
    overflow: hidden;
}

.inline-group table.dynamic-deviceinput_set input[type="text"],
.inline-group table.dynamic-deviceoutput_set input[type="text"] {
    width: 100% !important;
    min-width: 0 !important;
    max-width: 100% !important;
    box-sizing: border-box !important;
}


/* ============================================================
 * Type selects: Fixed / Variable tinting  (item 11)
 *
 * History worth keeping: this select was once filled solid green for Fixed
 * and solid red for Variable, which made an ordinary Variable bus look like
 * a failed field. PR #78 dropped the fills, then dropped the muted badge
 * that briefly replaced them. What was wanted all along is a tint -- enough
 * to scan a column of 40 buses at a glance, not enough to read as an error.
 *
 * The `.is-fixed` / `.is-variable` class is set by the script in
 * base_site.html on load, on every `change`, and on `formset:added`, so the
 * colour tracks the select's value live rather than only at page load.
 *
 * Any other type select gets this by taking `.type-select` plus the same two
 * state classes -- no new colours. Mono/Stereo is the obvious next one.
 * ============================================================ */

.inline-group table td select.type-select.is-fixed,
select.type-select.is-fixed {
    color: var(--bus-fixed) !important;
    border-color: var(--bus-fixed-border) !important;
    background-color: var(--bus-fixed-bg) !important;
}

.inline-group table td select.type-select.is-variable,
select.type-select.is-variable {
    color: var(--bus-variable) !important;
    border-color: var(--bus-variable-border) !important;
    background-color: var(--bus-variable-bg) !important;
}

/* Unset keeps the shared neutral field style: the script applies no state
 * class, so nothing above matches and the plain `select` rules at the top of
 * this file win -- including the greyed "---------" empty label.
 *
 * The open dropdown stays neutral in both states. Tinting the options too
 * made the menu harder to read, and the tint is there to label the *current*
 * value, not the choices. */
.inline-group table td select.type-select.is-fixed option,
.inline-group table td select.type-select.is-variable option,
select.type-select.is-fixed option,
select.type-select.is-variable option {
    color: var(--ss-field-text) !important;
    background-color: var(--ss-field-bg) !important;
}
