/*
 * Koel — the Threads skin
 * -----------------------
 * Named 28 Aug 2026: the koel is an Indian bird, glossy black with glowing
 * crimson eyes — a dark surface with red LEDs, which is exactly this design.
 * Wells hold what you put in, keys are what you press, LEDs report state,
 * and everything sits under Panther-weight shadows on the Egret layout.
 * ----------------------------------
 * Loaded after the production stylesheets and scoped in its entirety under
 * `body.threads-koel`. It restyles the shared chrome classes the production
 * controllers depend on — it never renames them, and it never overrides
 * display, positioning, overflow or animation of anything a controller owns,
 * so every behaviour and every Threads motion survives unchanged.
 *
 * The design values come from the Egret concept study: opaque surfaces at two
 * depths instead of glass over linen, a solid dark rail for the masthead,
 * 16px cards on layered shadows, and smaller, denser type. Accent colours are
 * NOT redefined — the production `--accent*` tokens already carry the same
 * six-palette system, and Koel simply reads them.
 */

body.threads-koel {
    /* Surfaces. The room, the raised card, the quiet band inside it. */
    --koel-rail: #101319;
    --koel-rail-deep: #0a0c10;
    --koel-rail-line: #232833;
    --koel-rail-muted: #7f8998;
    --koel-card: #1e222a;
    --koel-card-quiet: #242832;
    --koel-line: #2c313b;
    --koel-line-strong: #3c434f;
    --koel-ink-strong: #ffffff;
    --koel-muted: #9aa3b1;
    --koel-muted-soft: #6d7684;
    --koel-radius: 16px;
    --koel-radius-sm: 10px;
    /* Panther, not Big Sur: one crisp contact line and one dominant downward
       key shadow with a moderate blur — the Mac OS X 10.3 look MPF asked for,
       28 Aug 2026. No spread, no ambient wash; the shadow lives below the
       surface, and reads lighter than the layered set it replaces. */
    --koel-shadow-card:
        0 1px 2px rgb(0 0 0 / 30%),
        0 6px 20px rgb(0 0 0 / 42%);
    --koel-shadow-raised:
        0 2px 4px rgb(0 0 0 / 35%),
        0 12px 34px rgb(0 0 0 / 55%);

    /* Status colours as r g b, so tints can be mixed at any opacity. */
    --koel-success-rgb: 102 196 140;
    --koel-error-rgb: 239 113 132;

    /* The one property override on body: the linen photograph becomes a solid
       canvas. Background only — the page-entry animation on the production
       body rule keeps playing exactly as it does everywhere else. */
    background: var(--canvas);
}

/* ==========================================================================
   Masthead — a solid Egret rail in place of the glass bar.
   `.site-header` keeps its production sticky/full-bleed geometry untouched.
   ========================================================================== */

.threads-koel .main-navigation {
    border: 0;
    border-bottom: 1px solid var(--koel-rail-line);
    border-radius: 0;
    background: var(--koel-rail);
    box-shadow: 0 1px 0 rgb(0 0 0 / 30%);
}

.threads-koel .koel-brand-identity {
    display: flex;
    flex-direction: column;
    line-height: 1.15;
}

.threads-koel .koel-brand-wordmark {
    color: var(--koel-ink-strong);
    font-size: 19px;
    font-weight: 700;
    letter-spacing: -0.02em;
}

.threads-koel .koel-brand-tagline {
    color: var(--accent-readable);
    font-size: 9.5px;
    font-weight: 700;
    letter-spacing: 0.14em;
    text-transform: uppercase;
}

/* Menu bar entries on the rail. */
.threads-koel .navigation-link {
    color: var(--koel-rail-muted);
    font-weight: 700;
}

.threads-koel .navigation-link:hover {
    background: rgb(255 255 255 / 6%);
    color: var(--koel-ink-strong);
}

.threads-koel .navigation-link.has-current-child,
.threads-koel .navigation-link.is-current {
    color: var(--accent-readable);
}

/* Dropdown panels: opaque card, Egret radius, deeper shadow. */
.threads-koel .navigation-submenu__panel {
    border: 1px solid var(--koel-line-strong);
    border-radius: 12px;
    background: var(--koel-card);
    box-shadow: var(--koel-shadow-raised);
}

.threads-koel .navigation-submenu__link {
    border-radius: 7px;
    color: var(--koel-muted);
}

.threads-koel .navigation-submenu__link:hover {
    background: rgb(255 255 255 / 6%);
    color: var(--koel-ink-strong);
}

.threads-koel .navigation-submenu__link.is-current,
.threads-koel .navigation-submenu__link.has-current-child {
    color: var(--accent-readable);
}

/* The rule between two runs of entries takes the panel's border colour, as
   it does in the production sheet. */
.threads-koel .navigation-submenu__separator {
    background: var(--koel-line-strong);
}

/* ==========================================================================
   Sub-bar — the breadcrumb strip under the masthead, an Egret device.
   Lives inside .site-header, so it stays with the sticky bar.
   ========================================================================== */

.threads-koel .koel-subbar {
    display: flex;
    align-items: center;
    padding: 8px 32px;
    border-bottom: 1px solid var(--koel-rail-line);
    background: var(--koel-rail-deep);
}

.threads-koel .koel-crumbs {
    display: flex;
    align-items: center;
    color: var(--koel-muted-soft);
    font-size: 12px;
    gap: 8px;
}

.threads-koel .koel-crumbs a {
    color: var(--koel-rail-muted);
    text-decoration: none;
}

.threads-koel .koel-crumbs a:hover { color: var(--koel-ink-strong); }

.threads-koel .koel-crumbs [aria-current="page"] {
    color: var(--koel-ink-strong);
    font-weight: 700;
}

/* A small breath between the sticky masthead and the first card, so the
   content never sits flush against the breadcrumb bar. MPF asked for it,
   28 Aug 2026 — very small, on Threads pages only. */
.threads-koel .application-main { padding-top: 14px; }

/* ==========================================================================
   Page scaffolding — eyebrow, heading, lede, and Koel note.
   ========================================================================== */

.threads-koel .koel-page {
    max-width: 1640px;
    margin: 0 auto;
}

.threads-koel .koel-page-head { margin-bottom: 22px; }

.threads-koel .koel-eyebrow {
    margin: 0 0 4px;
    color: var(--accent-readable);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.14em;
    text-transform: uppercase;
}

/* The title itself is the management card's (below): one page-title size on
   every page. Home, the six dashboards, the Cut workspaces and the Maps drew
   theirs at 26px against 24px everywhere else until 29 September 2026 — two
   title pieces for one job (Frost sweep, stray #9). */
.threads-koel .koel-page-head h1 {
    margin: 0;
}

.threads-koel .koel-page-lede {
    max-width: 68ch;
    margin: 6px 0 0;
    color: var(--koel-muted);
    font-size: 13px;
}

/* ==========================================================================
   Cards and the divided stat strip — the heart of the Egret dashboard.
   ========================================================================== */

.threads-koel .koel-card {
    padding: 20px 22px;
    border: 1px solid var(--koel-line);
    border-radius: var(--koel-radius);
    margin-bottom: 20px;
    background: var(--koel-card);
    box-shadow: var(--koel-shadow-card);
}

.threads-koel .koel-card__title {
    margin: 0 0 14px;
    color: var(--koel-muted);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.1em;
    text-transform: uppercase;
}

/* One card, hairlines between the figures — never gaps between tiles. */
.threads-koel .koel-stat-strip {
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: 1fr;
}

.threads-koel .koel-stat {
    display: flex;
    flex-direction: column;
    padding: 4px 22px;
    gap: 3px;
    text-decoration: none;
}

.threads-koel .koel-stat + .koel-stat { border-left: 1px solid var(--koel-line); }
.threads-koel .koel-stat:first-child { padding-left: 2px; }

.threads-koel .koel-stat__label {
    color: var(--koel-muted);
    font-size: 12px;
    font-weight: 700;
}

.threads-koel .koel-stat__value {
    color: var(--koel-ink-strong);
    font-size: 27px;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    letter-spacing: -0.01em;
    transition: color 0.15s;
}

.threads-koel .koel-stat__value--negative { color: var(--error); }

.threads-koel .koel-stat__hint {
    color: var(--koel-muted-soft);
    font-size: 11.5px;
}

a.koel-stat:hover .koel-stat__value { color: var(--accent-readable); }

/* Below tablet width the strip folds into rows and the hairlines rotate. */
@media (max-width: 900px) {
    .threads-koel .koel-stat-strip { grid-auto-flow: row; }
    .threads-koel .koel-stat + .koel-stat {
        border-top: 1px solid var(--koel-line);
        border-left: 0;
    }
    .threads-koel .koel-stat {
        padding: 12px 2px;
    }
    .threads-koel .koel-subbar { padding: 8px 16px; }
}

/* ==========================================================================
   Footer — same structure, Egret surface.
   ========================================================================== */

.threads-koel .application-footer {
    border-color: var(--koel-line);
    background: var(--koel-card);
}

/* ==========================================================================
   Motion — blocks arrive in reading order, a beat apart. The stagger index
   comes from koel.js; with scripting or motion unavailable the rule below
   never fires and the page simply stands there fully drawn.
   ========================================================================== */

@media (prefers-reduced-motion: no-preference) {
    .threads-koel .koel-page > [data-koel-rise] {
        animation: koel-rise-in 520ms cubic-bezier(0.16, 1, 0.3, 1) both;
        animation-delay: calc(var(--koel-i, 0) * 70ms);
    }
}

@keyframes koel-rise-in {
    from { opacity: 0; transform: translateY(12px); }
    to { opacity: 1; transform: none; }
}

/* ==========================================================================
   The Egret table language
   --------------------------------------------------------------------------
   Management pages inside the Koel chrome: the same production markup and
   controllers, restyled. Opaque surfaces at two depths, full-width cards on
   the 1640px canvas, denser 13px tables, softer chrome on every control.
   Colour, border, radius, shadow, spacing and type only — never display,
   positioning, overflow or animation of anything a controller owns.
   ========================================================================== */

/* -- The page and its one card --------------------------------------------- */

.threads-koel .management-page {
    width: 100%;
    max-width: 1640px;
    margin: 0 auto;
}

/* The opt-in wider page. Only a table that cannot fit at 1,640 takes it — see
   the note in the production sheet for why this is not the house width. */
.threads-koel .management-page--wide {
    max-width: 1800px;
}

/* The opt-in narrow page: a card sized to its content rather than to a table.
   Production Type Mappings is three panels and a box of wells, and at the
   house width they sat in a field of empty card. 960 of content plus the
   card's own padding, centred as every page is. MPF, 4 September 2026:
   "make the whole box narrow". Both of that page's cards take it now — see
   the Production Type Mappings section below for why the Mapped Fabrics card
   gave up the standard-table width the same day. */
.threads-koel .management-page--narrow {
    max-width: 1012px;
}

/* The page grows to the table it holds.
   -------------------------------------
   MPF, 12 September 2026: "allow the data table to stretch to 2000px if
   needed so no horizontal scrolls (can this be done across threads?)".
   The 2,000 stood as a ceiling until 25 September 2026, when a 2,005px need
   (Shirts Balance by Item Grouped Beta, Expand all) scrolled 5px under it
   and MPF asked "why do we have an arbitrary limit on the max size", then
   chose "Remove it". Now the window is the only limit.

   The house width is still 1,640 — and 1,750 or 1,800 where a page has been
   given a cap of its own. A page whose table already fits is not stamped at
   all and its layout is byte-identical to yesterday's. Only when the widest
   table on the page cannot fit does the page take exactly the room that table
   asks for.

   CSS alone cannot decide this. `width: fit-content` was the obvious try and
   it answers the wrong question: it is blown out by the max-content width of
   the pill board above the table, not by the table. So the measurement is made
   in threads-production-layout.js and arrives here as `--koel-page-max` on the
   page element, with `data-page-width-fitted` as the switch.

   The attribute carries a third selector's worth of weight deliberately:
   Balance by Shop's 1,750 rule and the `--wide` 1,800 one are narrower
   selectors, and a measured page has to be able to outvote them upwards. It
   only ever votes upwards — the script's floor is the width the page already
   had, so a deliberate cap is never reduced.

   No viewport clamp is needed here or in the script: the page is a block that
   fills its parent, so a max-width wider than the canvas cannot widen the
   document. At 1,440 and at 375 the page is still the canvas minus its
   gutters, and the table wrapper scrolls exactly as it always has. */
.threads-koel .management-page[data-page-width-fitted],
.threads-koel .koel-page[data-page-width-fitted] {
    max-width: var(--koel-page-max, 1640px);
}

/* The page that may go wider than the window.
   --------------------------------------------
   MPF, 5 October 2026, on Jeans Balance by Item Grouped Tabbed alone: "don't
   set the max width or any attribute that limits the table size on this
   particular page", then by popup "Let the page go wider" — the whole page
   grows past the window and the browser's own bar slides it, with no bar
   under the table. A page declares it with the `--uncapped` modifier (that
   page's `pageWidth => 'uncapped'`); no other page carries it.

   The shell has no cap at all, so it fills the canvas as a block does and
   never stops at 1,640. Where its widest table needs more than the canvas
   gives, threads-production-layout.js stamps that width as a FLOOR —
   `--koel-page-min` on <body>, `data-page-width-grown` on the page — and the
   BODY grows to the page plus the gutters it already keeps either side
   (`--koel-page-gutters`, read off the page by the same script: 64 on a desk,
   less on a phone), so the document, not the table's wrapper, takes the
   sideways scroll, and the page keeps its right-hand gutter when slid to the
   end. The page itself carries the floor too, so it is never narrower than
   its table whatever its parent does. The floor is only ever written when
   the canvas is too narrow, so on a window wide enough the page is never
   wider than it needs. None of this touches `--koel-page-max` or
   `data-page-width-fitted`: those are the cap, and this page has none.

   The masthead is canvas-wide and sticky at the top. On a body grown past the
   window it would be document-wide, putting the account and search keys off
   the right edge; so while the page is grown it is held to the window's width
   and stuck at the window's left as well, and the page slides under it the
   way rows slide under it going down. `100vw` is the canvas here, not the
   window: the root's `scrollbar-gutter: stable` takes the bar out of it. */
.threads-koel .management-page--uncapped {
    max-width: none;
}

.threads-koel .management-page--uncapped[data-page-width-grown] {
    min-width: var(--koel-page-min);
}

body.threads-koel:has(.management-page--uncapped[data-page-width-grown]) {
    min-width: calc(var(--koel-page-min) + var(--koel-page-gutters, 64px));
}

body.threads-koel:has(.management-page--uncapped[data-page-width-grown]) > .site-header {
    left: 0;
    width: 100vw;
}

/* The PPC choosers' own card is this card too (MPF, 8 Oct 2026): Permissions'
   panels stand on it, and the chooser should read the same. */
.threads-koel :is(.management-card, .ppc-chooser-card) {
    padding: 24px 26px 26px;
    border: 1px solid var(--koel-line);
    border-radius: var(--koel-radius);
    background: var(--koel-card);
    box-shadow: var(--koel-shadow-card);
    backdrop-filter: none;
}

.threads-koel .management-eyebrow {
    color: var(--accent-readable);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.14em;
    text-transform: uppercase;
}

.threads-koel .management-card h1,
.threads-koel .koel-page-head h1 {
    color: var(--koel-ink-strong);
    font-size: 24px;
    font-weight: 700;
    letter-spacing: -0.02em;
}

.threads-koel .management-description {
    color: var(--koel-muted);
    font-size: 13px;
}

/* -- Lifecycle and segment tabs -------------------------------------------- */

.threads-koel .user-table-lifecycle-tabs {
    gap: 2px;
    padding: 3px;
    border: 1px solid rgb(255 255 255 / 6%);
    border-radius: 11px;
    background: #0d0d0d;
    box-shadow:
        inset 0 1px 1px 1px rgb(0 0 0 / 50%),
        0 1px 0 0 rgb(255 255 255 / 6%);
}

.threads-koel .user-table-lifecycle-tab {
    border-radius: 8px;
    color: var(--koel-muted);
    font-weight: 700;
}

.threads-koel .user-table-lifecycle-tab:hover { color: var(--koel-ink-strong); }

/* The current tab sits pressed IN — a sunken well like every chosen thing in
   Koel — with its LED lit and its count inverted to a small raised chip. No
   halo: production draws an accent ring here that fights the aesthetic. */
.threads-koel .user-table-lifecycle-tab.is-current {
    border-color: transparent;
    outline: none;
    background: #0d0d0d;
    box-shadow:
        inset 0 1px 1px 1px rgb(0 0 0 / 50%),
        0 1px 0 0 rgb(255 255 255 / 6%);
    color: var(--accent-readable);
}

.threads-koel .user-table-lifecycle-tab:focus-visible {
    border-color: rgb(var(--accent-readable-rgb) / 45%);
    outline: none;
}

/* Which view you are on is state, and state carries an LED here: the current
   tab's white dot glows in the accent, like the switches glow their colour. */
.threads-koel .user-table-lifecycle-tab.is-current::before {
    display: inline-block;
    width: 5px;
    height: 5px;
    border-radius: 50%;
    margin-right: 7px;
    background: #ffffff;
    box-shadow:
        0 0 5px 2px rgb(var(--accent-readable-rgb) / 85%),
        0 0 3px 1px rgb(var(--accent-readable-rgb) / 85%);
    content: "";
}

/* The count pill is a tiny well set into its tab — recessed against the
   raised key of the current tab, accent ink when its tab is the one. */
.threads-koel .user-table-lifecycle-count {
    border-radius: 999px;
    background: #0d0d0d;
    box-shadow: inset 0 1px 1px 0 rgb(0 0 0 / 45%);
    color: var(--koel-muted);
}

.threads-koel .user-table-lifecycle-tab.is-current .user-table-lifecycle-count {
    background: #1f2127;
    box-shadow:
        inset 0 1px 0 0 rgb(255 255 255 / 9%),
        0 1px 2px rgb(0 0 0 / 40%);
    color: var(--accent-readable);
}

/* -- Toolbar: search, clear-all, exports ----------------------------------- */

/* Text fields are wells: recessed near-black, the same surface the toggle
   track and the tab rail sit in. You put things into a well.

   `user-table-numeric-filter` is deliberately NOT in this list: it is the
   class production puts on the numeric column's <th>, not on its input, so
   including it painted the whole cell as a well with the real field sunk
   inside it (MPF spotted the doubled inset, 28 Aug 2026). The input carries
   `user-table-column-text-filter` and is covered above. */
.threads-koel .user-table-search input,
.threads-koel .user-table-column-text-filter {
    border: 1px solid rgb(255 255 255 / 7%);
    border-radius: var(--koel-radius-sm);
    background: #0d0d0d;
    box-shadow: inset 0 1px 1px 1px rgb(0 0 0 / 50%);
    color: var(--ink);
}

/* The same halo we removed from the triggers, options, tabs and date
   picker: focus is a quiet accent edge on the well, never a ring. */
.threads-koel .user-table-search input:focus {
    border-color: rgb(var(--accent-readable-rgb) / 55%);
    box-shadow: inset 0 1px 1px 1px rgb(0 0 0 / 50%);
}

/* Toolbar buttons are keys, like the row actions. */
.threads-koel .user-table-export-button,
.threads-koel .user-table-clear-all {
    border: 1px solid rgb(255 255 255 / 7%);
    border-radius: var(--koel-radius-sm);
    background: #16181d;
    box-shadow:
        inset 0 1px 0 0 rgb(255 255 255 / 7%),
        0 1px 2px rgb(0 0 0 / 40%);
    color: var(--koel-muted);
}

.threads-koel .user-table-export-button:hover,
.threads-koel .user-table-clear-all:hover:not(:disabled) {
    border-color: rgb(255 255 255 / 12%);
    background: #1f2127;
    color: var(--koel-ink-strong);
}

/* -- The table ------------------------------------------------------------- */

.threads-koel .user-table { font-size: 14px; }

/* The heading band. Opaque because it is a band, and because a sticky heading
   must not let rows scroll through it. */
.threads-koel .user-table thead th {
    background: #1a1d23;
}

.threads-koel .user-table th,
.threads-koel .user-table td {
    padding: 11px 14px;
    border-bottom: 1px solid var(--koel-line);
}

/* The compact table: `user-table--compact`, a declared density of the house
   table for a management table with more columns than the standard surface
   holds at 1440 — Shirt Cards and TR Cards (thirteen each: Item Number,
   Colours, Deemed Cards and Status beside the Requirement axes). It recovers
   width from table chrome, never data: 6px cell sides, a tighter filter row
   and trigger, smaller sort gaps. Before 29 September 2026 each Cards page
   carried its own id-scoped copy of these rules (`#shirt-cards-table`,
   `#tr-cards-table` in threads-production-ui.css — Frost sweep, stray #4).
   With the standard's 14px sides both tables need ~1,655px and scroll 350px
   at 1440, which is what MPF had those rules written against on 6 September
   2026 ("the first live Card put a scrollbar under the table"), so the
   density stays — as one modifier on the shared table, not a copy per page.
   It lives here, beside the cell padding it tightens, so it outvotes it by
   one class rather than by an id. */
.threads-koel .user-table.user-table--compact {
    width: 100%;
}

.threads-koel .user-table.user-table--compact th,
.threads-koel .user-table.user-table--compact td {
    padding-inline: 6px;
}

.threads-koel .user-table.user-table--compact .user-table-selection-cell {
    width: 38px;
    min-width: 38px;
    padding-inline: 10px;
}

.threads-koel .user-table.user-table--compact .user-table-filter-row th {
    padding: 8px 4px 11px;
}

.threads-koel .user-table.user-table--compact .user-table-filter > summary.threads-select__trigger {
    gap: 5px;
    min-height: 36px;
    padding: 7px 6px;
    font-size: 12px;
}

.threads-koel .user-table.user-table--compact .user-table-filter__status {
    font-size: 10px;
}

.threads-koel .user-table.user-table--compact .user-table-sort-button {
    gap: 4px;
}

.threads-koel .user-table.user-table--compact .user-table-sort-indicator {
    min-width: 12px;
}

.threads-koel .user-table.user-table--compact .user-table-column-text-filter {
    min-width: 0;
    padding-inline: 6px;
}

/* Typography for every th, including a report's row labels, which are
   `th scope="row"` inside the body. The background is deliberately NOT here:
   painting every th opaque ran a slab down the first column so the banding
   looked like it started at column two, which is what MPF saw. It moves to the
   heading cells below, and a body row-header is then left to take its own row's
   stripe and hover like any other cell. 30 Aug 2026. */
.threads-koel .user-table th {
    color: var(--koel-muted);
    font-size: 12px;
    font-weight: 700;
    letter-spacing: 0.07em;
    text-transform: uppercase;
}

.threads-koel .user-table td { color: var(--ink); font-size: 14px; }

/* Deliberate view changes — a tab, a filter, a page — transition instead of
   teleporting: the control acknowledges instantly, and the new rows arrive
   as a short staggered wave, so the table reads as refilling rather than
   blinking. The controller already raises is-rows-entering for exactly these
   actions and never for search, which stays instant. Hidden rows share the
   tbody, so the stagger counts only visible data rows. */
.threads-koel .user-table > tbody.is-rows-entering {
    animation: none;
}

@keyframes koel-row-in {
    from { opacity: 0; transform: translateY(7px); }
    to { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: no-preference) {
    .threads-koel .user-table > tbody.is-rows-entering > tr.user-table__data-row:not([hidden]) {
        animation: koel-row-in 300ms cubic-bezier(0.22, 0.61, 0.36, 1) both;
        animation-delay: 290ms;
    }

    .threads-koel .user-table > tbody.is-rows-entering > tr:nth-child(1 of .user-table__data-row:not([hidden])) { animation-delay: 0ms; }
    .threads-koel .user-table > tbody.is-rows-entering > tr:nth-child(2 of .user-table__data-row:not([hidden])) { animation-delay: 26ms; }
    .threads-koel .user-table > tbody.is-rows-entering > tr:nth-child(3 of .user-table__data-row:not([hidden])) { animation-delay: 52ms; }
    .threads-koel .user-table > tbody.is-rows-entering > tr:nth-child(4 of .user-table__data-row:not([hidden])) { animation-delay: 78ms; }
    .threads-koel .user-table > tbody.is-rows-entering > tr:nth-child(5 of .user-table__data-row:not([hidden])) { animation-delay: 104ms; }
    .threads-koel .user-table > tbody.is-rows-entering > tr:nth-child(6 of .user-table__data-row:not([hidden])) { animation-delay: 130ms; }
    .threads-koel .user-table > tbody.is-rows-entering > tr:nth-child(7 of .user-table__data-row:not([hidden])) { animation-delay: 156ms; }
    .threads-koel .user-table > tbody.is-rows-entering > tr:nth-child(8 of .user-table__data-row:not([hidden])) { animation-delay: 182ms; }
    .threads-koel .user-table > tbody.is-rows-entering > tr:nth-child(9 of .user-table__data-row:not([hidden])) { animation-delay: 208ms; }
    .threads-koel .user-table > tbody.is-rows-entering > tr:nth-child(10 of .user-table__data-row:not([hidden])) { animation-delay: 234ms; }
    .threads-koel .user-table > tbody.is-rows-entering > tr:nth-child(11 of .user-table__data-row:not([hidden])) { animation-delay: 260ms; }
    .threads-koel .user-table > tbody.is-rows-entering > tr:nth-child(12 of .user-table__data-row:not([hidden])) { animation-delay: 286ms; }

    /* A group subtotal arrives with the rows it closes rather than being
       already there when they land: it is not a record, so it takes no place
       in the stagger, but a line that never moved while the table refilled
       around it read as part of the furniture. */
    .threads-koel .user-table > tbody.is-rows-entering > tr.user-table-subtotal-row {
        animation: koel-row-in 300ms cubic-bezier(0.22, 0.61, 0.36, 1) both;
        animation-delay: 290ms;
    }
}

/* The hovered row keeps production's glow. It was flattened here early on,
   before the reports were in Koel — and the reports never lost it, so a
   Threads built entirely of the same tables highlighted its rows two
   different ways depending on which page you were on. MPF saw the report's
   version and asked for it everywhere, 28 Aug 2026, so the override is gone
   and every table glows alike. */

.threads-koel .user-table-sort-button {
    color: inherit;
    font: inherit;
    letter-spacing: inherit;
    text-transform: inherit;
}

/* The core sheet already turns a sorted heading accent. The rule above matches
   at the very same specificity and this skin loads afterwards, so it quietly
   won and flattened every sorted column back to the muted heading grey — the
   accent was written, it just never survived the skin. Restored here, where the
   skin can win.

   --accent-readable rather than the core's --accent-hover: the heading is 11px
   uppercase on a dark bar, and the raw accent goes muddy at that size. The
   arrow and its priority number inherit from the button, so they come with it.
   MPF asked for the header and the arrow to carry the accent, 30 Aug 2026. */
.threads-koel .user-table-sort-button:hover,
.threads-koel .user-table-sort-button:focus-visible,
.threads-koel .user-table-sort-button.is-sorted {
    color: var(--accent-readable);
}

/* -- Column filter panels and selects -------------------------------------- */

.threads-koel .user-table-filter__panel,
.threads-koel .threads-select__panel,
.threads-koel .threads-date__panel {
    border: 1px solid var(--koel-line-strong);
    border-radius: 12px;
    background: var(--koel-card);
    box-shadow: var(--koel-shadow-raised);
    backdrop-filter: none;
}

.threads-koel .threads-select__trigger,
.threads-koel .threads-date__trigger {
    border: 1px solid rgb(255 255 255 / 7%);
    border-radius: var(--koel-radius-sm);
    background: #0d0d0d;
    box-shadow: inset 0 1px 1px 1px rgb(0 0 0 / 45%);
    color: var(--ink);
}

.threads-koel .threads-select__trigger:focus-visible,
.threads-koel .threads-select__trigger.is-open,
.threads-koel .threads-date__trigger:focus-visible {
    border-color: var(--accent-readable);
    box-shadow: 0 0 0 3px rgb(var(--accent-readable-rgb) / 18%);
}

.threads-koel .threads-select__search,
.threads-koel .user-table-filter__search {
    border: 1px solid rgb(255 255 255 / 7%);
    border-radius: 8px;
    background: #0d0d0d;
    box-shadow: inset 0 1px 1px 1px rgb(0 0 0 / 45%);
    color: var(--ink);
}

/* Inside the panels, choosing presses the option IN: a chosen option sits as
   a sunken well — the same material as the toggle track and the pills — with
   accent ink at the bottom of it. Hover hints at the press with a shallow
   dip. A pressed key stays down; MPF's call, 28 Aug 2026. */
.threads-koel .threads-select__option,
.threads-koel .user-table-filter__option {
    border-radius: 8px;
    color: var(--ink);
}

.threads-koel .threads-select__option:hover,
.threads-koel .user-table-filter__option:hover {
    background: rgb(0 0 0 / 30%);
    box-shadow: inset 0 1px 2px 0 rgb(0 0 0 / 35%);
}

.threads-koel .threads-select__option[aria-selected="true"],
.threads-koel .user-table-filter__option:has(input:checked) {
    background: #0d0d0d;
    box-shadow:
        inset 0 1px 1px 1px rgb(0 0 0 / 50%),
        0 1px 0 0 rgb(255 255 255 / 6%);
    color: var(--accent-readable);
}

/* A breath more room inside the panel, so the wells and keys read as such. */
.threads-koel .user-table-filter__panel,
.threads-koel .threads-select__panel {
    padding: 8px;
}

/* Clear-all and Select-all-visible are simply the first two rows of the same
   list: identical metrics to an option, a glyph where an option has its
   checkbox, and the same hover dip. MPF asked for the column filters to be
   the dialog dropdown, full stop — so nothing here may look like a separate
   deck (28 Aug 2026). */
.threads-koel .user-table-filter__panel.threads-select__panel .user-table-filter__select-visible:not([hidden]) {
    margin: 0;
}

.threads-koel .user-table-filter__panel.threads-select__panel .user-table-filter__clear,
.threads-koel .user-table-filter__panel .user-table-filter__select-visible {
    display: flex;
    align-items: center;
    width: 100%;
    min-height: 36px;
    margin: 0;
    /* The option's own resolved metrics, not the filter stylesheet's: with
       both classes on the element the shared select rule wins, so these must
       match it or the rows sit differently in the same list. */
    padding: 7px 10px;
    border: 0;
    border-radius: 8px;
    background: transparent;
    box-shadow: none;
    color: var(--koel-muted);
    font: inherit;
    font-size: 14px;
    text-align: left;
}

/* The glyphs are CSS masks: the controller rewrites these buttons' text (the
   live count) and would wipe any child markup, but a ::before survives. */
.threads-koel .user-table-filter__panel.threads-select__panel .user-table-filter__clear::before,
.threads-koel .user-table-filter__panel .user-table-filter__select-visible::before {
    display: inline-block;
    width: 14px;
    height: 14px;
    flex: 0 0 14px;
    margin-right: 9px;
    background-color: currentColor;
    content: "";
    -webkit-mask: var(--koel-glyph) center / contain no-repeat;
    mask: var(--koel-glyph) center / contain no-repeat;
}

.threads-koel .user-table-filter__panel.threads-select__panel .user-table-filter__clear {
    --koel-glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='9'/%3E%3Cpath d='m9 9 6 6m0-6-6 6'/%3E%3C/svg%3E");
}

.threads-koel .user-table-filter__panel .user-table-filter__select-visible {
    --koel-glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m3 6 1.5 1.5L7 5'/%3E%3Cpath d='m3 12.5 1.5 1.5L7 11.5'/%3E%3Cpath d='m3 19 1.5 1.5L7 18'/%3E%3Cpath d='M10.5 6.2h10M10.5 12.7h10M10.5 19.2h10'/%3E%3C/svg%3E");
}

.threads-koel .user-table-filter__panel.threads-select__panel .user-table-filter__clear:hover:not(:disabled),
.threads-koel .user-table-filter__panel .user-table-filter__select-visible:hover:not(:disabled) {
    background: rgb(0 0 0 / 30%);
    box-shadow: inset 0 1px 2px 0 rgb(0 0 0 / 35%);
    color: var(--koel-ink-strong);
}

.threads-koel .user-table-filter__panel.threads-select__panel .user-table-filter__clear:disabled,
.threads-koel .user-table-filter__panel .user-table-filter__select-visible:disabled {
    background: transparent;
    box-shadow: none;
    color: var(--koel-muted-soft);
    cursor: not-allowed;
    opacity: 1;
}

/* Nothing bespoke about the scroll: the panel scrolls as one surface, the
   way the dialog dropdowns do. Their shared .threads-select__panel rule
   already says how.

   Production pins the search at the top of that scroller and Clear all
   just below it, which is why Koel ever needed opaque seals behind
   them. A dialog dropdown pins nothing — its search scrolls away with the
   list — so neither does this. Unpinning them is the last difference
   between the two panels, and it is what stops options from tracking
   through the command rows now that those rows are plain (MPF, 28 Aug
   2026). Position only; no controller reads it. */
.threads-koel .user-table-filter__panel .user-table-filter__search,
.threads-koel .user-table-filter__panel.threads-select__panel .user-table-filter__clear,
.threads-koel .user-table-filter__panel .user-table-filter__select-visible {
    position: static;
    top: auto;
    z-index: auto;
}


/* Type matches the dialog dropdown exactly: production's own option and
   search sizes, with no filter-only bump. */
.threads-koel .user-table-filter > summary.threads-select__trigger { font-size: 14px; }


/* Between pages: the production departure plays, the browser asks the server
   for the next document, and on a slow answer the room used to just sit
   empty. If the wait outlasts the departure, the brand thread stitches
   itself mid-canvas until the next page lands — the same loader MPF chose
   from the gallery, 28 Aug 2026. koel.js owns the timing; pages that arrive
   promptly never see it. */
.threads-koel .koel-page-loader {
    position: fixed;
    inset: 0;
    z-index: 9998;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgb(10 12 16 / 55%);
    opacity: 0;
    pointer-events: none;
    transition: opacity 240ms ease-out;
}

.threads-koel .koel-page-loader.is-waiting { opacity: 1; }

@media (prefers-reduced-motion: no-preference) {
    .threads-koel .koel-page-loader [data-koel-thread] {
        animation: koel-thread-draw 2.6s cubic-bezier(0.45, 0.05, 0.35, 0.95) infinite;
    }

    .threads-koel .koel-page-loader [data-koel-eye] {
        animation: koel-eye-blink 2.6s ease-in-out infinite;
    }
}

@keyframes koel-thread-draw {
    0% { stroke-dashoffset: 100; }
    55%, 70% { stroke-dashoffset: 0; }
    100% { stroke-dashoffset: -100; }
}

@keyframes koel-eye-blink {
    0%, 45% { opacity: 0; }
    55%, 75% { opacity: 1; }
    100% { opacity: 0; }
}

/* Panels arrive the way the menu does — the same 160ms fade, rise and settle
   as navigation-submenu-open, so every floating surface in the Console opens
   with one gesture. Production shows these by removing [hidden], which
   re-renders the element, so the animation replays on every open. */
@keyframes koel-panel-open {
    from { opacity: 0; transform: translateY(-6px) scale(0.98); }
    to { opacity: 1; transform: none; }
}

@keyframes koel-panel-open-up {
    from { opacity: 0; transform: translateY(6px) scale(0.98); }
    to { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: no-preference) {
    /* Keyed to the OPEN-state markers, not to visibility: the controllers
       hide these panels without resetting them, so an animation keyed to
       visibility played once on the first open and never again. The state
       leaves and returns on every open, restarting the animation each time.
       A column filter is a native <details> — its truth is the [open]
       attribute; dialog selects and the date picker mark is-open; a select
       panel lifted to the top layer answers to :popover-open. */
    /* Slower than the menu's 160ms: these panels are larger surfaces and MPF
       wanted the arrival — and now the departure — readable, 28 Aug 2026. */
    .threads-koel details.user-table-filter[open] > .user-table-filter__panel,
    .threads-koel .threads-select.is-open > .threads-select__panel,
    .threads-koel .threads-date.is-open > .threads-date__panel,
    .threads-koel .threads-select__panel--top-layer:popover-open {
        animation: koel-panel-open 300ms cubic-bezier(0.22, 0.61, 0.36, 1) both;
    }

    /* A panel that opens upward rises from below its trigger instead. */
    .threads-koel .threads-select.is-open-up > .threads-select__panel {
        animation: koel-panel-open-up 300ms cubic-bezier(0.22, 0.61, 0.36, 1) both;
    }

    /* The filter panel's EXIT cannot be done in CSS at all — the controller
       collapses the details and the top layer in one tick, faster than any
       transition — so koel.js fades a positioned snapshot of the panel
       instead. The entry above replays because the details [open] attribute
       genuinely leaves and returns on every open. */

    .threads-koel .user-table-filter__panel[popover]:popover-open {
        opacity: 1;
        transform: none;
    }

    @starting-style {
        .threads-koel .user-table-filter__panel[popover]:popover-open {
            opacity: 0;
            transform: translateY(-8px) scale(0.98);
        }
    }
}

/* -- Bulk selection bar ---------------------------------------------------- */

/* The bulk-actions bar is a well strip: the selection count reads in accent
   ink, and each action is a key. The danger key speaks in the error red. */
.threads-koel .user-table-bulk-actions {
    border: 1px solid rgb(255 255 255 / 6%);
    border-radius: var(--koel-radius-sm);
    background: #0d0d0d;
    box-shadow:
        inset 0 1px 1px 1px rgb(0 0 0 / 50%),
        0 1px 0 0 rgb(255 255 255 / 6%);
}

.threads-koel .user-table-bulk-actions strong { color: var(--accent-readable); }

.threads-koel .user-table-bulk-actions__divider { background: rgb(255 255 255 / 8%); }

.threads-koel .user-table-bulk-action {
    padding: 6px 11px;
    border: 1px solid rgb(255 255 255 / 7%);
    border-radius: 8px;
    background: #16181d;
    box-shadow:
        inset 0 1px 0 0 rgb(255 255 255 / 7%),
        0 1px 2px rgb(0 0 0 / 40%);
    color: var(--koel-muted);
}

.threads-koel .user-table-bulk-action:hover:not(:disabled) {
    border-color: rgb(255 255 255 / 12%);
    background: #1f2127;
    color: var(--koel-ink-strong);
}

.threads-koel .user-table-bulk-action--danger:hover:not(:disabled) { color: var(--error); }

.threads-koel .user-table-bulk-action:disabled {
    box-shadow: none;
    background: transparent;
    border-color: rgb(255 255 255 / 4%);
}

/* -- Row actions ----------------------------------------------------------- */

/* The action cluster centres its keys on the switch's own midline — the
   toggle button is 44px tall against the 30px keys, and without this the
   keys hang from its top edge, sitting 7px high of the track. */
.threads-koel .user-table-row-actions { align-items: center; }

/* Row actions as keys on the instrument panel: the same near-black surface,
   inset depth and top-edge highlight as the switch beside them. No glow —
   glow means state, and these are actions. Icons brighten on hover instead. */
.threads-koel .user-table-row-action {
    width: 34px;
    height: 34px;
    border: 1px solid rgb(255 255 255 / 7%);
    border-radius: 8px;
    background: #0d0d0d;
    box-shadow:
        inset 0 1px 1px 1px rgb(0 0 0 / 50%),
        0 1px 0 0 rgb(255 255 255 / 7%);
    color: var(--koel-muted);
}

.threads-koel .user-table-row-action:hover:not(:disabled) {
    background: #16181d;
    color: var(--accent-readable);
}

.threads-koel .user-table-row-action--danger:hover:not(:disabled) {
    background: #16181d;
    color: var(--error);
}

.threads-koel .user-table-row-action:active:not(:disabled) {
    box-shadow: inset 0 2px 3px 1px rgb(0 0 0 / 60%);
}

.threads-koel .user-table-row-action__icon { display: block; margin: 0 auto; }

/* -- Checkboxes ------------------------------------------------------------
   A checkbox is a small well; ticking it fills the well with the accent key.
   The tick itself is production's border-drawn glyph, untouched. */

.threads-koel input[type="checkbox"] {
    border: 1px solid rgb(255 255 255 / 7%);
    background: #0d0d0d;
    box-shadow: inset 0 1px 1px 1px rgb(0 0 0 / 45%);
}

.threads-koel input[type="checkbox"]:hover:not(:disabled):not(:checked) {
    border-color: rgb(255 255 255 / 16%);
}

.threads-koel input[type="checkbox"]:checked {
    border-color: transparent;
    background: var(--accent);
    box-shadow:
        inset 0 1px 0 0 rgb(255 255 255 / 22%),
        0 1px 2px rgb(0 0 0 / 40%);
}

/* -- Buttons --------------------------------------------------------------- */

/* One button voice: every .button is the same key as the toolbar's, and the
   primary is a keycap — a slight top-lit gradient over the accent, so it
   reads as the most pressable thing on the page without shouting. */
.threads-koel .button {
    /* Buttons here are laid out as blocks with a min-height, so their label
       sat against the top padding rather than in the middle — and a button
       whose label is wrapped in a span (Save preference) sat a few pixels
       lower than one with bare text (Change password), which is the mismatch
       MPF spotted, 28 Aug 2026. Centring the content fixes both. The gap
       replaces the whitespace that flex layout collapses between an icon and
       its label. */
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    min-height: 36px;
    padding: 7px 14px;
    border: 1px solid rgb(255 255 255 / 7%);
    border-radius: 9px;
    background: #16181d;
    box-shadow:
        inset 0 1px 0 0 rgb(255 255 255 / 8%),
        0 1px 2px rgb(0 0 0 / 40%);
    color: var(--ink);
    font-size: 13px;
    font-weight: 700;
}

.threads-koel .button:hover {
    border-color: rgb(255 255 255 / 13%);
    background: #1f2127;
}

.threads-koel .button:active { box-shadow: inset 0 2px 3px 1px rgb(0 0 0 / 55%); }

/* The primary button is the accent key: same top-edge highlight and press
   as every other key, no hover glow — glow means state, not affordance. */
.threads-koel .button--primary {
    border-color: rgb(0 0 0 / 35%);
    background: linear-gradient(180deg, var(--accent-hover), var(--accent));
    box-shadow:
        inset 0 1px 0 0 rgb(255 255 255 / 22%),
        0 1px 2px rgb(0 0 0 / 45%);
    color: var(--accent-ink);
}

.threads-koel .button--primary:hover {
    background: linear-gradient(180deg, var(--accent-hover), var(--accent-hover));
    box-shadow:
        inset 0 1px 0 0 rgb(255 255 255 / 22%),
        0 2px 5px rgb(0 0 0 / 50%);
}

.threads-koel .button--danger {
    border-color: rgb(239 113 132 / 45%);
    background: rgb(239 113 132 / 14%);
    color: var(--error);
}

/* -- Dialogs --------------------------------------------------------------- */

.threads-koel .user-dialog-surface {
    border: 1px solid var(--koel-line-strong);
    border-radius: var(--koel-radius);
    background: var(--koel-card);
    box-shadow: var(--koel-shadow-raised);
    backdrop-filter: none;
}

.threads-koel .user-dialog::backdrop { background: rgb(6 7 10 / 62%); }

.threads-koel .form-field input,
.threads-koel .form-field select,
.threads-koel .form-field textarea {
    border: 1px solid rgb(255 255 255 / 7%);
    border-radius: var(--koel-radius-sm);
    background: #0d0d0d;
    box-shadow: inset 0 1px 1px 1px rgb(0 0 0 / 45%);
    color: var(--ink);
}

.threads-koel .form-field input:focus,
.threads-koel .form-field select:focus {
    border-color: var(--accent-readable);
    box-shadow: 0 0 0 3px rgb(var(--accent-readable-rgb) / 18%);
}

/* -- Order editor, status tags, footer counts ------------------------------ */

.threads-koel .order-editor-item {
    border: 1px solid var(--koel-line);
    border-radius: var(--koel-radius-sm);
    background: var(--koel-card-quiet);
}

/* Neutral tags — the rounding "Default" and friends — are quiet dark chips
   in the same material, with no LED: they are information, not state. */
.threads-koel .status-tag {
    border-color: rgb(255 255 255 / 6%);
    background: #16181d;
    box-shadow: inset 0 1px 1px 0 rgb(0 0 0 / 35%);
    color: var(--koel-muted);
}

.threads-koel .user-table-result-count,
.threads-koel .user-table-page-size,
.threads-koel .user-table-page-count { color: var(--koel-muted); font-size: 12.5px; }

/* -- Pagination ------------------------------------------------------------
   Page buttons are keys; the current page is the raised key with accent ink,
   exactly like the current lifecycle tab; the rows-per-page select is a well. */

.threads-koel .user-table-pagination-button {
    border: 1px solid rgb(255 255 255 / 7%);
    border-radius: 8px;
    background: #16181d;
    box-shadow:
        inset 0 1px 0 0 rgb(255 255 255 / 7%),
        0 1px 2px rgb(0 0 0 / 40%);
    color: var(--koel-muted);
}

.threads-koel .user-table-pagination-button:hover:not(:disabled) {
    border-color: rgb(255 255 255 / 12%);
    background: #1f2127;
    color: var(--koel-ink-strong);
}

/* The page you are on is pressed in, like every current thing in Koel. */
.threads-koel .user-table-pagination-button.is-current {
    border-color: transparent;
    background: #0d0d0d;
    box-shadow:
        inset 0 1px 1px 1px rgb(0 0 0 / 50%),
        0 1px 0 0 rgb(255 255 255 / 6%);
    color: var(--accent-readable);
}

/* The current page is disabled too — you cannot press the key you are on —
   so the flattening reserved for genuinely inert buttons must not strip it. */
.threads-koel .user-table-pagination-button:disabled:not(.is-current) {
    box-shadow: none;
    background: transparent;
    border-color: rgb(255 255 255 / 4%);
}

/* The rows-per-page select drops the browser's native costume: a well with
   its own quiet chevron, sized like the page keys beside it. Where the
   browser supports customizable selects, the OPEN list becomes a Koel panel
   too — options as keys, the chosen one raised in accent ink; elsewhere it
   falls back to the native popup untouched. */



.threads-koel .user-table-page-size select {
    min-height: 34px;
    padding: 6px 30px 6px 12px;
    appearance: none;
    -webkit-appearance: none;
    border: 1px solid rgb(255 255 255 / 7%);
    border-radius: 8px;
    background-color: #0d0d0d;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%239aa3b1' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 9px center;
    background-size: 13px;
    box-shadow: inset 0 1px 1px 1px rgb(0 0 0 / 45%);
    color: var(--ink);
    font-weight: 700;
}

@supports (appearance: base-select) {
    .threads-koel .user-table-page-size select,
    .threads-koel .user-table-page-size select::picker(select) {
        appearance: base-select;
    }

    /* base-select draws its own indicator and lays the control out as a flex
       box: the painted chevron doubled it, and the default alignment left the
       label sitting above centre (MPF, 28 Aug 2026). */
    .threads-koel .user-table-page-size select {
        align-items: center;
        background-image: none;
        padding-right: 12px;
    }

    .threads-koel .user-table-page-size select::picker(select) {
        padding: 6px;
        border: 1px solid rgb(255 255 255 / 7%);
        border-radius: 12px;
        margin-top: 4px;
        background: var(--koel-card);
        box-shadow: var(--koel-shadow-raised);
    }

    /* The same open picker as every other select in Koel: spaced options, a
       hover dip, and the chosen one sunken with accent ink. */
    .threads-koel .user-table-page-size select option {
        margin-top: 10px;
        padding: 7px 12px;
        border-radius: 8px;
        background: transparent;
        color: var(--koel-muted);
        font-size: 13px;
        font-weight: 700;
    }

    .threads-koel .user-table-page-size select option:first-of-type { margin-top: 0; }

    .threads-koel .user-table-page-size select option:hover {
        background: rgb(0 0 0 / 30%);
        box-shadow: inset 0 1px 2px 0 rgb(0 0 0 / 35%);
        color: var(--koel-ink-strong);
    }

    .threads-koel .user-table-page-size select option:checked {
        background: #0d0d0d;
        box-shadow:
            inset 0 1px 1px 1px rgb(0 0 0 / 50%),
            0 1px 0 0 rgb(255 255 255 / 6%);
        color: var(--accent-readable);
    }

    .threads-koel .user-table-page-size select option::checkmark {
        color: var(--accent-readable);
    }
}

/* -- Totals ----------------------------------------------------------------
   The totals are the readout at the foot of the instrument: a dark band like
   the header, muted label, strong numerals; the grand total heavier still. */

.threads-koel .user-table-total-row > th,
.threads-koel .user-table-total-row > td {
    border-top: 1px solid var(--koel-line-strong);
    background: #1a1d23;
    color: var(--koel-ink-strong);
    font-weight: 700;
}

.threads-koel .user-table-total-row > th {
    color: var(--koel-muted);
    font-size: 11px;
    letter-spacing: 0.07em;
    text-transform: uppercase;
}

.threads-koel .user-table-grand-total-row > th,
.threads-koel .user-table-grand-total-row > td {
    background: #16181d;
    color: var(--accent-readable);
}

/* A group subtotal is a reading taken part way down the instrument, not the
   readout at its foot — so it is the one line on the table drawn in the theme
   colour rather than in the footer's dark band. A 15% accent tint over the
   row, and text in the readable accent deepened towards the true accent: both
   the Card label and its figure, so the line reads as one statement. The two
   footer rows keep their dark band and are still the heavier totals.
   MPF asked for the accent here, 2 September 2026. */
.threads-koel .user-table-subtotal-row > th,
.threads-koel .user-table-subtotal-row > td {
    border-top: 1px solid rgb(var(--accent-rgb) / 55%);
    background: rgb(var(--accent-rgb) / 15%);
    color: color-mix(in srgb, var(--accent-readable) 78%, var(--accent));
    font-weight: 600;
}

/* The label is a Card number, so it keeps the body's size and casing rather
   than the footer's spaced small capitals — "CARD 05525-38-42" letter-spaced
   reads as a heading, not a number. */
.threads-koel .user-table-subtotal-row > th {
    font-size: 13px;
    letter-spacing: normal;
    text-transform: none;
}

/* -- Fabrics: the mapping browser and status toggles ----------------------- */

.threads-koel .product-field-browser,
.threads-koel .fabric-mapping-create-form {
    border-color: var(--koel-line);
    background: var(--koel-card-quiet);
}

/* -- The lifecycle switch, restyled ---------------------------------------
   Adapted from Yaya12085's variation of elijahgummer's switch on uiverse.io
   (MIT), translated onto the production track/thumb markup: a near-black
   pill, a dark thumb carrying a small LED that glows red when the record is
   inactive and green when it is active — the same red/green language as the
   status pills. Geometry (track size, thumb travel) belongs to
   application.css and is left alone. */

.threads-koel .fabric-status-toggle__track {
    border-color: transparent;
    background: #0d0d0d;
    box-shadow:
        inset 0 1px 1px 1px rgb(0 0 0 / 50%),
        0 1px 0 0 rgb(255 255 255 / 8%);
}

.threads-koel .fabric-status-toggle__thumb {
    background: #1f2127;
    box-shadow:
        inset 0 1px 0 0 rgb(255 255 255 / 10%),
        0 1px 2px rgb(0 0 0 / 45%);
}

/* The LED. A white core whose glow carries the state colour. */
.threads-koel .fabric-status-toggle__thumb::after {
    position: absolute;
    top: 50%;
    left: 50%;
    width: 5px;
    height: 5px;
    border-radius: 50%;
    background: #ffffff;
    box-shadow:
        0 0 5px 2px rgb(var(--koel-error-rgb) / 90%),
        0 0 3px 1px rgb(var(--koel-error-rgb) / 90%);
    content: "";
    transform: translate(-50%, -50%);
    transition: box-shadow 300ms ease;
}

.threads-koel .fabric-status-toggle.is-active .fabric-status-toggle__track {
    border-color: transparent;
    background: #0d0d0d;
    box-shadow:
        inset 0 1px 1px 1px rgb(0 0 0 / 50%),
        0 1px 0 0 rgb(var(--koel-success-rgb) / 22%);
}

.threads-koel .fabric-status-toggle.is-active .fabric-status-toggle__thumb {
    background: #1f2127;
}

.threads-koel .fabric-status-toggle.is-active .fabric-status-toggle__thumb::after {
    box-shadow:
        0 0 5px 2px rgb(var(--koel-success-rgb) / 90%),
        0 0 3px 1px rgb(var(--koel-success-rgb) / 90%);
}

/* The LED grows with the thumb it sits in, and its glow with it. */
.threads-koel .form-field__toggle .fabric-status-toggle__thumb::after {
    width: 6px;
    height: 6px;
    box-shadow:
        0 0 6px 2px rgb(var(--koel-error-rgb) / 90%),
        0 0 4px 1px rgb(var(--koel-error-rgb) / 90%);
}

.threads-koel .form-field__toggle.is-active .fabric-status-toggle__thumb::after {
    box-shadow:
        0 0 6px 2px rgb(var(--koel-success-rgb) / 90%),
        0 0 4px 1px rgb(var(--koel-success-rgb) / 90%);
}

.threads-koel .fabric-status-toggle.is-active { color: var(--success); }

/* -- Status pills, in the toggle's language --------------------------------
   The pill is a miniature of the LED switch beside it: the same near-black
   surface, inset depth and top-edge highlight, and a white LED whose glow
   carries the state — green for Active, red for Inactive. The label stays
   neutral ink, the way a hardware label would; the LED does the talking.
   Status pills are the only status-tags that carry the `data-search-text`
   marker after render, so neutral tags (the rounding "Default" tag) keep
   their production look. */

.threads-koel .status-tag[data-search-text] {
    border-color: rgb(255 255 255 / 7%);
    background: #0d0d0d;
    color: #cfd4db;
    box-shadow:
        inset 0 1px 1px 1px rgb(0 0 0 / 50%),
        0 1px 0 0 rgb(255 255 255 / 7%);
}

.threads-koel .status-tag[data-search-text]::before {
    display: inline-block;
    width: 5px;
    height: 5px;
    border-radius: 50%;
    margin-right: 7px;
    background: #ffffff;
    box-shadow:
        0 0 5px 2px rgb(var(--koel-success-rgb) / 90%),
        0 0 3px 1px rgb(var(--koel-success-rgb) / 90%);
    content: "";
}

.threads-koel .status-tag--warning[data-search-text] {
    border-color: rgb(255 255 255 / 7%);
    background: #0d0d0d;
    color: #cfd4db;
}

.threads-koel .status-tag--warning[data-search-text]::before {
    box-shadow:
        0 0 5px 2px rgb(var(--koel-error-rgb) / 90%),
        0 0 3px 1px rgb(var(--koel-error-rgb) / 90%);
}


/* ==========================================================================
   Ported from the Koel gallery (koel-proposals.css), 28 Aug 2026 — approved
   blocks move verbatim; the gallery remains the fine-tuning bench. Where a
   block refines an earlier rule, being later in the file is the point.
   ========================================================================== */

/* ==========================================================================
   1 · Dialogs — the Koel modal
   ========================================================================== */

/* The surface below is the one true dialog box; the dialog element itself
   stays chrome-free so the corners never double up (MPF's screenshot,
   28 Aug 2026). */
.threads-koel .user-dialog {
    padding: 0;
    border: 0;
    border-radius: var(--koel-radius);
    background: transparent;
    box-shadow: none;
    color: var(--ink);
}

.threads-koel .user-dialog::backdrop {
    background: rgb(0 0 0 / 55%);
}

.threads-koel .user-dialog-header {
    border-bottom: 1px solid var(--koel-line);
    border-radius: var(--koel-radius) var(--koel-radius) 0 0;
    background: var(--koel-card-quiet);
}

.threads-koel .user-dialog-heading h2 {
    color: var(--koel-ink-strong);
    font-size: 17px;
    font-weight: 700;
    letter-spacing: -0.01em;
}

.threads-koel .user-dialog-heading p {
    color: var(--koel-muted);
    font-size: 13px;
}

/* Close is a key. */
.threads-koel .user-dialog-close {
    border: 1px solid var(--koel-line-strong);
    border-radius: 8px;
    background: #16181d;
    box-shadow:
        inset 0 1px 0 0 rgb(255 255 255 / 7%),
        0 1px 2px rgb(0 0 0 / 35%);
    color: var(--koel-muted);
}

.threads-koel .user-dialog-close:hover {
    background: #1f2127;
    color: var(--koel-ink-strong);
}

.threads-koel .user-dialog-help {
    color: var(--koel-muted-soft);
}

.threads-koel .user-dialog-actions {
    border-top: 1px solid var(--koel-line);
    border-radius: 0 0 var(--koel-radius) var(--koel-radius);
    background: var(--koel-card-quiet);
}

/* -- The command bar ------------------------------------------------------
   Cmd+K's dialog (docs/fuzzy-scope-and-command-bar-spec.md §2 item 11). It is
   the dialog skeleton holding the dialog dropdown's search box and rows, so
   nearly everything here is geometry: the skeleton's own 390px is a form's
   width, and a row that reads "Without Reserve   Shirts › Reports › Card
   Working" needs more. `calc(100vw - 32px)` is the phone rule every dialog in
   threads-production-ui.css carries, so at 375px it goes full-width the way
   the rest do, with no breakpoint of its own to drift from them. 680px since
   29 September 2026 (MPF: "make it slightly wider"): a scope's pills and a
   long path both wanted the room at 560. */
.threads-koel .user-dialog--command-bar {
    width: min(680px, calc(100vw - 32px));
    max-width: min(680px, calc(100vw - 32px));
}

.threads-koel .user-dialog--command-bar .user-dialog-form {
    gap: 8px;
}

/* In a dropdown the search box is sticky at the top of the scrolling panel.
   Here the list is the scroller and the box sits above it, so it stays put on
   its own; sticky would only pin it to a grid cell it never leaves. */
.threads-koel .user-dialog--command-bar .threads-select__search {
    position: static;
    min-height: 40px;
    margin-bottom: 0;
    font-size: 14px;
}

/* The list scrolls inside a fixed height rather than growing the dialog: eight
   rows appear and vanish on every keystroke, and a dialog that changed height
   with them would move the footer under the pointer. Eight rows of 36px and
   the 3px the list keeps between rows, so the eighth row is fully in view. */
.threads-koel .user-dialog--command-bar .threads-select__options {
    max-height: calc(8 * 36px + 7 * 3px);
    overflow-x: hidden;
    overflow-y: auto;
    overscroll-behavior: contain;
}

/* Label then path on one line. The dropdown's row spreads its two children to
   the ends because the second is a tick; here the second is where the screen
   lives, and it reads as one phrase only when it follows the label directly. */
.threads-koel .user-dialog--command-bar .threads-select__option {
    justify-content: flex-start;
    gap: 10px;
    /* The list is a grid, and a grid item's min-width is its content — so at
       375px "Production Type Mappings" and its path made the row 384px in a
       305px list, and the list grew a sideways scrollbar instead of the path
       giving way. Told to fit the track; the path's own ellipsis then does
       the giving. Stage 5, 11 September 2026. */
    min-width: 0;
    width: 100%;
}

.threads-koel .user-dialog--command-bar .threads-command-bar__label {
    flex: 0 0 auto;
    white-space: nowrap;
}

/* The family in muted text after the label (spec item 12): "Card Working"
   three times over is Shirts', Jeans' and Trousers', and the path is how the
   reader tells them apart. It gives way first when the row is short of room —
   the label is the answer, the path is only where it lives — and ellipsises
   rather than wrapping so the eight rows stay eight rows tall. The highlighted
   row needs nothing here: it is the option's own aria-selected look. */
.threads-koel .user-dialog--command-bar .threads-command-bar__path {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    color: var(--koel-muted);
    font-size: 13px;
    font-weight: 500;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Level two — the chip, the status line and the Tab hint (spec §2 items 18–19,
   stage 10, 11 September 2026).

   The chip is a row of its own above the box rather than something inside it:
   the box is the dialog dropdown's search input, and a pill drawn inside an
   <input> is the one thing CSS cannot do. A row above reads the same — the
   chosen thing, then what is being typed about it — and costs the input
   nothing. It wraps because "Card Working · Without Reserve With Size" is a
   long pill in a 375px dialog, and a pill that overflowed would take the
   dialog's own width with it. */
.threads-koel .user-dialog--command-bar .threads-command-bar__scope {
    display: flex;
    flex-wrap: wrap;
    /* The Loom's filter track's own 8px, since its chips moved in (29 Sep). */
    gap: 8px;
    align-items: center;
}

/* A class that declares a display beats the browser's own [hidden] rule —
   the trap .filter-pill fell into on 1 September 2026 — so the empty pill
   was drawn above the box before any chip landed. Stage 10 review. */
.threads-koel .user-dialog--command-bar .threads-command-bar__scope[hidden] { display: none; }

/* The status line is the filter board's .filter-scope__status, colours and all
   — ready, issue and forgiven are already tinted by data-scope there, and
   re-stating them here would be a second set of colours to keep in step. Only
   the geometry is the bar's: the board's rule caps it at 46% because it sits in
   a filter lane beside two buttons, and here it has the dialog's width to
   itself, so the cap is lifted and a complaint may wrap rather than ellipsise
   out of existence. */
.threads-koel .user-dialog--command-bar .filter-scope__status {
    max-width: 100%;
    margin: 0 2px;
    white-space: normal;
}

/* "Tab" at the end of a row that can be tabbed into. Muted and small because
   it is a hint and not the answer — the label is the answer — and pushed to
   the far end so the eight rows' hints line up as a column the eye can skim
   rather than a mark that moves with every screen name's length. */
.threads-koel .user-dialog--command-bar .threads-command-bar__hint {
    flex: 0 0 auto;
    margin-left: auto;
    padding-left: 8px;
    color: var(--koel-muted);
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.04em;
}

/* Level two holds ONE size (MPF, 8 October 2026: the bar "grows and shrinks"
   as he types a scope; docs/command-bar-steady-size-plan.md §2 items 1–2).
   The list's max-height above caps it but never holds it open, so from Tab
   until the chip comes off (`is-scoped`, set by the script) the body is a
   fixed height instead: one line of pills, the box, one status line and the
   list's own eight rows, with the 8px rhythm between them. Inside that
   height the list takes whatever the rest leaves — the status line coming
   and going, the pills wrapping onto more lines — and scrolls inside it, so
   nothing that changes while typing can reach the dialog's edge. On a
   screen too short for it the surface stops at the viewport, the body gives
   way and the list scrolls; the height is still the same in every state.
   No transition: there is no height change left to animate (plan §3). */
.threads-koel .user-dialog--command-bar.is-scoped {
    --command-bar-pill-line: 34px;
    --command-bar-status-line: 20px;
    --command-bar-eight-rows: calc(8 * 36px + 7 * 3px);
}

.threads-koel .user-dialog--command-bar.is-scoped .user-dialog-surface {
    display: flex;
    flex-direction: column;
    max-height: calc(100vh - 32px);
    max-height: calc(100dvh - 32px);
}

.threads-koel .user-dialog--command-bar.is-scoped .user-dialog-surface > * {
    flex: 0 0 auto;
}

.threads-koel .user-dialog--command-bar.is-scoped .user-dialog-surface > .user-dialog-form {
    display: flex;
    flex-direction: column;
    flex: 0 1 auto;
    box-sizing: content-box;
    height: calc(var(--command-bar-pill-line) + 40px + var(--command-bar-status-line)
        + var(--command-bar-eight-rows) + 3 * 8px);
    min-height: 0;
    overflow: hidden;
}

.threads-koel .user-dialog--command-bar.is-scoped .user-dialog-form > * {
    flex: 0 0 auto;
}

/* Seventeen Shop pills from a Territory wrap; past three lines they scroll
   in their own row rather than squeezing the list out of sight. */
.threads-koel .user-dialog--command-bar.is-scoped .threads-command-bar__scope {
    max-height: calc(3 * var(--command-bar-pill-line) + 2 * 8px);
    overflow-y: auto;
    overscroll-behavior: contain;
}

.threads-koel .user-dialog--command-bar.is-scoped .threads-select__options {
    flex: 1 1 0;
    order: 2;
    min-height: 0;
    max-height: none;
    /* The list is a grid, and a grid taller than its rows stretches them:
       four recents stood 85px apart. Rows keep their own height, from the top. */
    align-content: start;
}

/* The one-line hint ("Scopes you run on this page show here") under the
   box, not at the foot of the empty list. */
.threads-koel .user-dialog--command-bar.is-scoped .threads-select__no-results {
    order: 1;
}

/* A recent's label is its run's pills, and a Territory's seventeen Shops
   are a long line: the label gives way at the edge (plan §7's default)
   while its muted kinds ("17 Shops") keep their room after it. */
.threads-koel .user-dialog--command-bar .threads-select__option[data-threads-command-bar-recent] .threads-command-bar__label {
    flex: 0 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
}

.threads-koel .user-dialog--command-bar .threads-select__option[data-threads-command-bar-recent] .threads-command-bar__path {
    flex: 0 0 auto;
}

/* ==========================================================================
   2 · Form fields — labels over wells
   ========================================================================== */

.threads-koel .form-field > span {
    color: var(--koel-muted);
    font-size: 11.5px;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
}

.threads-koel .form-field input[type="text"],
.threads-koel .form-field input[type="number"],
.threads-koel .form-field input[type="password"],
.threads-koel .form-field input[type="search"],
.threads-koel .form-field input[type="date"],
.threads-koel .form-field textarea,
.threads-koel .form-field select {
    border: 1px solid var(--koel-line);
    border-radius: var(--koel-radius-sm);
    background: #0d0d0d;
    box-shadow:
        inset 0 1px 1px 1px rgb(0 0 0 / 50%),
        0 1px 0 0 rgb(255 255 255 / 6%);
    color: var(--ink);
    font-size: 14px;
}

.threads-koel .form-field input:focus-visible,
.threads-koel .form-field textarea:focus-visible,
.threads-koel .form-field select:focus-visible {
    border-color: rgb(var(--accent-readable-rgb) / 55%);
    outline: none;
}

.threads-koel .form-field input::placeholder {
    color: var(--koel-muted-soft);
}

/* Dropdowns keep the well but wear the chevron. */
.threads-koel .form-field select {
    appearance: none;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%239aa3b1' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
    background-position: right 12px center;
    background-repeat: no-repeat;
    background-size: 14px;
    padding-right: 34px;
}


/* ==========================================================================
   3 · Status messages — a quiet band with an LED
   ========================================================================== */

.threads-koel .status-message {
    display: flex;
    align-items: baseline;
    gap: 9px;
    border: 1px solid var(--koel-line);
    border-radius: var(--koel-radius-sm);
    background: var(--koel-card-quiet);
    color: var(--koel-muted);
    font-size: 13.5px;
}

.threads-koel .status-message::before {
    content: "";
    display: inline-block;
    flex: none;
    width: 5px;
    height: 5px;
    border-radius: 50%;
    background: #fff;
    box-shadow:
        0 0 5px 2px rgb(var(--accent-readable-rgb) / 60%),
        0 0 3px 1px rgb(var(--accent-readable-rgb) / 80%);
    vertical-align: 2px;
}

.threads-koel .status-message--danger::before {
    box-shadow:
        0 0 5px 2px rgb(var(--koel-error-rgb) / 60%),
        0 0 3px 1px rgb(var(--koel-error-rgb) / 80%);
}

.threads-koel .status-message--warning::before {
    box-shadow:
        0 0 5px 2px rgb(230 180 90 / 60%),
        0 0 3px 1px rgb(230 180 90 / 80%);
}


/* ==========================================================================
   4 · Toasts — dark keys that float in
   ========================================================================== */

/*
 * A toaster has to say which of the two things happened before it is read.
 *
 * This skin used to set `color: var(--ink)` on the toaster and
 * `var(--accent-readable)` on its icon, which flattened both — the base sheet
 * carries the state in `currentColor`, and the stripe and the icon tint are
 * the only things drawn in it. So a save and a refusal arrived as the same
 * dark key with the same green icon, differing by one glyph: ✓ against !.
 * MPF asked for them told apart, 1 Sep 2026.
 *
 * The state colour is restored and then made to count three times over: the
 * stripe down the left, the icon filled solid rather than tinted, and a wash
 * of the state across the surface itself. The panel stays a Koel dark key —
 * the wash is 8%, enough to read across the room, not a coloured box.
 */
.threads-koel .toast {
    border: 1px solid var(--koel-line-strong);

    /* The stripe last, or the shorthand above paints it the line colour. */
    border-left: 4px solid var(--toast-state);
    border-radius: 12px;
    background:
        linear-gradient(color-mix(in srgb, var(--toast-state) 8%, transparent),
                        color-mix(in srgb, var(--toast-state) 8%, transparent)),
        #16181d;
    box-shadow: var(--koel-shadow-raised);
}

/* Solid, not tinted: at 26px the filled disc is what the eye lands on first,
   and it carries the glyph in the panel's own dark rather than in the state
   colour, so the check and the exclamation stay legible at every state. */
.threads-koel .toast__icon {
    background: var(--toast-state);
    color: #16181d;
}

/* The title stays the readable ink; only the accents carry the state. */
.threads-koel .toast__content strong {
    color: var(--ink);
}

.threads-koel .toast__content span {
    color: var(--muted);
}


/* ==========================================================================
   6 · Report furniture — cards, scope pills, report tables
   ========================================================================== */

.threads-koel .dispatch-report-card {
    border: 1px solid var(--koel-line);
    border-radius: var(--koel-radius);
    background: var(--koel-card);
    box-shadow: var(--koel-shadow-card);
}

/* The well is a recessed surface, like the other quiet bands in the skin, so
   the outlined candidates inside it read as sitting below the field rather
   than floating beside it. */
.threads-koel .token-input__well {
    border: 1px dashed var(--koel-line);
    background: var(--koel-card-quiet);
}

.threads-koel .token-input__option {
    border: 1px solid var(--koel-line-strong);
    color: var(--koel-muted);
}

.threads-koel .token-input__option:hover {
    border-color: var(--accent);
    color: var(--ink);
}

.threads-koel .token-input__well-empty { color: var(--koel-muted); }

/* The scope box's third state. A forgiven spelling — "maksa → Maska Satin" —
   keeps ready's accent ink, because Return will do exactly what the line
   says, and gains a soft accent wash behind the text so the eye lands on it
   before the reader has parsed a word: the spec's "the arrow is the tell",
   and the tint is what makes the tell visible across the room. Not the
   warning colour — nothing is wrong, something was noticed. The wash is
   mixed from --accent-readable-rgb like the row-select tints, so it follows
   the six-palette accent. Wraps like an issue line: the arrowed sentence is
   longer than a plain ready one and an ellipsis would eat the label.
   docs/fuzzy-scope-and-command-bar-spec.md §2 item 7, 11 September 2026. */
.threads-koel .filter-scope__status[data-scope="forgiven"] {
    border-radius: 6px;
    padding: 2px 8px;
    background: rgb(var(--accent-readable-rgb) / 12%);
    color: var(--accent-readable);
    white-space: normal;
}

/* The candidates the status line named, as rows under the box.
   docs/fuzzy-scope-and-command-bar-spec.md §2 item 6a, 11 September 2026.

   `.filter-scope` is a flex ROW that does not wrap (application.css:4709), so
   the list would otherwise be squeezed in beside the field and the two
   buttons. The lane is told to wrap and the list takes a whole line of it, so
   the field keeps its row and the rows sit directly beneath — which is where
   the reader is already looking. Five rows at most, the spec's limit, and the
   list scrolls rather than growing the board if a shorter row count is ever
   raised. */
/* A small panel floating under the box, not a row that grows the lane.
   MPF, 11 September 2026, on seeing the first version: "instead of expanding
   the whole box with suggestions, give me like a small tooltip that I can
   arrow through with my keyboard if I choose to". So the lane keeps its one
   row and its height, and the rows hang beneath the field in the dialog
   dropdown's own panel — same border, ground and shadow as
   .threads-select__panel — the width of the field, over whatever sits below,
   gone the moment there is nothing to name. Five rows at most; the panel
   scrolls rather than growing past them. */
.threads-koel .filter-scope { position: relative; }

.threads-koel .filter-scope__choices {
    position: absolute;
    top: calc(100% + 4px);
    left: 4px;
    z-index: 30;
    width: min(360px, calc(100% - 8px));
    max-height: calc(5 * 36px + 4 * 3px + 16px);
    padding: 8px;
    overflow-x: hidden;
    overflow-y: auto;
    overscroll-behavior: contain;
    border: 1px solid var(--koel-line-strong);
    border-radius: 12px;
    background: var(--koel-card);
    box-shadow: var(--koel-shadow-raised);
}

/* The Balance rail scrolls (overflow-y: auto), so a panel hanging past its
   bottom edge would be cut off there; inside the rail the rows stay in the
   flow, under the field, where the narrow column wraps them anyway. Stage 10
   review, 11 September 2026. */
.threads-koel .management-card--rail .filter-scope { flex-wrap: wrap; }
.threads-koel .management-card--rail .filter-scope__choices {
    position: static;
    flex: 1 1 100%;
    width: auto;
}

/* `.threads-select__options` sets `display: grid`, and an author rule beats
   the browser's own `[hidden] { display: none }` — so the list has to be told
   to honour its own hidden attribute, which is the only thing holding it
   closed. */
.threads-koel .filter-scope__choices[hidden] { display: none; }

/* The command bar's row, reused rather than redrawn: label first, the Master's
   noun after it, and told to fit the track because the list is a grid and a
   grid item's min-width is its content — at 375px a long Item name would push
   the row wider than the lane and grow a sideways scrollbar. */
.threads-koel .filter-scope__choices .threads-select__option {
    justify-content: flex-start;
    gap: 10px;
    min-width: 0;
    width: 100%;
}

.threads-koel .filter-scope__choice-label {
    flex: 0 0 auto;
    white-space: nowrap;
}

/* The noun gives way first when the row is short of room — the label is the
   answer, the noun is only which Master it came from — and ellipsises rather
   than wrapping so five rows stay five rows tall. The highlighted row needs
   nothing here: it is the option's own aria-selected look. */
.threads-koel .filter-scope__choice-well {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    color: var(--koel-muted);
    font-size: 13px;
    font-weight: 500;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* The token pill takes the same sunken-key treatment the scope pills get, so
   a pill reads identically wherever Koel shows one. */
.threads-koel .token-input__pill {
    border: 1px solid var(--koel-line-strong);
    background: #16181d;
    box-shadow:
        inset 0 1px 0 0 rgb(255 255 255 / 7%),
        0 1px 2px rgb(0 0 0 / 35%);
    color: var(--ink);
}

.threads-koel .token-input__empty { color: var(--koel-muted); }

.threads-koel .dispatch-quick-scope__pills .status-tag {
    border: 1px solid var(--koel-line-strong);
    border-radius: 999px;
    background: #16181d;
    box-shadow:
        inset 0 1px 0 0 rgb(255 255 255 / 7%),
        0 1px 2px rgb(0 0 0 / 35%);
    color: var(--koel-muted);
}


/* ==========================================================================
   7 · Masthead — the Koel pass over the Egret rail
   The rail stays; what changes is grammar. The current menu entry is chosen,
   so it sits sunken with accent ink (today it only tints). Submenu current
   entries likewise. The account status dot becomes a true LED.
   ========================================================================== */

.threads-koel .navigation-link.is-current,
.threads-koel .navigation-link.has-current-child {
    border-radius: 8px;
    background: #0d0d0d;
    box-shadow:
        inset 0 1px 1px 1px rgb(0 0 0 / 50%),
        0 1px 0 0 rgb(255 255 255 / 6%);
    color: var(--accent-readable);
}

.threads-koel .navigation-submenu__link.is-current {
    background: #0d0d0d;
    box-shadow:
        inset 0 1px 1px 1px rgb(0 0 0 / 50%),
        0 1px 0 0 rgb(255 255 255 / 6%);
    color: var(--accent-readable);
}

.threads-koel .navigation-status-light {
    width: 5px;
    height: 5px;
    border-radius: 50%;
    background: #fff;
    box-shadow:
        0 0 5px 2px rgb(var(--koel-success-rgb) / 60%),
        0 0 3px 1px rgb(var(--koel-success-rgb) / 80%);
}


/* ==========================================================================
   8 · Footer — a quiet end of the room, on rail material
   ========================================================================== */

.threads-koel .application-footer {
    border-top: 1px solid var(--koel-rail-line);
    background: var(--koel-rail);
    color: var(--koel-rail-muted);
}

.threads-koel .application-footer__wordmark {
    color: var(--koel-ink-strong);
    font-weight: 700;
    letter-spacing: -0.01em;
}

/* The product tag is a small key. */
.threads-koel .application-footer__product {
    padding: 3px 9px;
    border: 1px solid var(--koel-rail-line);
    border-radius: 999px;
    background: #16181d;
    box-shadow: inset 0 1px 0 0 rgb(255 255 255 / 6%);
    color: var(--koel-rail-muted);
    font-size: 11px;
    font-weight: 700;
}

.threads-koel .application-footer__legal {
    color: var(--koel-muted-soft);
    font-size: 12px;
}

.threads-koel .application-footer__owner {
    color: var(--koel-rail-muted);
    font-weight: 700;
}


/* ==========================================================================
   9 · The open select — the native picker dressed as a Koel panel
   Chrome's customizable select (appearance: base-select) lets the dropdown
   list itself take the skin; other browsers keep the native picker.
   ========================================================================== */

@supports (appearance: base-select) {
    .threads-koel .form-field select {
        appearance: base-select;
        /* base-select draws its own indicator; the painted chevron would double it. */
        background-image: none;
    }

    .threads-koel .form-field select::picker(select) {
        appearance: base-select;
        margin-top: 6px;
        padding: 6px;
    }

    .threads-koel .form-field select option:first-of-type {
        margin-top: 0;
        border: 1px solid var(--koel-line-strong);
        border-radius: 12px;
        background: var(--koel-card);
        box-shadow: var(--koel-shadow-raised);
    }

    .threads-koel .form-field select option {
        margin-top: 10px;
        padding: 8px 11px;
        border-radius: 8px;
        background: transparent;
        color: var(--koel-muted);
        font-size: 14px;
        font-weight: 700;
    }

    .threads-koel .form-field select option:hover {
        background: rgb(0 0 0 / 30%);
        color: var(--koel-ink-strong);
    }

    .threads-koel .form-field select option:focus-visible {
        outline: none;
        background: rgb(0 0 0 / 30%);
        box-shadow: inset 0 0 0 1px rgb(var(--accent-readable-rgb) / 45%);
    }

    /* Chosen sits sunken, as everywhere in Koel. */
    .threads-koel .form-field select option:checked {
        background: #0d0d0d;
        box-shadow:
            inset 0 1px 1px 1px rgb(0 0 0 / 50%),
            0 1px 0 0 rgb(255 255 255 / 6%);
        color: var(--accent-readable);
    }

    .threads-koel .form-field select option::checkmark {
        color: var(--accent-readable);
    }
}



/* ==========================================================================
   Date picker — the calendar is a deck of day keys
   The gallery's date-control design mapped onto the production threads-date
   contract (the panel surface and trigger well are already styled above).
   Days are quiet keys: hover dips, the chosen day is sunken with accent ink,
   today carries an LED under its number, outside days fall back to whisper.
   ========================================================================== */

.threads-koel .threads-date__header strong {
    color: var(--koel-ink-strong);
    font-size: 13.5px;
    font-weight: 700;
}

.threads-koel .threads-date__step,
.threads-koel .threads-date__footer button {
    border: 1px solid rgb(255 255 255 / 7%);
    border-radius: 7px;
    background: #16181d;
    box-shadow:
        inset 0 1px 0 0 rgb(255 255 255 / 7%),
        0 1px 2px rgb(0 0 0 / 35%);
    color: var(--koel-muted);
}

.threads-koel .threads-date__step:hover,
.threads-koel .threads-date__step:focus-visible,
.threads-koel .threads-date__footer button:hover,
.threads-koel .threads-date__footer button:focus-visible {
    border-color: rgb(255 255 255 / 12%);
    background: #1f2127;
    color: var(--koel-ink-strong);
    outline: none;
}

.threads-koel .threads-date__weekdays span {
    color: var(--koel-muted-soft);
    font-size: 9.5px;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

.threads-koel .threads-date__grid button {
    position: relative;
    border: 0;
    border-radius: 7px;
    background: transparent;
    color: var(--ink);
    font-size: 12.5px;
    font-weight: 700;
}

.threads-koel .threads-date__grid button:hover {
    background: rgb(0 0 0 / 30%);
    box-shadow: inset 0 1px 2px 0 rgb(0 0 0 / 35%);
    color: var(--ink);
}

.threads-koel .threads-date__grid button:focus-visible {
    outline: none;
    box-shadow: inset 0 0 0 1px rgb(var(--accent-readable-rgb) / 45%);
}

.threads-koel .threads-date__grid button[data-outside="true"] {
    color: var(--koel-muted-soft);
}

/* Today: an LED under the number — state, as always. */
.threads-koel .threads-date__grid button[data-today="true"] {
    background: transparent;
    box-shadow: none;
    color: var(--ink);
}

.threads-koel .threads-date__grid button[data-today="true"]::after {
    content: "";
    position: absolute;
    left: 50%;
    bottom: 2px;
    width: 4px;
    height: 4px;
    border-radius: 50%;
    background: #fff;
    box-shadow:
        0 0 4px 1.5px rgb(var(--accent-readable-rgb) / 60%),
        0 0 2px 1px rgb(var(--accent-readable-rgb) / 80%);
    transform: translateX(-50%);
}

/* The chosen day is sunken — a pressed key stays down. */
.threads-koel .threads-date__grid button[aria-selected="true"] {
    background: #0d0d0d;
    box-shadow:
        inset 0 1px 1px 1px rgb(0 0 0 / 50%),
        0 1px 0 0 rgb(255 255 255 / 6%);
    color: var(--accent-readable);
}


/* The card-status toggle's bare-footing rule went on 29 September 2026 with
   the class itself: those rows now wear the house `.fabric-status-toggle`,
   which never had a box (Frost sweep, stray #1). */


/* The Requirement edit dialog: production gives each row its own grid, so
   nothing lines up between rows. One shared six-track grid instead — the
   rows dissolve into it (display: contents keeps the markup contract) and
   every field takes aligned tracks: three pairs on top, the Fabric pair
   with four singles beneath, Status under the Date pair. MPF asked for the
   alignment, 28 Aug 2026. */
.threads-koel .requirement-edit-form {
    display: grid;
    grid-template-columns: repeat(6, minmax(0, 1fr));
    gap: 16px 14px;
}

.threads-koel .requirement-edit-form .requirement-edit-row {
    display: contents;
}

.threads-koel .requirement-edit-row--context > .form-field,
.threads-koel .requirement-edit-row--status > .form-field {
    grid-column: span 2;
}

.threads-koel .requirement-edit-row--details > .form-field {
    grid-column: span 1;
}

.threads-koel .requirement-edit-row--details > .form-field:first-child {
    grid-column: span 2;
}

@media (max-width: 920px) {
    .threads-koel .requirement-edit-form {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }

    .threads-koel .requirement-edit-row--context > .form-field,
    .threads-koel .requirement-edit-row--status > .form-field,
    .threads-koel .requirement-edit-row--details > .form-field {
        grid-column: span 1;
    }

    .threads-koel .requirement-edit-row--details > .form-field:first-child {
        grid-column: 1 / -1;
    }
}


/* Arrow-key focus in the option panels wore production's filled accent pill
   with a 2px ring — loud, and nothing like the filters (MPF's screenshot,
   28 Aug 2026). In Koel the keyboard-active option is the same shallow dip
   as hover, marked by one quiet accent inset line; when it lands on the
   already-chosen option, the sunken well simply gains the same line. */
.threads-koel .threads-select__option.is-keyboard-active,
.threads-koel .user-table-filter__option.is-keyboard-active {
    background: rgb(0 0 0 / 30%);
    box-shadow:
        inset 0 1px 2px 0 rgb(0 0 0 / 35%),
        inset 0 0 0 1px rgb(var(--accent-readable-rgb) / 45%);
    color: var(--koel-ink-strong);
}

.threads-koel .threads-select__option[aria-selected="true"].is-keyboard-active,
.threads-koel .user-table-filter__option.is-keyboard-active:has(input:checked) {
    background: #0d0d0d;
    box-shadow:
        inset 0 1px 1px 1px rgb(0 0 0 / 50%),
        0 1px 0 0 rgb(255 255 255 / 6%),
        inset 0 0 0 1px rgb(var(--accent-readable-rgb) / 45%);
    color: var(--accent-readable);
}


/* The "Choose …" placeholder stands slightly apart from the real options —
   it clears the field rather than choosing anything (MPF asked for the
   breath, 28 Aug 2026). Only the empty-value option is a placeholder. */
.threads-koel .threads-select__option[data-value=""] {
    /* 6px when the options below it were flush. The list now carries 3px of its
       own, so 3 here keeps the placeholder's breath at the 6 it was drawn as. */
    margin-bottom: 3px;
}


/* ==========================================================================
   Tab pills — one segmented key strip
   MPF's shape call, 28 Aug 2026: the keys sit flush, sharing a single
   hairline, and only the outer corners of the strip are round. Each key
   carries the same lit face as the buttons (a top-lit gradient rather than
   flat paint, which is what read as "grey"), and the chosen one presses
   into an accent-washed socket — so the accent appears once per row, on the
   thing it describes, instead of five times.

   Production's travelling indicator is removed. It slides an accent pill
   between tabs and flares a halo mid-journey (tab-indicator-travel peaks at
   0 0 18px accent/42%): that was the glow on page load, and it borrows the
   one thing Koel reserves for state. A key does not slide, it goes down.
   ========================================================================== */

.threads-koel .user-table-lifecycle-tabs {
    gap: 0;
}

.threads-koel .user-table-lifecycle-tab {
    border: 1px solid rgb(255 255 255 / 8%);
    /* The left edge is the neighbour's right edge: one hairline, not two. */
    border-left-width: 0;
    border-radius: 0;
    background: linear-gradient(180deg, #1d2027, #16181d);
    box-shadow:
        inset 0 1px 0 0 rgb(255 255 255 / 8%),
        0 1px 2px rgb(0 0 0 / 40%);
}

.threads-koel .user-table-lifecycle-tab:first-of-type {
    border-left-width: 1px;
    border-radius: 8px 0 0 8px;
}

.threads-koel .user-table-lifecycle-tab:last-of-type {
    border-radius: 0 8px 8px 0;
}

/* A strip of one is still a key, not a slice of one. */
.threads-koel .user-table-lifecycle-tab:first-of-type:last-of-type {
    border-radius: 8px;
}

.threads-koel .user-table-lifecycle-tab:hover {
    background: linear-gradient(180deg, #24272f, #1c1f25);
    color: var(--koel-ink-strong);
}

/* Focus is a quiet line inside the key, never a ring around it — a ring is
   how the chosen tab used to look, and the two must not be confusable. */
.threads-koel .user-table-lifecycle-tab:focus-visible {
    outline: none;
    box-shadow:
        inset 0 0 0 1px rgb(var(--accent-readable-rgb) / 45%),
        inset 0 1px 0 0 rgb(255 255 255 / 8%),
        0 1px 2px rgb(0 0 0 / 40%);
}

/* The chosen key is pressed in, and the socket carries the accent. */
.threads-koel .user-table-lifecycle-tab.is-current,
.threads-koel .user-table-lifecycle-tab.is-current:hover,
.threads-koel .user-table-lifecycle-tab.is-current:focus-visible,
.threads-koel .user-table-lifecycle-tabs.has-travelling-indicator .user-table-lifecycle-tab.is-current {
    background:
        linear-gradient(180deg,
            rgb(var(--accent-readable-rgb) / 10%),
            rgb(var(--accent-readable-rgb) / 4%)),
        #0d0d0d;
    box-shadow:
        inset 0 1px 1px 1px rgb(0 0 0 / 55%),
        inset 0 -1px 0 0 rgb(255 255 255 / 5%);
    color: var(--accent-readable);
}

.threads-koel .user-table-lifecycle-indicator {
    display: none;
}

/* ==========================================================================
   Quick Scope — the composer becomes the well it always meant to be
   Three things set this block apart from every other control (MPF, 28 Aug
   2026): production tints the whole fieldset with a 5% accent wash while its
   sibling sections stay clear; the composer was an outlined box holding a
   second, fully dressed well; and its label kept sentence case while every
   other field label is a Koel micro-label. One well, one label, no wash.
   ========================================================================== */

.threads-koel .dispatch-quick-scope {
    background: transparent;
}

.threads-koel .dispatch-quick-scope__composer {
    border: 1px solid rgb(255 255 255 / 7%);
    border-radius: var(--koel-radius-sm);
    background: #0d0d0d;
    box-shadow:
        inset 0 1px 1px 1px rgb(0 0 0 / 50%),
        0 1px 0 0 rgb(255 255 255 / 6%);
}

.threads-koel .dispatch-quick-scope__composer:focus-within {
    border-color: rgb(var(--accent-readable-rgb) / 55%);
    box-shadow:
        inset 0 1px 1px 1px rgb(0 0 0 / 50%),
        0 1px 0 0 rgb(255 255 255 / 6%);
}

/* The composer is the well; the field inside it is bare. The composer sits
   inside a .form-field, so this needs the type to outrank the shared field
   rule rather than lose to it. */
.threads-koel .dispatch-quick-scope__composer input[type="text"],
.threads-koel .dispatch-quick-scope__composer input[type="search"] {
    border: 0;
    border-radius: 0;
    background: transparent;
    box-shadow: none;
    font-size: 14px;
}

.threads-koel .dispatch-quick-scope__field-label {
    color: var(--koel-muted);
    font-size: 11.5px;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
}

.threads-koel .dispatch-quick-scope__help,
.threads-koel .dispatch-quick-scope__status {
    color: var(--koel-muted-soft);
}

.threads-koel .dispatch-quick-scope__status:not(:empty) {
    color: var(--accent-readable);
}

/* A staged scope term is a chosen thing, so it sits sunken with accent ink —
   the same chip the column filters use for a selected value. */
.threads-koel .dispatch-quick-scope__pill {
    border: 1px solid rgb(255 255 255 / 7%);
    background: #0d0d0d;
    box-shadow:
        inset 0 1px 1px 1px rgb(0 0 0 / 50%),
        0 1px 0 0 rgb(255 255 255 / 6%);
    color: var(--accent-readable);
}

.threads-koel .dispatch-quick-scope__pill--all {
    border-color: rgb(255 255 255 / 7%);
    background: #16181d;
    box-shadow:
        inset 0 1px 0 0 rgb(255 255 255 / 7%),
        0 1px 2px rgb(0 0 0 / 35%);
    color: var(--koel-muted);
}

.threads-koel .dispatch-quick-scope__pill-label {
    color: var(--koel-muted-soft);
}

.threads-koel .dispatch-quick-scope__pill-remove:hover,
.threads-koel .dispatch-quick-scope__pill-remove:focus-visible {
    background: rgb(var(--accent-readable-rgb) / 22%);
    outline: none;
}


/* ==========================================================================
   Round four of MPF's tab review, 28 Aug 2026
   1. The pressed key gets a surface. You cannot see a shadow on black: the
      chosen key was near-black with a dark inset on it, so the depression
      had nothing to fall on and read flat. It now carries the primary
      button's accent face run in reverse — darker at the top, lighter at
      the bottom, the way light lands in a depression — with a real rim
      above and a lit lip below. The fill is the state now, so the LED
      stands down; two signals saying one thing is how LEDs lose meaning.
   2. The tray stops shouting: less padding, no outer lip, a softer floor.
   ========================================================================== */

.threads-koel .user-table-lifecycle-tabs {
    padding: 2px;
    border-color: rgb(255 255 255 / 5%);
    border-radius: 10px;
    box-shadow: inset 0 1px 2px 0 rgb(0 0 0 / 40%);
}

.threads-koel .user-table-lifecycle-tab.is-current,
.threads-koel .user-table-lifecycle-tab.is-current:hover,
.threads-koel .user-table-lifecycle-tab.is-current:focus-visible,
.threads-koel .user-table-lifecycle-tabs.has-travelling-indicator .user-table-lifecycle-tab.is-current {
    border-color: rgb(0 0 0 / 35%);
    background: linear-gradient(180deg, var(--accent), var(--accent-hover));
    box-shadow:
        inset 0 2px 3px 0 rgb(0 0 0 / 45%),
        inset 0 -1px 0 0 rgb(255 255 255 / 20%);
    color: var(--accent-ink);
}

/* The fill says "this one"; the lamp would only repeat it. */
.threads-koel .user-table-lifecycle-tab.is-current::before {
    display: none;
}

/* On an accent face the count inverts: a dark chip set into the key. */
.threads-koel .user-table-lifecycle-tab.is-current .user-table-lifecycle-count {
    background: rgb(0 0 0 / 30%);
    box-shadow: inset 0 1px 1px 0 rgb(0 0 0 / 45%);
    color: var(--accent-ink);
}

/* The dissolve that used to sit here is gone with the deck it softened:
   the options list now has its own scroll box, so nothing passes under
   anything and there is no cut to hide. */

/* ==========================================================================
   Filter trigger and its pills
   The trigger kept an early halo — an accent ring plus a 3px glow, keyed to
   :focus-visible as well as .is-open, so it stayed lit after the panel
   closed and read as a permanent state. Koel spends glow on state only, so
   open and focus are now the same quiet accent edge every other well uses.
   A chosen value is a chosen thing: its pill sits sunken with accent ink,
   the way the same value looks as an option inside the panel.
   ========================================================================== */

.threads-koel .threads-select__trigger:focus-visible,
.threads-koel .threads-select__trigger.is-open,
.threads-koel .threads-date__trigger:focus-visible,
.threads-koel details.user-table-filter[open] > summary.threads-select__trigger {
    border-color: rgb(var(--accent-readable-rgb) / 55%);
    box-shadow: inset 0 1px 1px 1px rgb(0 0 0 / 45%);
}

/*
 * The field a dialog opens on rings on plain `:focus`, not only on
 * `:focus-visible`. A browser draws `:focus-visible` when it judges the focus
 * to have come from the keyboard, and a dialog opened by clicking a button does
 * not qualify — so the field Threads had deliberately chosen to start on looked
 * exactly like the ones it had not, and Add Dispatch opened with the cursor in
 * a Card dropdown that gave no sign of it. MPF, 30 August 2026.
 *
 * Scoped to a dialog on purpose: a table filter that rings the moment it is
 * clicked would be noise, and it already has `.is-open` to say it is open.
 *
 * A ring rather than a tinted border, because the border at 55% is most of a
 * pixel and reads as nothing on a near-black field.
 */
.threads-koel .user-dialog .threads-select__trigger:focus,
.threads-koel .user-dialog .threads-select__trigger:focus-visible,
.threads-koel .user-dialog .threads-date__trigger:focus,
.threads-koel .user-dialog .threads-date__trigger:focus-visible {
    border-color: var(--accent-readable);
    box-shadow:
        inset 0 1px 1px 1px rgb(0 0 0 / 45%),
        0 0 0 3px rgb(var(--accent-readable-rgb) / 22%);
}

/* The line builders get a louder ring than the rest of the dialog. After the
   `+` stages a line, focus is put back on Item by script, not by a click or a
   Tab, so the only sign the cursor moved is the ring itself — and the 22%
   halo above is easy to miss. Solid accent ring, then the soft halo outside
   it. MPF asked for it to be obvious, 2 September 2026. Sits here, not in
   application.css, because this file loads last and its dialog rule above
   would otherwise win on specificity. */
.threads-koel .user-dialog .requirement-line-builder .threads-select__trigger:focus,
.threads-koel .user-dialog .requirement-line-builder .threads-select__trigger:focus-visible,
.threads-koel .user-dialog .requirement-line-builder > .form-field--pieces input:focus {
    border-color: var(--accent-readable);
    box-shadow:
        inset 0 1px 1px 1px rgb(0 0 0 / 45%),
        0 0 0 2px var(--accent-readable),
        0 0 0 5px rgb(var(--accent-readable-rgb) / 30%);
}

/* Everything else a dialog can start on gets the same ring, so the answer to
   "where am I?" does not depend on which kind of control it happens to be. */
.threads-koel .user-dialog input:focus,
.threads-koel .user-dialog select:focus,
.threads-koel .user-dialog textarea:focus {
    border-color: var(--accent-readable);
    box-shadow: 0 0 0 3px rgb(var(--accent-readable-rgb) / 22%);
}

/* A REFUSED FIELD IS RED, WHATEVER ELSE IT IS — MPF, 27 September 2026
   (docs/small-fixes-27-sep-spec.md, fix 1). threads-production-ui.css marks a
   select or date trigger the house validation refused with
   `[aria-invalid="true"]`, but every rule above names the same triggers under
   `.threads-koel` in a file that loads later, so the red lost to Koel's quiet
   edge at rest and to the accent ring once the refusal put focus on it — the
   one field the reader had to find looked like all the others. Re-stated here,
   after them, for every state that sets a border in this file: rest, keyboard
   focus, open, a dialog's `:focus`, and the line builders' louder ring. The
   ring keeps its shape in focus so "where am I?" is still answered, in red. */
.threads-koel .threads-select__trigger[aria-invalid="true"],
.threads-koel .threads-date__trigger[aria-invalid="true"] {
    border-color: var(--error);
    box-shadow: inset 0 1px 1px 1px rgb(0 0 0 / 45%), 0 0 0 3px rgb(214 91 91 / 18%);
}
.threads-koel .threads-select__trigger[aria-invalid="true"]:focus-visible,
.threads-koel .threads-select__trigger[aria-invalid="true"].is-open,
.threads-koel .threads-date__trigger[aria-invalid="true"]:focus-visible,
.threads-koel .user-dialog .threads-select__trigger[aria-invalid="true"]:focus,
.threads-koel .user-dialog .threads-select__trigger[aria-invalid="true"]:focus-visible,
.threads-koel .user-dialog .threads-date__trigger[aria-invalid="true"]:focus,
.threads-koel .user-dialog .threads-date__trigger[aria-invalid="true"]:focus-visible,
.threads-koel .user-dialog .requirement-line-builder .threads-select__trigger[aria-invalid="true"]:focus,
.threads-koel .user-dialog .requirement-line-builder .threads-select__trigger[aria-invalid="true"]:focus-visible {
    border-color: var(--error);
    box-shadow: inset 0 1px 1px 1px rgb(0 0 0 / 45%), 0 0 0 3px rgb(214 91 91 / 30%);
}

/* A narrowed column should be visible at a glance, so the chosen value is an
   accent pill rather than a near-black key with accent text on it. MPF asked
   for accent colour here, 30 Aug 2026. */
.threads-koel .user-table-filter__pill {
    border: 1px solid var(--accent);
    background: color-mix(in srgb, var(--accent) 22%, #0d0d0d);
    box-shadow: 0 1px 0 0 rgb(255 255 255 / 6%);
    color: var(--ink);
}

.threads-koel .user-table-filter__pill-remove {
    color: var(--ink);
    opacity: 0.75;
}

.threads-koel .user-table-filter__pill-remove:hover,
.threads-koel .user-table-filter__pill-remove:focus-visible {
    background: rgb(0 0 0 / 40%);
    box-shadow: inset 0 1px 2px 0 rgb(0 0 0 / 40%);
    color: var(--koel-ink-strong);
    outline: none;
}


/* ==========================================================================
   Tab strip — transport
   MPF chose rendition 1 from the Koel gallery, 28 Aug 2026, replacing the
   inverted accent-key strip that preceded it.

   A tape deck. The whole strip rests in graphite: no key carries the accent,
   because a row of accent faces put the brightest colour on the page on the
   options you are NOT looking at, and a yellow or green accent turned it
   into a wall. Selection is mechanical instead — the engaged key is simply
   held down, deeper than its neighbours, with a playhead seam burning along
   its bottom lip. The accent therefore appears exactly twice per strip, on
   the seam and on the engaged count, and both are reporting state.

   This is also what the segmented-control literature is unanimous about: the
   chosen segment must be legible by VALUE contrast, not colour alone, so it
   survives a dim screen, a colour-blind reader, and any accent the user
   picks. The lamp stays off here — among keys that are all dark, a pressed
   key needs no lamp to say it is live, and the seam already says it.

   Weights softened on MPF's read of the first ship, 28 Aug 2026: the dark
   hairline and the 2px drop under each key were stacking into one thick rim,
   and the face ran too far from top to bottom. The rim is now a single quiet
   pixel over a 1px drop, and the gradient covers about a third of its former
   range — enough to say "lit from above" without modelling the key. Taken
   down once more on his second read: a border cannot be thinner than the
   pixel it is, so what reads as thickness is its darkness — now 7% black.
   The key is separated from the deck by its own lit face rather than by a
   line drawn around it.
   ========================================================================== */

/* The deck: a shallow well the keys sit in, not a bordered container.
   What MPF was reading as thick black lines was this floor, seen through a
   3px gap between the keys and a 3px margin around them, painted almost
   pure black against faces two shades lighter. The seams are narrower now
   and the floor is lifted toward the keys, so the strip reads as one slab
   with fine partings rather than tiles grouted in black. */
.threads-koel .user-table-lifecycle-tabs {
    gap: 2px;
    padding: 2px;
    border: 1px solid rgb(255 255 255 / 5%);
    border-radius: 12px;
    background: #15171c;
    box-shadow: inset 0 1px 2px 0 rgb(0 0 0 / 35%);
}

/* Separate keys again: the flush single-hairline strip belonged to the
   earlier shape, so the shared edge is given back. */
.threads-koel .user-table-lifecycle-tab,
.threads-koel .user-table-lifecycle-tab:first-of-type,
.threads-koel .user-table-lifecycle-tab:last-of-type,
.threads-koel .user-table-lifecycle-tab:first-of-type:last-of-type {
    border: 1px solid rgb(0 0 0 / 7%);
    border-radius: 3px;
}

/* The strip's outer silhouette answers to the toolbar it sits above, not to
   its own keys: Clear all filters, the search field, the export keys and the
   dropdowns are all --koel-radius-sm, and a 3px end made this the one sharp
   thing in the row (MPF, 28 Aug 2026). So the ends round to that radius and
   the inner seams stay square, which is also what keeps the strip reading as
   one bank rather than a line of separate buttons.

   Where a legend holds the left end — Category One, Customer Group — the
   corner on show is the deck's, and the deck is already round enough to
   answer for it. The indicator is a span, so :first-of-type still finds the
   first key in either shape of strip. */
.threads-koel .user-table-lifecycle-tab:first-of-type {
    border-start-start-radius: var(--koel-radius-sm);
    border-end-start-radius: var(--koel-radius-sm);
}

.threads-koel .user-table-lifecycle-tab:last-of-type {
    border-start-end-radius: var(--koel-radius-sm);
    border-end-end-radius: var(--koel-radius-sm);
}

/* A segment strip opens with its legend, so its first key is an inner one. */
.threads-koel .user-table-segment-tabs .user-table-lifecycle-tab:first-of-type:not(:last-of-type) {
    border-start-start-radius: 3px;
    border-end-start-radius: 3px;
}

/* Once the strip wraps, the bank has no ends: the rounded right corner
   travels to whichever key fell to the second row, and the key that now
   finishes the first row sits square. There is no CSS for "last on this
   line", so koel.js flags a wrapped strip and every key simply becomes its
   own rounded key — which is what the strip has actually turned into. This
   is the small fix, not the narrow-screen redesign these rows still want. */
.threads-koel .user-table-lifecycle-tabs.is-wrapped .user-table-lifecycle-tab,
.threads-koel .user-table-lifecycle-tabs.is-wrapped .user-table-lifecycle-tab:first-of-type,
.threads-koel .user-table-lifecycle-tabs.is-wrapped .user-table-lifecycle-tab:last-of-type,
.threads-koel .user-table-segment-tabs.is-wrapped .user-table-lifecycle-tab:first-of-type:not(:last-of-type) {
    border-radius: var(--koel-radius-sm);
}

/* Rows of equal keys need a little air between the lines. */
.threads-koel .user-table-lifecycle-tabs.is-wrapped {
    row-gap: 3px;
}

.threads-koel .user-table-lifecycle-tab {
    padding: 8px 16px;
    background: linear-gradient(180deg, #272b33, #1e212a);
    box-shadow:
        inset 0 1px 0 0 rgb(255 255 255 / 9%),
        0 1px 1px rgb(0 0 0 / 20%);
    color: var(--koel-muted);
    letter-spacing: 0.04em;
    text-transform: uppercase;
    font-size: 12px;
}

.threads-koel .user-table-lifecycle-tab:hover {
    background: linear-gradient(180deg, #2e323b, #23262f);
    color: var(--koel-ink-strong);
}

/* On a graphite face an accent ring would read as a second selection, so
   focus is a light hairline inside the key. */
.threads-koel .user-table-lifecycle-tab:focus-visible {
    outline: none;
    box-shadow:
        inset 0 0 0 1px rgb(255 255 255 / 45%),
        inset 0 1px 0 0 rgb(255 255 255 / 9%),
        0 1px 1px rgb(0 0 0 / 20%);
}

/* A count on a resting key is a chip sunk into it. */
.threads-koel .user-table-lifecycle-count {
    background: rgb(0 0 0 / 40%);
    box-shadow: inset 0 1px 1px 0 rgb(0 0 0 / 55%);
    color: var(--koel-muted-soft);
}

/* Held down: deeper, darker, one pixel lower, and the playhead lit. */
.threads-koel .user-table-lifecycle-tab.is-current,
.threads-koel .user-table-lifecycle-tab.is-current:hover,
.threads-koel .user-table-lifecycle-tab.is-current:focus-visible,
.threads-koel .user-table-lifecycle-tabs.has-travelling-indicator .user-table-lifecycle-tab.is-current {
    border-color: rgb(0 0 0 / 12%);
    background: linear-gradient(180deg, #141619, #191b21);
    box-shadow:
        inset 0 3px 4px 0 rgb(0 0 0 / 60%),
        inset 0 -2px 0 0 var(--accent-readable);
    color: var(--koel-ink-strong);
    transform: translateY(1px);
}

/* No lamp: every key here is dark, so a pressed one cannot be mistaken for
   an off one, and the seam is already the report. */
.threads-koel .user-table-lifecycle-tab.is-current::before {
    display: none;
}

/* The engaged count is the second and last place the accent appears. */
.threads-koel .user-table-lifecycle-tab.is-current .user-table-lifecycle-count {
    background: rgb(0 0 0 / 45%);
    box-shadow: inset 0 1px 1px 0 rgb(0 0 0 / 60%);
    color: var(--accent-readable);
}

@media (prefers-reduced-motion: reduce) {
    .threads-koel .user-table-lifecycle-tab.is-current {
        transform: none;
    }
}


/* ==========================================================================
   Dashboard — the one screen that predated Koel
   It was built in increment 1, before the grammar existed: three flat cards
   of text, nothing pressable, no state anywhere. Every figure links to the
   page that can act on it, so every figure is now a key; the Pieces row gets
   the pipeline it always was; and Balance carries the lamp, because whether
   the shops are still owed is the one thing on this page with a state.
   MPF asked, 28 Aug 2026.
   ========================================================================== */

.threads-koel .koel-stat-strip {
    gap: 8px;
    padding: 2px;
}

/* The room is #191b20, and the first pass ran these from #1d2027 down to
   #16181d — so a key's lower half sat darker than the canvas and the tile
   read as neither raised nor sunken (MPF, 28 Aug 2026). Both stops are
   above the canvas now, over a Panther shadow: light card on a darker room,
   which is also what a pressable thing should look like here. */
.threads-koel .koel-stat {
    padding: 12px 16px;
    border: 1px solid rgb(255 255 255 / 7%);
    border-radius: var(--koel-radius-sm);
    background: linear-gradient(180deg, #262a33, #1f232b);
    box-shadow:
        inset 0 1px 0 0 rgb(255 255 255 / 9%),
        0 1px 2px rgb(0 0 0 / 30%),
        0 5px 16px rgb(0 0 0 / 38%);
    transition: background 0.16s ease, box-shadow 0.16s ease;
}

/* The divider belonged to a strip of text; these are separate keys now. */
.threads-koel .koel-stat + .koel-stat { border-left: 1px solid rgb(255 255 255 / 7%); }
.threads-koel .koel-stat:first-child { padding-left: 16px; }

a.threads-koel .koel-stat:hover,
.threads-koel a.koel-stat:hover {
    background: linear-gradient(180deg, #2e323c, #262a33);
    box-shadow:
        inset 0 1px 0 0 rgb(255 255 255 / 12%),
        0 2px 4px rgb(0 0 0 / 32%),
        0 8px 22px rgb(0 0 0 / 42%);
}

/* A key you press goes down. */
.threads-koel a.koel-stat:active {
    background: #0d0d0d;
    box-shadow:
        inset 0 2px 3px 0 rgb(0 0 0 / 55%),
        inset 0 -1px 0 0 rgb(255 255 255 / 8%);
}

.threads-koel a.koel-stat:focus-visible {
    outline: none;
    box-shadow:
        inset 0 0 0 1px rgb(var(--accent-readable-rgb) / 45%),
        inset 0 1px 0 0 rgb(255 255 255 / 8%),
        0 1px 2px rgb(0 0 0 / 40%);
}

.threads-koel .koel-stat__label {
    display: flex;
    align-items: center;
    gap: 8px;
    color: var(--koel-muted);
    font-size: 11px;
    letter-spacing: 0.07em;
    text-transform: uppercase;
}

.threads-koel .koel-stat__value { font-size: 30px; }

/* The lamp: green while the shops are still owed, red once over-dispatched. */
.threads-koel .koel-stat__lamp {
    flex: none;
    width: 5px;
    height: 5px;
    border-radius: 50%;
    background: #fff;
}

.threads-koel .koel-stat__lamp--success {
    box-shadow:
        0 0 5px 2px rgb(var(--koel-success-rgb) / 60%),
        0 0 3px 1px rgb(var(--koel-success-rgb) / 80%);
}

.threads-koel .koel-stat__lamp--error {
    box-shadow:
        0 0 5px 2px rgb(var(--koel-error-rgb) / 60%),
        0 0 3px 1px rgb(var(--koel-error-rgb) / 80%);
}

/* The pipeline: a well holding how far the work has actually travelled. */
.threads-koel .koel-pipeline {
    display: grid;
    gap: 8px;
    margin: 18px 2px 2px;
}

.threads-koel .koel-pipeline__track {
    height: 10px;
    border: 1px solid rgb(255 255 255 / 6%);
    border-radius: 999px;
    background: #0d0d0d;
    box-shadow:
        inset 0 1px 1px 1px rgb(0 0 0 / 50%),
        0 1px 0 0 rgb(255 255 255 / 6%);
    overflow: hidden;
}

.threads-koel .koel-pipeline__fill {
    height: 100%;
    border-radius: 999px;
    background: linear-gradient(180deg, var(--accent-hover), var(--accent));
    box-shadow: inset 0 1px 0 0 rgb(255 255 255 / 22%);
}

@media (prefers-reduced-motion: no-preference) {
    .threads-koel .koel-pipeline__fill {
        animation: koel-pipeline-fill 700ms cubic-bezier(0.22, 0.61, 0.36, 1) both;
    }
}

@keyframes koel-pipeline-fill {
    from { width: 0; }
}

.threads-koel .koel-pipeline__legend {
    margin: 0;
    color: var(--koel-muted);
    font-size: 12.5px;
}

.threads-koel .koel-pipeline__legend strong {
    color: var(--koel-ink-strong);
    font-variant-numeric: tabular-nums;
}

.threads-koel .koel-pipeline__percent {
    margin-left: 8px;
    padding: 2px 8px;
    border-radius: 999px;
    background: #0d0d0d;
    box-shadow: inset 0 1px 1px 0 rgb(0 0 0 / 45%);
    color: var(--accent-readable);
    font-size: 11.5px;
    font-weight: 700;
}


/* ==========================================================================
   Dashboard, round two — MPF, 28 Aug 2026
   The stats became keys, which turned the sections into cards holding cards.
   The keys are the cards now, so the sections go flat: a heading and a row
   of keys straight on the canvas. The page also says which module it is —
   the folded-shirt glyph, drawing itself once — and the Requirement splits
   read as three donuts in one accent, stepped down in strength so the ring
   stays a family rather than a palette.
   ========================================================================== */

.threads-koel .koel-section {
    margin: 0 0 26px;
    padding: 0;
    border: 0;
    background: none;
    box-shadow: none;
}

/* Home is a narrower page than the rest.
   ---------------------------------------
   Six family tiles across a 1640px page draw each one 539 wide, which is a lot
   of card for a glyph and two short lines. Capping the row instead was the
   first try and it was wrong in a different way: the tiles were the right size
   but the grid sat in the left 1236 of a 1640 page with dead room down the
   right, because a row capped inside an uncapped container cannot centre.

   Capping the page is what centres it. The heading, the lede, the group titles,
   the tiles and the separator then share one box and move together, so the
   whole block is centred and everything in it still lines up on the left.
   MPF preferred the smaller tile, 29 August 2026.

   Home only. The dashboards and tables want the full 1640. */
.threads-koel.application-page--koel-dashboard .koel-page {
    max-width: 1236px;
}

/* Home's two group headings take the accent. The page already carries one
   accent mark — the separator between the groups — and with everything else on
   it in muted ink that mark had nothing to belong to; the headings give it
   company and read the two groups as a set.

   Scoped to Home on purpose. Accent means "you are here" everywhere else in
   this chrome — the lit tab, the current page — and the dashboards stack four
   of these titles, where four accent labels would be noise and would spend a
   signal that is doing real work elsewhere. MPF's idea, 29 August 2026.
   Broadening it is one selector, if the dashboards ever want it. */
.threads-koel.application-page--koel-dashboard .koel-section__title {
    color: var(--accent-readable);
}

/* What sits between two groups of sections. A rule was the first answer and it
   was the wrong register — a hard line across a page whose sections are
   deliberately frameless. This is a Koel separator instead, tiny and centred,
   running one pass on arrival and then holding still. The file is named for the
   job rather than the drawing, so swapping which of the six is used is a copy
   over group-separator-animated-once.svg and nothing here changes. It was the
   LED chase; MPF picked the running stitch, 29 August 2026.

   Only Home has groups to separate. The dashboards stack four .koel-section
   blocks meant to read as one continuous column, so nothing here applies to a
   section itself. */
.threads-koel .koel-group-separator {
    /* The product row's own geometry, so the mark is placed by the grid rather
       than by the page. Centring it on the page was right only until the row
       hit its max-width and stopped growing: past 1236px the row stayed left
       and the separator kept centring on the viewport, 202px adrift at 1800px.
       MPF spotted it. Sharing the grid means it cannot drift again at any
       width. */
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 12px;
    /* 7px above, not 2: the mark sat too close under the card it follows, so
       it read as belonging to that row rather than as the gap between two.
       MPF asked for about five more, 29 August 2026. */
    margin: 7px 0 24px;
}

/* The middle column — the Trousers card's width, at every viewport. */
.threads-koel .koel-group-separator svg {
    grid-column: 2;
    width: 100%;
    height: 12px;
}

.threads-koel .koel-section__title {
    margin: 0 0 10px;
    color: var(--koel-muted);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.1em;
    text-transform: uppercase;
}

.threads-koel .koel-page-head--glyphed {
    display: flex;
    align-items: center;
    gap: 18px;
}

.threads-koel .koel-page-glyph {
    display: grid;
    flex: none;
    place-items: center;
    width: 84px;
    height: 84px;
    border: 1px solid rgb(255 255 255 / 7%);
    border-radius: var(--koel-radius);
    background: #0d0d0d;
    box-shadow:
        inset 0 1px 1px 1px rgb(0 0 0 / 50%),
        0 1px 0 0 rgb(255 255 255 / 6%);
    --ka-ink: var(--koel-muted);
    --ka-accent: var(--accent-readable);
}

.threads-koel .koel-page-glyph svg { width: 60px; height: 60px; }

/* -- Requirement splits ---------------------------------------------------- */

.threads-koel .koel-split-row {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
    gap: 12px;
}

.threads-koel .koel-split {
    display: block;
    padding: 16px;
    border: 1px solid rgb(255 255 255 / 7%);
    border-radius: var(--koel-radius-sm);
    background: linear-gradient(180deg, #262a33, #1f232b);
    box-shadow:
        inset 0 1px 0 0 rgb(255 255 255 / 9%),
        0 1px 2px rgb(0 0 0 / 30%),
        0 5px 16px rgb(0 0 0 / 38%);
    color: var(--ink);
    text-decoration: none;
    transition: background 0.16s ease, box-shadow 0.16s ease;
}

.threads-koel a.koel-split:hover {
    background: linear-gradient(180deg, #2e323c, #262a33);
    box-shadow:
        inset 0 1px 0 0 rgb(255 255 255 / 12%),
        0 2px 4px rgb(0 0 0 / 32%),
        0 8px 22px rgb(0 0 0 / 42%);
}

.threads-koel a.koel-split:focus-visible {
    outline: none;
    box-shadow:
        inset 0 0 0 1px rgb(var(--accent-readable-rgb) / 45%),
        inset 0 1px 0 0 rgb(255 255 255 / 8%);
}

.threads-koel .koel-split__title {
    margin: 0 0 14px;
    color: var(--koel-muted);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.07em;
    text-transform: uppercase;
}

.threads-koel .koel-split__body {
    display: flex;
    align-items: center;
    gap: 16px;
}

.threads-koel .koel-split__ring {
    width: 96px;
    height: 96px;
    flex: none;
    /* Dash arrays start at three o'clock; turn the ring so slice one starts
       at the top, where a reader looks first. */
    transform: rotate(-90deg);
}

.threads-koel .koel-split__track {
    fill: none;
    stroke: #14161b;
    stroke-width: 5.5;
}

.threads-koel .koel-split__slice {
    fill: none;
    stroke: rgb(var(--accent-readable-rgb) / var(--koel-slice, 1));
    stroke-width: 5.5;
}

@media (prefers-reduced-motion: no-preference) {
    .threads-koel .koel-split__slice {
        animation: koel-split-draw 620ms cubic-bezier(0.22, 0.61, 0.36, 1) both;
    }
}

@keyframes koel-split-draw {
    from { opacity: 0; }
}

.threads-koel .koel-split__legend {
    display: grid;
    gap: 5px;
    margin: 0;
    padding: 0;
    min-width: 0;
    flex: 1;
    list-style: none;
}

.threads-koel .koel-split__legend li {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 12px;
}

.threads-koel .koel-split__swatch {
    flex: none;
    width: 8px;
    height: 8px;
    border-radius: 2px;
    background: rgb(var(--accent-readable-rgb) / var(--koel-slice, 1));
}

.threads-koel .koel-split__name {
    overflow: hidden;
    color: var(--koel-muted);
    text-overflow: ellipsis;
    white-space: nowrap;
}

.threads-koel .koel-split__share {
    margin-left: auto;
    color: var(--koel-ink-strong);
    font-variant-numeric: tabular-nums;
    font-weight: 700;
}

.threads-koel .koel-split__empty {
    margin: 0;
    color: var(--koel-muted-soft);
    font-size: 12.5px;
}


/* ==========================================================================
   Home — the product families as keys
   The classic home lists five families with flat PNG glyphs; here each is a
   key carrying its own garment drawing, which draws itself once on arrival.
   The three families Threads does not carry yet stay inert — a key you
   cannot press should not pretend otherwise, so it keeps the tray's own
   floor rather than a lit face. MPF asked, 28 Aug 2026.
   ========================================================================== */

/* A fixed three-column track rather than auto-fit, so a family tile is the same
   size in every group. This was `repeat(auto-fit, minmax(170px, 1fr))`, which
   stretched its tracks to fill whatever row they were in — fine while Home was
   one flat row of five, wrong the moment it split into Readymade and Cut Piece,
   because the two-family group then drew each tile 52% wider than the
   three-family one. One grid reading as two.
 
   Counting the columns is what fixes it, and auto-fit cannot: it packs by the
   track minimum, so capping the maximum instead just gave seven 169px tracks.
   Three tracks also let the shorter group stop short rather than spreading two
   tiles across three columns' worth of room.

   No max-width here. It carried one briefly, at 1236px, which left the grid in
   the left 1236 of a .koel-page that is 1640 and centred — 404px of dead room
   down the right, which MPF saw straight away. The row fills its container like
   every other block on the page; the page is what caps the width. */
.threads-koel .koel-product-row {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 12px;
}

/* Readymade holds four families since Cotton Trousers joined it (6 Sep 2026);
   a fixed three-wide row left Jeans alone on a second line. */
.threads-koel .koel-product-row--four { grid-template-columns: repeat(4, 1fr); }

.threads-koel .koel-product {
    display: grid;
    justify-items: center;
    gap: 4px;
    padding: 22px 16px 18px;
    border: 1px solid rgb(255 255 255 / 7%);
    border-radius: var(--koel-radius-sm);
    background: linear-gradient(180deg, #262a33, #1f232b);
    box-shadow:
        inset 0 1px 0 0 rgb(255 255 255 / 9%),
        0 1px 2px rgb(0 0 0 / 30%),
        0 5px 16px rgb(0 0 0 / 38%);
    color: var(--ink);
    text-align: center;
    text-decoration: none;
    transition: background 0.16s ease, box-shadow 0.16s ease;
}

.threads-koel a.koel-product:hover {
    background: linear-gradient(180deg, #2e323c, #262a33);
    box-shadow:
        inset 0 1px 0 0 rgb(255 255 255 / 12%),
        0 2px 4px rgb(0 0 0 / 32%),
        0 8px 22px rgb(0 0 0 / 42%);
}

.threads-koel a.koel-product:active {
    background: #0d0d0d;
    box-shadow:
        inset 0 2px 3px 0 rgb(0 0 0 / 55%),
        inset 0 -1px 0 0 rgb(255 255 255 / 8%);
}

.threads-koel a.koel-product:focus-visible {
    outline: none;
    box-shadow:
        inset 0 0 0 1px rgb(var(--accent-readable-rgb) / 45%),
        inset 0 1px 0 0 rgb(255 255 255 / 9%);
}

/* Nothing to open: it sits in the floor rather than standing on it. */
.threads-koel .koel-product--inert {
    background: #14161b;
    box-shadow: inset 0 1px 1px 1px rgb(0 0 0 / 45%);
}

.threads-koel .koel-product__glyph {
    display: grid;
    place-items: center;
    width: 74px;
    height: 74px;
    margin-bottom: 8px;
    --ka-ink: var(--koel-muted);
    --ka-accent: var(--accent-readable);
}

.threads-koel .koel-product__glyph svg { width: 100%; height: 100%; }

.threads-koel a.koel-product:hover .koel-product__glyph { --ka-ink: var(--koel-ink-strong); }

.threads-koel .koel-product--inert .koel-product__glyph { --ka-ink: var(--koel-muted-soft); }

.threads-koel .koel-product__name {
    color: var(--koel-ink-strong);
    font-size: 15px;
    font-weight: 700;
    letter-spacing: -0.01em;
}

.threads-koel .koel-product--inert .koel-product__name { color: var(--koel-muted); }

.threads-koel .koel-product__hint {
    color: var(--koel-muted-soft);
    font-size: 11.5px;
}


/* The pills live inside the trigger's value span, so every selection widened
   the trigger, which widened the column, which reflowed the whole table
   (MPF, 28 Aug 2026). The span comes out of flow: it fills the trigger and
   clips, contributing nothing to the column's intrinsic width, so the column
   is sized by its heading and its data as it should be. */
.threads-koel .user-table-filter { display: block; }

.threads-koel .user-table-filter > summary.threads-select__trigger {
    position: relative;
    width: 100%;
}

.threads-koel .user-table-filter > summary.threads-select__trigger > .user-table-filter__status {
    position: absolute;
    inset: 0 32px 0 12px;
    display: flex;
    align-items: center;
    gap: 5px;
    overflow: hidden;
}

.threads-koel .user-table-filter > summary.threads-select__trigger > .threads-select__chevron {
    margin-left: auto;
}

/* ==========================================================================
   Filter trigger and list, MPF's keyboard-and-selection pass, 28 Aug 2026
   A column header must not change height as selections accumulate: the
   trigger keeps one line and lets the pills run out of view, since the
   count beside them already says how many there are. And two chosen options
   next to each other are two sunken wells, which read as one long trough
   with no gap — the list spaces its rows so each choice keeps its shape.
   ========================================================================== */

.threads-koel .user-table-filter > summary.threads-select__trigger {
    flex-wrap: nowrap;
    overflow: hidden;
}

.threads-koel .user-table-filter__pill {
    flex: none;
    /* The base rule caps this at 112px, which clipped "Big Bull - 30". Lifting
       the cap alone was not enough — the column is 150px, and once the cell
       padding, the chevron and the pill's own padding are taken out there were
       76px left for an 85px name. The weight and padding come down to buy that
       back; anything longer still ellipses, which is the column's size talking
       rather than this rule. */
    max-width: 100%;
    min-width: 0;
    padding: 3px 5px;
    gap: 3px;
    font-weight: 600;
    overflow: hidden;
}

/* Room for the pill and its chevron without the two colliding. */
.threads-koel .user-table-filter__status {
    display: flex;
    align-items: center;
    min-width: 0;
    overflow: hidden;
}

.threads-koel .user-table-filter__pill-text {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Both panels draw their options as wells — a chosen one pressed in, a hovered
   one dipped — so two of them must never share an edge. Sitting flush, the
   selected option and the one directly above or below it ran their 8px corners
   together and read as a single shape rather than two. The filter panel already
   kept 3px for this reason; the select panel never did, and MPF found it on the
   Sleeve dropdown, 30 August 2026. One rule now, so the two panels cannot drift
   apart again. */
.threads-koel .user-table-filter__options,
.threads-koel .threads-select__options {
    display: grid;
    gap: 3px;
}


/* ==========================================================================
   Three tiers — masthead, menubar, sub-bar
   The menu shared the masthead with the brand and the account block, which
   crowded it and left the breadcrumb strip almost no height. It has its own
   bar now, as the Egret study always had, and the sub-bar can breathe.
   MPF asked, 28 Aug 2026.
   ========================================================================== */

.threads-koel .main-navigation {
    padding-block: 12px;
}

.threads-koel .koel-menubar {
    display: flex;
    align-items: center;
    padding: 0 32px;
    border-bottom: 1px solid var(--koel-rail-line);
    background: var(--koel-rail-deep);
}

.threads-koel .koel-menubar .navigation-menu {
    display: flex;
    align-items: stretch;
    gap: 2px;
    margin: 0;
    padding: 0;
    list-style: none;
}

.threads-koel .koel-menubar .navigation-menu > li { display: flex; }

/* An entry fills the bar's height, so the whole strip is the target and the
   current one can be a lit panel rather than a floating pill. */
.threads-koel .koel-menubar .navigation-link {
    display: flex;
    align-items: center;
    padding: 15px 18px;
    border: 0;
    border-radius: 0;
    background: transparent;
    box-shadow: none;
    color: var(--koel-rail-muted);
    font-size: 14.5px;
    font-weight: 700;
    cursor: pointer;
}

.threads-koel .koel-menubar .navigation-link:hover {
    background: rgb(255 255 255 / 5%);
    color: var(--koel-ink-strong);
}

/* The section you are in is lit from beneath, the way the pressed tab keys
   are: accent ink over an accent seam, not a floating chip. */
.threads-koel .koel-menubar .navigation-link.is-current,
.threads-koel .koel-menubar .navigation-link.has-current-child {
    background: linear-gradient(180deg,
        rgb(var(--accent-readable-rgb) / 0%),
        rgb(var(--accent-readable-rgb) / 10%));
    box-shadow: inset 0 -2px 0 0 var(--accent-readable);
    color: var(--accent-readable);
}

.threads-koel .koel-menubar .navigation-link:focus-visible {
    outline: none;
    box-shadow: inset 0 0 0 1px rgb(var(--accent-readable-rgb) / 55%);
}

/* -- The find glyph at the right end of the bar ----------------------------
   The command bar's mouse-and-thumb entry (docs/fuzzy-scope-and-command-bar-spec.md
   §2 item 9). The list is the bar's only child and sizes to its entries, so it
   is told to fill the bar and the glyph's item is pushed to the far end — the
   entry itself keeps every menubar-link rule (full-height strip, hover, the
   lit current state it never gets). The magnifier is 18px, a shade larger
   than the 14.5px labels beside it so an icon-only entry does not read as a
   missing word. */
.threads-koel .koel-menubar .navigation-menu {
    flex: 1 1 auto;
    min-width: 0;
}

.threads-koel .koel-menubar .navigation-menu > li:has(> .koel-menubar__search) {
    margin-left: auto;
}

.threads-koel .koel-menubar .koel-menubar__search {
    padding-inline: 13px;
}

.threads-koel .koel-menubar .koel-menubar__search svg {
    width: 18px;
    height: 18px;
}

/* -- The family row, and the tabs that choose it -------------------------- */

/* The group tabs are buttons, and .koel-menubar .navigation-link above already
   zeroes a button's border, background and radius — so all that is missing is
   the font, which a button does not inherit on its own. Nothing here makes
   them look like the table pills they behave like: MPF asked for the operation
   and not the appearance. */
.threads-koel .koel-family-tab {
    font: inherit;
    font-size: 14.5px;
    font-weight: 700;
}

/* The open group, when it is not the group you are in — on Home, or while
   browsing the other row. It needs to be legible as "this row belongs to that
   tab" without claiming "you are here", which is what the accent seam on
   .is-current says. So: brighter ink, no seam. */
.threads-koel .koel-family-tab[aria-pressed="true"]:not(.is-current) {
    background: rgb(255 255 255 / 4%);
    color: var(--koel-ink-strong);
}

/* The second tier. Same ground as the bar above it so the two read as one
   piece of chrome, with shallower entries so it is plainly the subordinate
   row and not a second menubar. */
.threads-koel .koel-menubar--families {
    /* Its height comes from the entries inside it, so an empty row would
       collapse and the whole chrome would change height between Home and a
       family page — the page jumping under the reader on every other
       navigation. The floor is what a filled row measures, so the bar is the
       same height whether or not a group is chosen. MPF asked for empty
       rather than absent, 29 August 2026. */
    min-height: 40px;
    padding-top: 0;
    padding-bottom: 0;
    background: var(--koel-rail-deep);
    box-shadow: inset 0 1px 0 0 rgb(0 0 0 / 18%);
}

/* `hidden` is only a display:none default, and .koel-menubar .navigation-menu
   sets display:flex — which wins, so the hidden row rendered anyway and both
   groups appeared side by side. The attribute has to be honoured explicitly
   wherever a display is set on the same element. */
.threads-koel .koel-menubar--families .navigation-menu[hidden] {
    display: none;
}

.threads-koel .koel-menubar--families .navigation-link,
.threads-koel .koel-menubar--families .navigation-submenu > summary {
    padding-top: 11px;
    padding-bottom: 11px;
    font-size: 13.5px;
}

/* A family Threads knows about and has not built. It is deliberately present
   and deliberately dead: naming it says the family exists and is coming, which
   is more than leaving it out would say, and a dead link would teach the user
   the wrong thing. */
.threads-koel .koel-menubar .navigation-link--inert,
.threads-koel .koel-menubar .navigation-link--inert:hover {
    background: none;
    box-shadow: none;
    color: var(--koel-muted-soft);
    cursor: default;
}

/* -- The sub-bar gets its height back ------------------------------------- */

.threads-koel .koel-subbar {
    padding: 11px 32px;
}

.threads-koel .koel-crumbs {
    gap: 6px;
    font-size: 12.5px;
}

.threads-koel .koel-crumbs__separator {
    display: inline-flex;
    align-items: center;
    color: var(--koel-muted-soft);
}

.threads-koel .koel-crumbs__separator svg { width: 12px; height: 12px; }

/* A step that only names a level of the menu is not a destination. */
.threads-koel .koel-crumbs__level { color: var(--koel-muted-soft); }

.threads-koel .koel-crumbs a { padding: 2px 0; }


/* ==========================================================================
   PPC Rules — the cascade becomes one instrument
   The last interactive surface in Shirts still wearing classic styling: four
   flat slabs, a selected row lit by an inner accent glow with a 3px bar down
   its edge, and an accent focus ring on the panel search. Two of those are
   idioms Koel spent the whole build removing — glow belongs to LEDs, and a
   chosen thing is sunken. MPF asked, 28 Aug 2026.
   ========================================================================== */

.threads-koel .ppc-panel-browser {
    border: 1px solid var(--koel-line);
    border-radius: var(--koel-radius);
    background: var(--koel-line);
    box-shadow: var(--koel-shadow-card);
}

.threads-koel .ppc-panel {
    background: var(--koel-card);
}

/* A column awaiting its turn is an empty well, not a dimmed slab. */
.threads-koel .ppc-panel.is-locked {
    background: #14161b;
    box-shadow: inset 0 1px 2px 0 rgb(0 0 0 / 40%);
}

.threads-koel .ppc-panel + .ppc-panel { border-left-color: var(--koel-line); }

.threads-koel .ppc-panel-header {
    border-bottom: 1px solid var(--koel-line);
    background: var(--koel-card-quiet);
}

.threads-koel .ppc-panel-header h2 {
    color: var(--koel-muted);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

.threads-koel .ppc-panel-header p { color: var(--koel-muted-soft); }

/* The sort control is a key like every other small pressable. */
.threads-koel .ppc-panel-sort {
    border: 1px solid rgb(255 255 255 / 7%);
    border-radius: 7px;
    background: #16181d;
    box-shadow:
        inset 0 1px 0 0 rgb(255 255 255 / 7%),
        0 1px 2px rgb(0 0 0 / 35%);
    color: var(--koel-muted);
}

.threads-koel .ppc-panel-sort:hover,
.threads-koel .ppc-panel-sort:focus-visible {
    border-color: rgb(255 255 255 / 12%);
    background: #1f2127;
    color: var(--koel-ink-strong);
    outline: none;
}

/* The panel search is a well, and focus is the quiet accent edge every other
   field here uses rather than a ring. */
.threads-koel .ppc-panel-search {
    border-bottom: 1px solid var(--koel-line);
    background: var(--koel-card);
}

.threads-koel .ppc-panel-search input {
    border: 1px solid rgb(255 255 255 / 7%);
    border-radius: 8px;
    background: #0d0d0d;
    box-shadow: inset 0 1px 1px 1px rgb(0 0 0 / 45%);
    color: var(--ink);
    font-size: 12px;
}

.threads-koel .ppc-panel-search input::placeholder { color: var(--koel-muted-soft); }

.threads-koel .ppc-panel-search input:focus {
    border-color: rgb(var(--accent-readable-rgb) / 55%);
    box-shadow: inset 0 1px 1px 1px rgb(0 0 0 / 45%);
}

.threads-koel .ppc-panel-search__icon { color: var(--koel-muted-soft); }

/* -- Rows: hover dips, the chosen one is pressed in ------------------------ */

.threads-koel .ppc-panel-row { border-bottom: 1px solid var(--koel-line); }

.threads-koel .ppc-panel-row:hover {
    background: rgb(0 0 0 / 26%);
    box-shadow: inset 0 1px 2px 0 rgb(0 0 0 / 30%);
}

/* No glow, and no bar down the edge: the row simply goes down, the way every
   chosen thing in Koel does. */
.threads-koel .ppc-panel-row.is-selected {
    background: #0d0d0d;
    box-shadow:
        inset 0 1px 1px 1px rgb(0 0 0 / 50%),
        inset 0 -1px 0 0 rgb(255 255 255 / 5%);
}

.threads-koel .ppc-panel-row.is-selected::before { background: transparent; }

.threads-koel .ppc-panel-row.is-selected .ppc-panel-option__label {
    color: var(--accent-readable);
}

/* The initial glyph is a small key on a resting row and a lit chip on the
   chosen one, so the pressed row reads as the lit thing in the column. */
.threads-koel .ppc-panel-option__glyph {
    border: 1px solid rgb(255 255 255 / 7%);
    background: #16181d;
    box-shadow:
        inset 0 1px 0 0 rgb(255 255 255 / 7%),
        0 1px 2px rgb(0 0 0 / 35%);
    color: var(--koel-muted);
}

.threads-koel .ppc-panel-row.is-selected .ppc-panel-option__glyph {
    border-color: rgb(0 0 0 / 35%);
    background: linear-gradient(180deg, var(--accent-hover), var(--accent));
    box-shadow: inset 0 1px 0 0 rgb(255 255 255 / 22%);
    color: var(--accent-ink);
}

.threads-koel .ppc-panel-option__label { font-size: 12px; }

.threads-koel .ppc-panel-option:focus-visible {
    outline: none;
    box-shadow: inset 0 0 0 1px rgb(var(--accent-readable-rgb) / 45%);
}

.threads-koel .ppc-panel-empty {
    color: var(--koel-muted-soft);
    font-size: 11.5px;
}

.threads-koel .ppc-panel-empty strong {
    color: var(--koel-muted);
    font-size: 12.5px;
}


/* Preferences is a small settings page, not a workspace: it holds one card
   with six choices in it, so it has no business spanning a 1440px table
   layout (MPF, 28 Aug 2026). A narrow column, two choices to a row, and
   smaller rows to match. */
.threads-koel .preferences-page {
    width: min(100%, 560px);
}

.threads-koel .accent-picker__choices {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 10px;
}

.threads-koel .accent-choice {
    min-height: 56px;
    padding: 10px;
    gap: 10px;
}

.threads-koel .accent-choice__swatch {
    width: 26px;
    height: 26px;
    flex: 0 0 26px;
}

/* ==========================================================================
   Preferences — the accent picker in Koel
   The page joined Koel with Masters and Admin, so its one real control
   should speak the grammar: each choice is a key, the swatch is the lamp of
   the colour it offers, and the accent you are actually using sits pressed
   into the card. MPF, 28 Aug 2026.
   ========================================================================== */

.threads-koel .accent-choice {
    border: 1px solid rgb(255 255 255 / 7%);
    border-radius: var(--koel-radius-sm);
    background: linear-gradient(180deg, #262a33, #1f232b);
    box-shadow:
        inset 0 1px 0 0 rgb(255 255 255 / 9%),
        0 1px 2px rgb(0 0 0 / 30%);
}

.threads-koel .accent-choice:hover {
    background: linear-gradient(180deg, #2e323c, #262a33);
    box-shadow:
        inset 0 1px 0 0 rgb(255 255 255 / 12%),
        0 2px 5px rgb(0 0 0 / 38%);
}

/* The swatch glows its own colour, the way every lamp here does. */
.threads-koel .accent-choice__swatch {
    box-shadow:
        inset 0 1px 0 0 rgb(255 255 255 / 22%),
        0 0 10px 1px rgb(0 0 0 / 35%);
}

/* Chosen means pressed in — the same socket as a current tab, tinted by the
   colour it selects rather than by the accent currently in force. */
.threads-koel .accent-choice:has(input:checked) {
    border-color: rgb(0 0 0 / 40%);
    background:
        linear-gradient(180deg, rgb(255 255 255 / 4%), rgb(255 255 255 / 0%)),
        #0d0d0d;
    box-shadow:
        inset 0 2px 3px 0 rgb(0 0 0 / 55%),
        inset 0 -1px 0 0 rgb(255 255 255 / 8%);
}

.threads-koel .accent-choice:has(input:checked) .accent-choice__swatch {
    box-shadow:
        inset 0 1px 0 0 rgb(255 255 255 / 26%),
        0 0 9px 2px var(--choice-colour);
}

.threads-koel .accent-choice input:focus-visible ~ .accent-choice__swatch {
    outline: none;
    box-shadow:
        inset 0 1px 0 0 rgb(255 255 255 / 26%),
        0 0 0 2px rgb(var(--accent-readable-rgb) / 55%);
}

.threads-koel .accent-preview-status { color: var(--koel-muted); }

.threads-koel .preferences-role-picture {
    border: 1px solid rgb(255 255 255 / 7%);
    border-radius: var(--koel-radius-sm);
    background: #0d0d0d;
    box-shadow:
        inset 0 1px 1px 1px rgb(0 0 0 / 50%),
        0 1px 0 0 rgb(255 255 255 / 6%);
}


/* ==========================================================================
   Row actions built in JavaScript — the mask is gone
   From 28 Aug 2026 Koel hid the text of any row key without an SVG and
   painted a mask glyph over it: a pencil by default, a bin on danger, and
   (4 Sep 2026) a tick and a cross for the Jeans allocation Save and Cancel.
   Every other key fell through to the pencil, so the Jeans Charts eye, the
   Card Dispatch Save and Cancel and the Dispatch Note + all showed a pencil.
   Since 29 September 2026 every row key is drawn as a real glyph from the
   house set — getActionIcon() in PHP, window.ThreadsActionIcon() in the
   scripts — and the mask went with it (Frost sweep, stray #6).
   ========================================================================== */


/* ==========================================================================
   Shirting Cut Mappings — the two that stay hand-built
   Shirting Fabric, Shirting Mapping and edit Cut Mapping used to be laid out
   here by dialog id. They are plain rows of short fields, so on 30 August 2026
   they moved onto the shared user-dialog--horizontal class, which derives their
   width and their Status track from the number of fields they carry.

   What is left below is what that ladder genuinely cannot say. Both of these
   hold the Cuts token field, whose chip well is the tall control and spans two
   tracks so ten short Cuts lay out in one row instead of a narrow stack — a
   shape no field count can imply.
   ========================================================================== */
/* Add carries one control more than Edit on the same row now that Status is a
   switch, so it takes the width of that extra track and its gap on top of the
   shared 860. The three fr fields then keep exactly the widths they had before
   the switch arrived, and the Cuts well still lays ten short Cuts out in one
   row rather than nine and a stray. MPF, 30 August 2026. */
.threads-koel dialog[id="create-shirting-cut-mapping-dialog"] {
    width: min(94vw, 920px);
    max-width: min(94vw, 920px);
}

.threads-koel dialog[id="create-shirting-cut-mapping-dialog"] .user-dialog-form {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 16px 14px;
    align-items: start;
}

/* Add gathers many Cuts at once. The token field is the tall control, so it
   shares its row with one select rather than taking a row of its own — a well
   spanning the whole dialog left a lot of air beside two short selects. MPF,
   30 August 2026. Status is a switch now rather than a select, so it closes the
   same row instead of taking one of its own: three fr tracks carry the fields
   and a fourth `auto` track is only as wide as the switch. MPF, 30 August 2026.

   Fields are placed by what they are, not by their position in the markup, so
   the two dialogs can order their fields differently without the grid needing
   to know which is which. Group lands left of the Cuts on Default and right of
   them on Custom: each dialog follows its own field order, and the mirror is
   deliberate. Edit keeps the plain four-track grid above. */
.threads-koel dialog[id="create-shirting-cut-mapping-dialog"] .user-dialog-form {
    grid-template-columns: repeat(3, minmax(0, 1fr)) auto;
    align-items: start;
}

/* Default: Group, then the Cuts filling the rest of the row. */
.threads-koel dialog[id="create-shirting-cut-mapping-dialog"] .form-field--mapping-group {
    grid-row: 1;
    grid-column: 1;
}

.threads-koel dialog[id="create-shirting-cut-mapping-dialog"] .token-input {
    grid-row: 1;
    grid-column: 2 / 4;
}

/* Custom: Item leads its own row, then the Cuts with Group beside them. The
   Item field is the tell — it exists only on the Custom dialog. */
.threads-koel dialog[id="create-shirting-cut-mapping-dialog"] .form-field--mapping-item {
    grid-row: 1;
    grid-column: 1;
}

.threads-koel dialog[id="create-shirting-cut-mapping-dialog"]:has(.form-field--mapping-item) .token-input {
    grid-row: 2;
    grid-column: 1 / 3;
}

.threads-koel dialog[id="create-shirting-cut-mapping-dialog"]:has(.form-field--mapping-item) .form-field--mapping-group {
    grid-row: 2;
    grid-column: 3;
}

.threads-koel dialog[id="create-shirting-cut-mapping-dialog"] .form-field--mapping-status {
    grid-row: 1;
    grid-column: 4;
}

.threads-koel dialog[id="create-shirting-cut-mapping-dialog"]:has(.form-field--mapping-item) .form-field--mapping-status {
    grid-row: 2;
}

/* The same two-up shape on the Fabrics page's per-row dialog. It has no Item
   field — the Item is the row — so it is Cuts, Group and Status across one
   row. Same grid, different address. */
/* Widened by the Status track and its gap when Status became a switch on this
   row, so the Cuts field keeps the width it had and ten short Cuts still lay
   out in one row. MPF, 30 August 2026. */
.threads-koel dialog[id^="add-shirting-cut-mapping-"] .user-dialog-surface {
    width: min(94vw, 840px);
}

.threads-koel dialog[id^="add-shirting-cut-mapping-"] {
    width: min(94vw, 840px);
    max-width: min(94vw, 840px);
}

.threads-koel dialog[id^="add-shirting-cut-mapping-"] .user-dialog-form {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr)) auto;
    gap: 16px 14px;
    align-items: start;
}

.threads-koel dialog[id^="add-shirting-cut-mapping-"] .token-input {
    grid-row: 1;
    grid-column: 1 / 3;
}

.threads-koel dialog[id^="add-shirting-cut-mapping-"] .form-field--mapping-group {
    grid-row: 1;
    grid-column: 3;
}

.threads-koel dialog[id^="add-shirting-cut-mapping-"] .form-field--mapping-status {
    grid-row: 1;
    grid-column: 4;
}

.threads-koel dialog[id^="add-shirting-cut-mapping-"] .token-input__well {
    max-height: 132px;
}

/* The well is half the dialog now, so ten short Cuts wrap to three rows rather
   than one. Room for those without a scrollbar on a list this short; anything
   longer still scrolls. */
.threads-koel dialog[id="create-shirting-cut-mapping-dialog"] .token-input__well {
    max-height: 132px;
}

.threads-koel dialog[id="create-shirting-cut-mapping-dialog"] .user-dialog-form > .status-message {
    grid-column: 1 / -1;
}

@media (max-width: 860px) {
    .threads-koel dialog[id="create-shirting-cut-mapping-dialog"] .user-dialog-form {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }

    /* Two tracks cannot honour a placement addressed to a third or fourth
       column: the fields would push implicit columns off the dialog. Drop back
       to flow so they wrap in the order they are written. */
    .threads-koel dialog[id="create-shirting-cut-mapping-dialog"] .user-dialog-form > .form-field,
    .threads-koel dialog[id="create-shirting-cut-mapping-dialog"] .user-dialog-form > .token-input {
        grid-row: auto;
        grid-column: auto;
    }
}

/* ==========================================================================
   Jeans edit Requirement — horizontal, like the batch dialog beside it
   Eight fields stacked into a tall column while Add Requirements lays its
   context and line builder out across the dialog. The edit form takes the
   same six-track grid the Shirt edit dialog uses, so both families edit the
   same way (MPF, 28 Aug 2026). Targeted by id prefix: the markup is a plain
   user-dialog shared with pages that should keep stacking, and nothing in
   the classic render changes.
   ========================================================================== */

.threads-koel dialog[id^="edit-jeans-requirement-"] {
    width: min(94vw, 880px);
    max-width: min(94vw, 880px);
}

/* The shared twelve tracks, so this dialog sizes its fields the same way the
   Add dialog beside it does. It used to hold six tracks and a run of
   :nth-child spans, which meant the layout was pinned to the field order and
   would have moved the moment a field was added. The rows now fall out of the
   widths: Date, Customer Group and Shop fill the first, Item, Fit, Size and
   Pieces the second, Status the third. */
.threads-koel dialog[id^="edit-jeans-requirement-"] .user-dialog-form {
    display: grid;
    grid-template-columns: repeat(12, minmax(0, 1fr));
    gap: 16px 14px;
}

.threads-koel dialog[id^="edit-jeans-requirement-"] .user-dialog-form > .form-field {
    grid-column: span var(--field-span, 3);
    min-width: 0;
}

@media (max-width: 920px) {
    .threads-koel dialog[id^="edit-jeans-requirement-"] .user-dialog-form > .form-field {
        grid-column: span 6;
    }
}


/* ==========================================================================
   Tooltips — the smallest floating surface, and the last one still classic
   The controller appends them to <body>, which carries .threads-koel, so
   they can be reached from here. They take the same material as every other
   thing that floats above the page — the panel card, its edge and the
   raised shadow — including the little arrow, which has to be painted to
   match or it gives the join away.
   ========================================================================== */

.threads-koel .table-action-tooltip {
    border: 1px solid var(--koel-line-strong);
    border-radius: 9px;
    background: var(--koel-card);
    box-shadow: var(--koel-shadow-raised);
    color: var(--ink);
    font-size: 12.5px;
}

/* Carries the tooltip for a disabled Add control. A disabled button emits no
   pointer events, so the explanation has to hang on something that does; this
   wraps it without changing the row's layout. */
.threads-koel .management-add-user-tooltip-anchor {
    display: inline-flex;
}

/* A tooltip that explains why something cannot be done, rather than naming what
   a control does. The lamp is the one the dashboard's Balance figure uses for a
   negative — same size, same glow, same meaning: look here.

   Laid out as a flex row so the lamp sits beside the text rather than inside
   its flow. The text keeps the line breaks the script puts in, one sentence per
   line, and is left-aligned because a list of sentences centred reads as
   ragged. */
.threads-koel .table-action-tooltip[data-tone="danger"] {
    display: flex;
    gap: 8px;
    align-items: flex-start;
    white-space: pre-line;
    text-align: left;
}

/* Aligned to the first line, not to the middle of the block: the lamp belongs
   to the sentence that says what is wrong, and the sentences after it say what
   to do. Half a line-height down from the top, computed from the 12.5px type at
   1.3 rather than guessed. */
.threads-koel .table-action-tooltip[data-tone="danger"]::before {
    content: "";
    flex: none;
    width: 5px;
    height: 5px;
    margin-top: 5.5px;
    border-radius: 50%;
    background: #fff;
    box-shadow:
        0 0 5px 2px rgb(var(--koel-error-rgb) / 60%),
        0 0 3px 1px rgb(var(--koel-error-rgb) / 80%);
}

.threads-koel .table-action-tooltip[data-placement="above"]::after,
.threads-koel .table-action-tooltip[data-placement="below"]::after {
    border-color: var(--koel-line-strong);
    background: var(--koel-card);
}


/* A negative quantity is red wherever it is printed — the rule the reports
   have always followed. Koel sets the ink on every table cell, which
   outranks the shared report rule, so it has to say so itself. */
.threads-koel .user-table td.card-working-negative,
.threads-koel .user-table th.card-working-negative,
.threads-koel .card-working-negative {
    color: var(--error);
}


/* The scope block's actions become a two-track grid so the workbook button
   can sit beneath Clear and View Report at exactly their combined width —
   the tracks size to those two buttons and the grid is pushed right, so the
   third spans both plus the gap between them. MPF asked, 28 Aug 2026. */
.threads-koel .dispatch-report-form--scope .dispatch-report-actions {
    display: grid;
    grid-template-columns: auto auto;
    justify-content: end;
    gap: 10px;
}

.threads-koel .dispatch-report-workbook {
    grid-column: 1 / -1;
    width: 100%;
}


/* ==========================================================================
   Sign-in — the first screen anyone sees
   The classic login sat on the linen photograph in a glass card. Koel has no
   photograph and no glass: the canvas is solid, the card is a raised panel
   with the room's own shadow, and the fields are wells like every other field
   in the application. Added when the classic chrome was retired, 28 Aug 2026
   — a sign-in wearing the old skin would have been the one screen that
   contradicted everything behind it.
   ========================================================================== */

.threads-koel.auth-page {
    background: var(--canvas);
}

.threads-koel .login-card {
    border: 1px solid var(--koel-line-strong);
    border-radius: var(--koel-radius);
    background: var(--koel-card);
    box-shadow: var(--koel-shadow-raised);
    backdrop-filter: none;
}

/* The wordmark is ink on the panel, not a plate of its own. */
.threads-koel .login-brand .threads-wordmark {
    background: transparent;
    box-shadow: none;
}

.threads-koel .login-heading h1 {
    color: var(--koel-ink-strong);
}

.threads-koel .login-heading p {
    color: var(--koel-muted);
}

/* Remember me is a switch on a panel, so it reads as one. */
.threads-koel .login-form .checkbox-field {
    color: var(--koel-muted);
}


/* ==========================================================================
   Phones — the chrome gets out of the way
   MPF asked for full parity on a phone, 28 Aug 2026, after the retirement.
   Measured first on a 375×812 screen: the masthead wrapped its account block
   to a second line (120px), the five-section menubar wrapped to two rows
   (96px), and with the breadcrumb bar that was 257px — a third of the screen
   — before a single word of the page. Then 116px of the width went to page
   and card padding sized for a desktop, leaving a nine-column table 256px to
   live in and a sideways scroll to reach the rest.

   Nothing here hides a control or changes a behaviour. It is the same chrome
   at a size that fits: the rail keeps its grammar, the menubar keeps every
   section, and the sub-bar keeps the trail. 760px is deliberately the same
   breakpoint the production mobile table uses for its `+` disclosure, so a
   screen either gets the whole narrow treatment or none of it.
   ========================================================================== */

/* Same 32px flex reservation and centred alignment as the former portrait.
   The mobile picture was 26px tall but still reserved 32px horizontally. */
/* MPF, 6 September 2026: a slightly larger personal emoji in the Users table. */
.threads-koel .user-table-emoji {
    display: inline-block;
    font-size: 20px;
    line-height: 1;
    vertical-align: -3px;
    margin-right: 3px;
}

.threads-koel .navigation-user-emoji {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    flex: 0 0 32px;
    font-size: 26px;
    line-height: 1;
}
.threads-koel .form-field--account-emoji input[type="text"] {
    width: 120px;
    height: 120px;
    padding: 0;
    font-size: 64px;
    line-height: 1;
    text-align: center;
}
.threads-koel .form-field--account-emoji input::placeholder { opacity: 1; }

@media (max-width: 760px) {
    /* Three family tiles will not hold their glyph and two lines of hint at
       this width; two will. */
    .threads-koel .koel-product-row,
    .threads-koel .koel-product-row--four { grid-template-columns: repeat(2, 1fr); }
    .threads-koel .koel-group-separator { grid-template-columns: repeat(2, 1fr); }
    .threads-koel .koel-group-separator svg { grid-column: 1 / -1; }

    /* 32px of rail padding each side is what pushed brand and account apart.
       At 14px they sit on one line with room to spare. */
    .threads-koel .main-navigation {
        padding: 10px 14px;
        gap: 10px;
    }

    .threads-koel .navigation-brand { gap: 8px; }
    .threads-koel .navigation-brand__logo { width: 30px; height: 30px; }
    .threads-koel .koel-brand-wordmark { font-size: 17px; }

    /* The account block is the widest thing on the rail; it loses its padding
       rather than any of its parts. */
    .threads-koel .navigation-account { gap: 8px; }
    .threads-koel .navigation-account-preferences { padding: 4px 6px; }
    .threads-koel .navigation-role-picture,
    .threads-koel .navigation-user-emoji { width: 26px; height: 26px; }
    .threads-koel .navigation-account-name { font-size: 12.5px; }
    .threads-koel .navigation-role { font-size: 10.5px; }

    /* Five sections in 347px: the bar loses its rail padding and each entry
       trades side padding and a point and a half of type for the second row
       it was costing. */
    .threads-koel .koel-menubar { padding: 0 6px; }
    .threads-koel .koel-menubar .navigation-menu { gap: 0; }
    .threads-koel .koel-menubar .navigation-link {
        padding: 12px 9px;
        font-size: 13px;
    }

    /* Should a longer section name ever arrive, the bar scrolls sideways by
       itself rather than folding — one row is the point of all of the above.
       The scrollbar is hidden because the row is short enough to swipe. */
    .threads-koel .koel-menubar .navigation-menu {
        flex-wrap: nowrap;
        overflow-x: auto;
        scrollbar-width: none;
    }
    .threads-koel .koel-menubar .navigation-menu::-webkit-scrollbar { display: none; }

    /* The glyph stays put while the sections swipe past under it: an
       auto-margin collapses once the row overflows and the glyph would ride
       off the right edge with the last section, which is the one width where
       it is the only way in. Sticky at the right, on the rail's own colour so
       the section scrolling beneath it never shows through. 44px across
       (18px glyph and 13px each side) is the thumb target.

       The glyph is what tips an administrator's seven entries past 375px,
       and a flex item that may shrink folds "Shirting" onto two lines before
       the row ever overflows — the bar grew from 43px to 54px in the stage 5
       pass. A tab that cannot wrap makes the row overflow instead, which is
       what the sideways scroll above was for. */
    .threads-koel .koel-menubar .koel-family-tab { white-space: nowrap; }
    .threads-koel .koel-menubar .navigation-menu > li:has(> .koel-menubar__search) {
        position: sticky;
        right: 0;
        flex: 0 0 auto;
        background: var(--koel-rail-deep);
    }
    .threads-koel .koel-menubar .koel-menubar__search { padding-inline: 13px; }

    .threads-koel .koel-subbar { padding: 8px 14px; }
    .threads-koel .koel-crumbs { font-size: 12px; }

    /* -- The width the table was missing ---------------------------------- */

    .threads-koel.application-page { padding-inline: 10px; }

    /* The header and footer are full-bleed by recovering the body's gutters:
       `width: calc(100% + 64px)` with a -32px inline margin, sized for the
       desktop gutter. Narrowing the gutter without restating these left both
       rails hanging 22px off each edge of a phone — the menubar's first entry
       fell off the screen. The recovery follows the gutter. */
    .threads-koel > .site-header {
        width: calc(100% + 20px);
        margin-inline: -10px;
    }

    .threads-koel > .site-footer {
        width: calc(100% + 20px);
        margin-inline: -10px;
    }

    .threads-koel .application-footer { padding-inline: 14px; }

    .threads-koel .management-card,
    .threads-koel .ppc-chooser-card,
    .threads-koel .koel-card,
    .threads-koel .dispatch-report-card {
        padding-inline: 12px;
    }

    .threads-koel .koel-page-head { padding-inline: 0; }

    /* 14px of cell padding either side is 28px per column; across the three
       columns a phone keeps, that alone was the last of the sideways scroll.
       The rows stay comfortable — it is the gutters that shrink, not the type
       or the touch targets. */
    .threads-koel .user-table th,
    .threads-koel .user-table td {
        padding: 10px 6px;
    }

    /* Production gives the table `min-width: 100%` here, which stops it
       insisting on the desktop's 840px floor but does not stop it preferring
       a width of its own — it sat at 388px in a 327px wrapper and scrolled.
       Asking for the width outright makes the columns share what there is,
       which is what the `+` disclosure left them for. */
    .threads-koel [data-production-table-standard] .user-table {
        width: 100%;
    }

    .threads-koel .user-table-row-actions { gap: 4px; }

    /* A strip that koel.js has condensed keeps its keys in the document —
       every controller still finds them — and simply stops being drawn. */
    .threads-koel .user-table-lifecycle-tabs.is-condensed {
        display: none;
    }

    .threads-koel .koel-strip-field {
        margin-top: 10px;
    }

    .threads-koel .koel-strip-field + .user-table-lifecycle-tabs,
    .threads-koel .koel-strip-field + .koel-strip-field {
        margin-top: 8px;
    }

    /* -- Dashboards ------------------------------------------------------- */

    /* One figure per row made the Shirts dashboard 2,712px of scrolling for
       twelve numbers. Two-up halves that and still leaves each key a
       comfortable target; the hairline that divided a desktop row has to
       divide a grid, so it runs on two edges rather than one. */
    .threads-koel .koel-stat-strip {
        grid-auto-flow: row;
        grid-template-columns: 1fr 1fr;
        gap: 0;
    }

    .threads-koel .koel-stat {
        padding: 12px 14px;
        border: 0;
        border-top: 1px solid var(--koel-line);
        border-left: 1px solid var(--koel-line);
    }

    /* The top row has nothing above it and the first column nothing beside
       it, so those edges stay clean. */
    .threads-koel .koel-stat:nth-child(-n+2) { border-top: 0; }
    .threads-koel .koel-stat:nth-child(odd) { border-left: 0; }

    .threads-koel .koel-stat + .koel-stat { border-left-width: 1px; }
    .threads-koel .koel-stat:nth-child(odd) + .koel-stat { border-left-width: 1px; }

    /* A donut needs its own width to stay readable; these stay one per row. */
    .threads-koel .koel-split-row { grid-template-columns: 1fr; }

    /* -- Parity: what the phone used to go without ------------------------ */

    /* The production sheet drops row selection and the bulk bar on a phone,
       reasoning that Archive-or-Delete-many is not a thumb job. That was the
       right call while a phone was for "a quick little thing here and there";
       MPF asked for full parity on 28 Aug 2026, so they come back. The width
       they need is taken from the Actions column rather than from the data:
       four controls in a 2×2 grid instead of a wrapped row is 44px narrower,
       which is almost exactly what the selection column costs. */
    .threads-koel .user-table .user-table-selection-cell,
    .threads-koel [data-production-table-selection-bar] {
        display: table-cell;
    }

    .threads-koel [data-production-table-selection-bar] { display: flex; }

    /* A checkbox needs a checkbox's worth of column. Left to the shared cell
       padding it took 83px — a quarter of the table — for a 16px control. */
    .threads-koel .user-table .user-table-selection-cell,
    .threads-koel .user-table .user-table-actions-cell,
    .threads-koel .user-table th:has(> [data-production-table-select-page]) {
        /* `width: 1%` is the shrink-to-fit idiom for an auto-layout table: it
           asks for less than the content needs, so the column settles at its
           minimum and the slack goes to the columns carrying the business
           values. Left to share equally, a 20px checkbox was holding 78px. */
        width: 1%;
        white-space: nowrap;
        padding-inline: 6px;
    }

    .threads-koel .user-table-row-actions {
        display: grid;
        grid-template-columns: repeat(2, auto);
        justify-content: start;
        gap: 4px;
    }

    .threads-koel .user-table-bulk-actions {
        flex-wrap: wrap;
        gap: 6px;
    }

    /* The column filters are moved out of the header row by koel.js rather
       than squeezed into it — six pills as wide as "All Shop" were setting
       the width of the whole table, which is why production hid them here.
       Out of the table they cost nothing and read as an ordinary panel. */
    .threads-koel .koel-filter-tray {
        margin: 10px 0 4px;
        border: 1px solid rgb(255 255 255 / 7%);
        border-radius: var(--koel-radius-sm);
        background: #14161b;
    }

    .threads-koel .koel-filter-tray > summary {
        display: flex;
        align-items: center;
        justify-content: space-between;
        padding: 11px 14px;
        color: var(--koel-muted);
        font-size: 12.5px;
        font-weight: 700;
        letter-spacing: 0.06em;
        text-transform: uppercase;
        cursor: pointer;
    }

    .threads-koel .koel-filter-tray > summary::-webkit-details-marker { display: none; }

    .threads-koel .koel-filter-tray > summary::after {
        content: "";
        width: 8px;
        height: 8px;
        border-right: 2px solid currentColor;
        border-bottom: 2px solid currentColor;
        transform: rotate(45deg) translateY(-2px);
    }

    .threads-koel .koel-filter-tray[open] > summary::after {
        transform: rotate(-135deg) translateY(-2px);
    }

    .threads-koel .koel-filter-tray__list {
        display: grid;
        gap: 8px;
        padding: 0 14px 14px;
    }

    /* In the header a filter is a compact pill sized to its column; here it
       is a field like any other and takes the row. */
    .threads-koel .koel-filter-tray .user-table-filter { width: 100%; }
    .threads-koel .koel-filter-tray .user-table-filter > summary { width: 100%; }
}

/* The tagline is the first thing to go: it names the product on a rail that
   already carries the wordmark, and on a small phone the account block needs
   the room more. */
@media (max-width: 430px) {
    .threads-koel .koel-product-row { grid-template-columns: 1fr; }
    .threads-koel .koel-group-separator { grid-template-columns: 1fr; }

    .threads-koel .koel-brand-tagline { display: none; }
    .threads-koel .koel-menubar .navigation-link { padding: 12px 7px; }
    .threads-koel .koel-menubar .koel-menubar__search { padding-inline: 13px; }
}

/* The Fabric heading, when the manager is scoped to one Fabric: the same
   disabled control as the Add field, shrunk so the heading row keeps its
   height. Only the metrics change — the colours, border and disabled treatment
   are the ordinary ones, which is the point of using the real control. */
.threads-koel .threads-select:has(.fabric-scope-heading-select) .threads-select__trigger {
    padding: 3px 8px;
    min-height: 0;
    font-size: 11px;
    font-weight: 600;
    text-transform: none;
    letter-spacing: normal;
}

.threads-koel .threads-select:has(.fabric-scope-heading-select) {
    max-width: 100%;
}


/* ==========================================================================
   Production Type Mappings
   The panels and the wells were rebuilt on the real ppc-panel-browser and
   filter-well/filter-pill components 4 Sep 2026 — see docs/ui-catalogue.md,
   "Always the house component, never a look-alike" — so Koel already themes
   every one of them and this section no longer needs to. What is left is the
   handful of things neither component is: the assign box's own card chrome
   around its wells, the small actions row beside the Fabrics count, and the
   gap between the page's two stacked cards.
   ========================================================================== */

/* Two `management-page` wrappers, one page: the mapper card above, the filter
   board and table below it — MPF's instruction, 4 September 2026: "put both
   of them in a separate box, which can be wider." The second half of that
   sentence he reversed the same day; both cards are narrow now, see the width
   note below. Koel's own `.management-page` carries no margin of its own
   (right for the first card on a page, which sits flush under the chrome
   above it), so nothing already separated a second one stacked under it. It
   was 26px, the gap `.koel-section` keeps between the dashboard's own stacked
   sections above; MPF asked for "a tad bit more" between these two on 4
   September 2026, so it is 36px. This page is the only one that stacks two
   management pages, so the rule lives here rather than in the shared
   page/card block above. */
.threads-koel .management-page + .management-page {
    margin-top: 36px;
}

/* Why the Mapped Fabrics card is narrow too. It went in at the house
   standard-table width, on MPF's "which can be wider" — and he reversed that
   the same day, 4 September 2026: "Make the Mapped Fabrics section the same
   width as the Production Type Mappings section above." So the page is one
   column of two `management-page--narrow` cards, and the width lives on the
   wrapper as it does everywhere else; there is no rule here to carry it. The
   board's own row wraps its wells at that width (`.filter-board__row` is a
   wrapping flex row in application.css), so nothing needed a narrow variant
   of the shared board. */

/* The running "12 Fabrics" beside the Fabrics heading — quieter than the
   heading itself, a fact about it rather than a second label. Blank while
   the panel is waiting on both Categories, per MPF's third instruction, 4
   Sep 2026 — the script empties the text rather than this rule hiding it. */
.threads-koel .production-type-mapper__count {
    color: var(--koel-muted-soft);
    font-size: 12px;
    font-weight: 600;
}

/* The Fabrics panel header carries its own Reset beside the running count, so
   the pair moves together against the heading rather than the count alone
   floating in the middle once a second control joins it. Return sat here too
   until MPF's sixth instruction, 4 September 2026, retired it — clicking a
   pill already IN a well now unassigns it directly, the job Return used to
   do, so a control that only ever cancelled a pick from the Fabrics panel
   (indistinguishable from clicking that same pill again) had nothing left to
   do. */
.threads-koel .production-type-mapper__panel-actions {
    display: flex;
    align-items: center;
    gap: 10px;
}

/* -- Assign Production Type Mappings --------------------------------------
   The box under the three panels, MPF's own words, 4 Sep 2026: a well per
   active Production Type, Reset and Save Mappings in its footer. The same
   card treatment as a panel above it, wide rather than narrow. */

.threads-koel .production-type-mapper__assign {
    margin-top: 16px;
    border: 1px solid var(--koel-line);
    border-radius: var(--koel-radius);
    background: var(--koel-card);
    box-shadow: var(--koel-shadow-card);
    overflow: hidden;
}

.threads-koel .production-type-mapper__assign-header {
    padding: 12px 14px;
    border-bottom: 1px solid var(--koel-line);
    background: var(--koel-card-quiet);
}

.threads-koel .production-type-mapper__assign-header h2 {
    margin: 0;
    color: var(--koel-muted);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

/* No active Production Type yet — one quiet line and a way to fix it, no
   footer, nothing to Save. */
.threads-koel .production-type-mapper__assign-empty {
    margin: 0;
    padding: 14px;
    color: var(--koel-muted-soft);
    font-size: 12.5px;
}

.threads-koel .production-type-mapper__assign-empty a {
    color: var(--accent-readable);
}

/* One well per active Production Type. The well itself is the house
   filter-well now — this is only the grid that lays several of them out
   side by side, which neither the filter board (a single row of wells) nor
   any other surface already has. Widened and given more headroom, MPF's
   third instruction, 4 September 2026: "increase the size of the wells
   slightly, both width and height."
   MPF's instruction, still 4 September 2026: "centre the wells" — they sat
   flush left because `minmax(300px, 1fr)` stretches every generated track
   to fill the row, and an `auto-fill` track with nothing placed in it still
   reserves its own width rather than getting out of the way, so even
   `justify-content: center` had no slack to work with. Fixed at 300px
   (never 1fr, so a wide box does not stretch a well past its house width)
   and `auto-fit`, which collapses an empty track to nothing — with one or
   two active Production Types the unused tracks vanish and centring has
   something real to centre. The max-width caps the row at exactly three
   wells' worth of that 300px-and-gap math so a very wide box cannot fit a
   fourth column beside them, and the auto margin seats that row in the
   middle of the box. `justify-items: stretch` keeps a well filling the
   full 300px of its own track, same as before. */
.threads-koel .production-type-mapper__wells {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(300px, 300px));
    justify-content: center;
    justify-items: stretch;
    gap: 14px;
    max-width: 956px;
    margin: 0 auto;
    padding: 14px;
}

/* The well's own track floors at 76px board-wide (filter board wells rarely
   need more, a lane's own is-empty default is a single pill). A mapper well
   holds a whole column of pills, so it gets its own, taller floor here
   rather than raising the shared one — MPF's third instruction, 4 September
   2026. Gap and top-alignment are the shared `.filter-well__track`'s own
   now that a well wraps pills the same horizontal way the Fabrics panel
   does (MPF's fifth instruction, still 4 September 2026) — this only adds
   what the shared track does not already have. */
.threads-koel .production-type-mapper__wells .filter-well__track {
    min-height: 168px;
}

/* Reset and Save Mappings, and since 4 September 2026 the line that says what
   a dashed pill means — MPF asked for the dashed look and the sentence goes
   where the pills are, the way the Shirting Cut board's tray carries its own.
   It wraps: at a phone's width the sentence and two buttons do not fit on one
   line, and without this the buttons were squeezed to fit beside it. */
.threads-koel .production-type-mapper__assign-footer {
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: 10px;
    padding: 12px 14px;
    border-top: 1px solid var(--koel-line);
    background: var(--koel-card-quiet);
}

/* Same one-column fold the panels above it take under 920px. */
@media (max-width: 920px) {
    .threads-koel .production-type-mapper__wells {
        grid-template-columns: 1fr;
    }
}
