/* The admin console's stylesheet.
 *
 * LIFTED VERBATIM out of events-management.html, which was 835 KB in one file: 637 KB of inline
 * script, this 119 KB of style and 78 KB of markup. Reading it once cost more than most tools can
 * hold, so every change became grep, read a window, edit, re-read. That is the cost this split
 * exists to remove.
 *
 * A MOVE IS A MOVE. Not one declaration was changed, reordered or tidied on the way out. A refactor
 * hidden inside a move is invisible to review, and this project has already paid for a commit that
 * carried two things.
 *
 * IT IS CACHED BY CONTENT HASH like every other asset here, so changing a rule no longer makes every
 * admin re-download the whole page, and changing the page no longer re-downloads the styles.
 */
/* =====================================================================
   MUSIAC Events Management -- DARK admin console
   Reproduces the approved prototype in docs/admin-spec.html (#proto), kept OUT of public/
   on purpose: it documents the whole permission model and was publicly crawlable (2026-07-30).
   near-black surfaces, amber accents, mono labels, left sidebar + table.
   Palette baked from docs/admin-spec.html's dark-mode override so the console
   is dark by default (not dependent on the OS colour scheme).
   ===================================================================== */
:root {
  --paper:  #0F0C07;   /* app / main background */
  --card:   #17130C;   /* tables, forms, notes, cards */
  --side:   #0A0806;   /* sidebar */
  --line:   #2E2617;   /* structural hairline (was #33291a) */
  --edge:   #2E2617;   /* brand edge token: all borders use this */
  --ink:    #F2EBD9;   /* primary text on dark */
  --bone:   #F2EBD9;   /* body text */
  --dark:   #0D0A06;   /* dark text on amber */
  --field:  #211A10;   /* inputs, chips, buttons */
  --amber:  #F5C518;
  --cobalt: #1B4FCC;
  --cobalt-lt: #7ea0ff;/* readable link on dark */
  --terra:  #C4513A;
  --terra-lt: #f0b6a8; /* rose: failure and warning text on dark */
  --plum:   #7A4FD9;
  --muted:  #a89b7e;
  --ok:     #2f8f52;
  /* The same green, dark enough to carry WHITE TEXT. #2f8f52 against white measures 4.06:1, under
     the 4.5:1 the contrast rule asks for, so anything with a word or a tick printed on it uses this
     instead. #2a7f49 measures 4.53:1. Two tokens rather than one darker green everywhere, because
     the progress bars and borders carry no text and there is no reason to dull them. */
  --ok-ink: #2a7f49;
  /* THE LIGHT FORMS, LIFTED OUT OF 32 BARE LITERALS rather than coined. #8fe3b3 was written out
     18 times and #f0b6a8 14 times, across the bulk screens and the ticketing ones, which is how
     a near miss gets typed in and nobody can tell which of the two was meant. Both are the
     READABLE-ON-DARK form of a token that already exists, the same job --cobalt-lt does for
     --cobalt, so they are named the same way. The hexes are unchanged to the byte: every one of
     the 32 rules resolves to the colour it resolved to before, asserted rule by rule.
     BOTH ROWS ARE NOW IN THE BRAND GUIDE, landed in this same range. This comment said "NEITHER
     HAS A ROW IN THE BRAND GUIDE YET ... the guide rows are owed" and that sentence was false by
     the time anyone read it, which is the ninth false premise found in a comment on this project's
     record. Fixed rather than left, because a comment asserting a debt that has been paid is how
     the debt gets paid twice. */
  --ok-lt:  #8fe3b3;   /* mint: success text and ticks on dark */
  --warn:   #c98a1e;
  /* THE THIRD LIGHT FORM, AND THE ONE THAT WAS MISSING. The console had a readable-on-dark form
     for success and for failure and NONE FOR ATTENTION, so anything meaning "waiting" or "needs a
     look" fell back to --muted, which is also the colour of ordinary secondary text. --warn
     #c98a1e cannot do this job: it measures 1.07:1 against --muted, which is no luminance
     separation at all, and colour alone is a coin flip.
     LIFTED OUT OF FOUR BARE LITERALS rather than coined, the same move the two above it made.
     #F0C878 already shipped on .pill.rev, .pill.unlinked, .lu-unlinked and .sub-type.type-review,
     which are the console's existing "waiting on MUSIAC" objects. The hex is unchanged to the
     byte: every one of those four rules resolves to the colour it resolved to before.
     11.66:1 on --card and 12.30:1 on --paper, and 1.73:1 of separation from --muted, against
     mint's 1.80 and rose's 1.56. */
  --warn-lt: #F0C878;  /* sand: attention and waiting text on dark */
  --disp: 'Archivo Black', 'Arial Black', sans-serif;
  --mono: 'Space Mono', ui-monospace, Menlo, Consolas, monospace;
  --serif: 'Crimson Pro', Georgia, serif;   /* guide: italic, for lead copy only */
  --sans: 'Space Mono', ui-monospace, Menlo, Consolas, monospace; /* brand body face (was banned system-ui) */
}

*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html { max-width: 100%; overflow-x: hidden; }
body {
  background: var(--paper);
  color: var(--bone);
  font-family: var(--sans);
  font-size: 15px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  overflow: hidden;
  height: 100dvh;
}
img, svg { display: block; max-width: 100%; }
a { color: var(--cobalt-lt); text-decoration: none; }
a:hover { text-decoration: underline; }
:focus-visible { outline: 3px solid var(--amber); outline-offset: 2px; }
button { font-family: inherit; }
.hidden { display: none !important; }
[hidden] { display: none !important; }

/* ===== APP SHELL ===== */
.app-shell { display: grid; grid-template-columns: 208px minmax(0, 1fr); height: 100dvh; overflow: hidden; }

/* ===== SIDEBAR ===== */
.side {
  background: var(--side); color: var(--ink);
  padding: 14px 10px; display: flex; flex-direction: column; gap: 2px;
  border-right: 1px solid var(--line);
  height: 100dvh; overflow-y: auto;
}
.brand {
  display: flex; align-items: center; gap: 7px;
  font-family: var(--disp); font-size: 15px; letter-spacing: .01em;
  padding: 4px 8px 12px; color: var(--ink);
}
/* One flex item, so the .brand gap can never fall between the letters of the wordmark. */
.brand .brand-label { display: inline-block; white-space: nowrap; }
.brand .am { color: var(--amber); }
/* MUS-713 F8: 9px to 11px, the floor. Measured in the drawer at 375 and in the 208px rail at
   1280: the longest label, People & trust, measures 85px of ink at 9px and 104px at 11px, inside
   a 187px box in the 208px rail and a 243px box in the 264px drawer. It still sits on one line
   at both widths, the block grows 33px to 36px, and document scrollWidth does not move. */
.navsec {
  font-family: var(--mono); font-size: 11px; letter-spacing: .12em; text-transform: uppercase;
  color: rgba(242,235,217,.42); padding: 14px 10px 5px 8px;
  border-left: 3px solid var(--amber); margin: 8px 0 2px;
}
.navitem {
  display: flex; align-items: center; gap: 9px; width: 100%; text-align: left;
  min-height: 44px; padding: 8px 10px; border: 0; border-radius:0;
  background: none; color: rgba(242,235,217,.82); font-family: var(--sans);
  font-size: 13px; cursor: pointer;
}
.navitem .ni-ic { width: 18px; height: 18px; flex-shrink: 0; display: inline-flex; align-items: center; justify-content: center; }
.navitem .ni-ic svg { width: 18px; height: 18px; display: block; }
.navitem .navitem .ni-badge {
  margin-left: auto; min-width: 20px; height: 20px; padding: 0 5px;
  display: inline-flex; align-items: center; justify-content: center;
  font-family: var(--mono); font-size: 10px; font-weight: 700;
  background: var(--terra); color: #fff; border: 2px solid var(--dark); border-radius: 0;
}
.navitem.on { background: var(--amber); color: var(--dark); font-weight: 700; box-shadow: 3px 3px 0 rgba(0,0,0,.5); }
.navitem.on .ni-badge { background: var(--dark); color: var(--amber); border-color: var(--dark); }
.navitem:not(.on):hover { background: rgba(242,235,217,.09); }
.side-foot { margin-top: auto; padding-top: 14px; }
.role {
  padding: 9px 10px; border: 2px solid var(--edge); border-radius: 0;
  font-family: var(--mono); font-size: 11px; line-height: 1.45; color: rgba(242,235,217,.72);
  word-break: break-word;
}
.role b { color: var(--amber); font-weight: 700; }
.role .role-r { color: rgba(242,235,217,.5); }
.side-foot .btn { margin-top: 10px; width: 100%; }

/* ===== MOBILE TOP BAR (drawer toggle) ===== */
.mtop {
  display: none; align-items: center; gap: 10px;
  position: sticky; top: 0; z-index: 60;
  background: var(--side); border-bottom: 1px solid var(--line);
  padding: 8px 12px;
}
.mtop-burger {
  width: 44px; height: 44px; flex-shrink: 0;
  background: none; border: 2px solid var(--edge); border-radius: 0;
  color: var(--ink); font-size: 20px; line-height: 1; cursor: pointer;
}
.mtop-brand { font-family: var(--disp); font-size: 14px; color: var(--ink); }
.mtop-brand .am { color: var(--amber); }
.mtop .mtop-spacer { flex: 1; }
.side-scrim {
  display: none; position: fixed; inset: 0; z-index: 65;
  background: rgba(5,4,2,.6);
}

/* ===== MAIN ===== */
.main { background: var(--paper); height: 100dvh; display: flex; flex-direction: column; overflow: hidden; min-width: 0; }
.main > section { flex: 1; min-height: 0; min-width: 0; display: flex; flex-direction: column; overflow: hidden; padding: 20px 22px; }
.view { flex: 1; min-height: 0; min-width: 0; display: flex; flex-direction: column; overflow: hidden; }
.main-hd { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 14px; flex: none; }
.main-hd h3 { font-family: var(--disp); font-size: 18px; letter-spacing: -.01em; color: var(--ink); }
.spacer { flex: 1; }

/* ===== BUTTONS ===== */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
  min-height: 44px; padding: 9px 14px;
  font-family: var(--sans); font-size: 13px; font-weight: 700;
  color: var(--bone); background: var(--field);
  border: 2px solid var(--edge); border-radius: 0; cursor: pointer;
  transition: transform .08s, box-shadow .08s; text-decoration: none; white-space: nowrap;
}
.btn:hover { text-decoration: none; }
.btn:active { transform: translateY(1px); }
.btn.pri { background: var(--amber); color: var(--dark); border-color: var(--dark); box-shadow: 4px 4px 0 rgba(0,0,0,.55); }
.btn.pri:hover { box-shadow: 2px 2px 0 rgba(0,0,0,.55); transform: translate(2px,2px); }
.btn.cobalt { background: var(--cobalt); color: #fff; border-color: var(--dark); box-shadow: 4px 4px 0 rgba(0,0,0,.55); }
.btn.cobalt:hover { box-shadow: 2px 2px 0 rgba(0,0,0,.55); transform: translate(2px,2px); }
.btn.danger { background: var(--terra); color: #fff; border-color: var(--dark); box-shadow: 4px 4px 0 rgba(0,0,0,.55); }
.btn.danger:hover { box-shadow: 2px 2px 0 rgba(0,0,0,.55); transform: translate(2px,2px); }
.btn.ghost { background: transparent; color: var(--muted); border-color: var(--edge); box-shadow: none; }
.btn.sm { min-height: 44px; padding: 7px 11px; font-size: 12px; }
.btn[aria-disabled="true"] { opacity: .5; pointer-events: none; }
.btn-block { width: 100%; }

/* ===== SEARCH / INPUTS ===== */
.search {
  flex: 1; min-width: 150px; max-width: 280px; min-height: 44px;
  border: 2px solid var(--line); border-radius:0; padding: 10px 12px;
  font-family: var(--sans); font-size: 13px; background: var(--field); color: var(--bone);
}
.search::placeholder { color: var(--muted); }
.search:focus { outline: none; border-color: var(--amber); }

/* ===== TABLE ===== */
.table-wrap {
  border: 2px solid var(--edge); border-radius: 0; overflow: auto;
  -webkit-overflow-scrolling: touch; background: var(--card);
  flex: 1; min-height: 0;
}
table { width: 100%; min-width: 620px; border-collapse: collapse; font-size: 13px; }
th {
  font-family: var(--mono); font-size: 11px; letter-spacing: .05em; text-transform: uppercase;
  color: var(--muted); text-align: left; padding: 11px 12px;
  border-bottom: 2px solid var(--amber); background: #1C1710; white-space: nowrap; font-weight: 700;
  position: sticky; top: 0; z-index: 2;
}
td { padding: 11px 12px; border-bottom: 1px solid var(--line); vertical-align: middle; color: var(--bone); }
tr:last-child td { border-bottom: 0; }
tbody tr:hover td { background: rgba(245,197,24,.05); }
td b { color: var(--ink); }
.td-mono { font-family: var(--mono); font-size: 12px; }
.td-actions { white-space: nowrap; }
tr.row-clickable { cursor: pointer; }
tr.row-clickable:hover td { background: rgba(245,197,24,.07); }
.a-thumb {
  width: 30px; height: 30px; flex-shrink: 0; border: 2px solid var(--dark); border-radius: 0;
  overflow: hidden; background: var(--cobalt); position: relative; display: inline-block; vertical-align: middle; margin-right: 9px;
}
.a-thumb img { width: 100%; height: 100%; object-fit: cover; }
.a-tile { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; font-family: var(--disp); font-size: 13px; }
.name-cell { display: flex; align-items: center; }
.name-txt { min-width: 0; }
.td-genres { color: var(--muted); }
.td-events { font-family: var(--mono); color: var(--muted); text-align: center; }

/* ===== ROW ACTION LINKS ===== */
.lnk {
  display: inline-flex; align-items: center; min-height: 44px; padding: 0 6px;
  color: var(--cobalt-lt); background: none; border: 0; cursor: pointer;
  font-family: var(--sans); font-weight: 700; font-size: 12.5px;
}
.lnk:hover { text-decoration: underline; }
.lnk.danger { color: var(--terra); }
.lnk-sep { color: var(--line); padding: 0 2px; }

/* ===== STATUS PILLS (square, 2px semantic border, dark tinted fill) ===== */
/* MUS-713 F8: 10px to 11px, the floor. RAISED ON THE SHARED RULE, not on .pill.pub, which is
   the one a gate happened to be looking at. A floor enforced on one modifier while six others
   keep 10px is not a floor, and every modifier here sets colour only. */
.pill {
  display: inline-flex; align-items: center;
  font-family: var(--mono); font-size: 11px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  padding: 2px 7px; border: 2px solid; border-radius: 0; white-space: nowrap;
}
.pill.pub  { background: rgba(47,143,82,.16); color: var(--ok-lt); border-color: var(--ok); }
.pill.rev  { background: rgba(201,138,30,.16); color: var(--warn-lt); border-color: var(--warn); }
.pill.draft{ background: var(--field); color: var(--muted); border-color: var(--edge); }
.pill.p2   { background: rgba(27,79,204,.18); color: var(--cobalt-lt); border-color: var(--cobalt); }
.pill.p3   { background: rgba(122,79,217,.18); color: #b79be6; border-color: var(--plum); }
.pill.verified { background: rgba(27,79,204,.18); color: var(--cobalt-lt); border-color: var(--cobalt); }
.pill.hl   { background: var(--amber); color: var(--dark); border-color: var(--dark); }
/* MUS-406: an act named in the blurb that is not linked to an artist profile. */
/* 11px, not the 10px the other pills inherit. A mobile gate measured this new label at 10px with
   0.04em tracking, and this project has already settled that exact case: a tracked uppercase label
   only earns the smaller tier at 0.12em, and four labels carrying 0.03em to 0.042em were found to
   have simply been left too small and were raised to 11px. This one carries 0.04em, so it does not
   qualify either, and it is new in this change. It also sits directly above its own reason line,
   which is 11px, so the row reads as one thing.
   KNOWN AND NOT FIXED HERE: the base .pill is still 10px, so Draft and Headliner beside this one
   are a pixel smaller. Raising the base touches every screen in the console and is its own change,
   not a thing to slip into an identity sweep. */
.pill.unlinked { background: rgba(201,138,30,.16); color: var(--warn-lt); border-color: var(--warn); font-size: 11px; }

/* ===== ROLES & CAPABILITIES MATRIX (MUS-130, read-only) ===== */
.cap-legend {
  flex: none; display: flex; flex-wrap: wrap; gap: 8px 14px;
  margin-bottom: 12px; font-size: 12px;
}
.cap-legend .cap-leg {
  display: inline-flex; align-items: center; gap: 7px;
  color: var(--muted); line-height: 1.35;
}
.cap-legend .captok { flex-shrink: 0; }
/* token chips */
.captok {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 30px; padding: 2px 8px; border-radius: 0;
  font-family: var(--mono); font-size: 11px; font-weight: 700;
  letter-spacing: .02em; text-transform: none; border: 1px solid var(--line);
}
.captok.t-yes  { background: #17331f; color: #63c281; border-color: #2f6a42; }
.captok.t-no   { background: transparent; color: var(--muted); border-color: transparent; font-weight: 400; }
.captok.t-own  { background: #2a2410; color: var(--amber); border-color: #5a4a19; }
.captok.t-own_submit { background: #241d0f; color: var(--warn); border-color: #5a4a19; }
.captok.t-all  { background: #101f3d; color: var(--cobalt-lt); border-color: #294a8f; }
.captok.t-na   { background: transparent; color: rgba(168,155,126,.6); border-color: transparent; font-weight: 400; }
.captok.t-sql  { background: #201434; color: #b79be6; border-color: #4a367a; }

.cap-table { min-width: 760px; font-size: 12px; }
.cap-table th, .cap-table td {
  text-align: center; padding: 10px 10px; white-space: nowrap;
}
.cap-table thead th { top: 0; z-index: 3; }
/* first (capability) column: sticky-left so the row is identifiable while
   the role columns scroll horizontally on narrow screens. */
.cap-table th.cap-rowh, .cap-table td.cap-rowh {
  position: sticky; left: 0; top: auto; z-index: 2;
  text-align: left; white-space: normal; min-width: 180px; max-width: 240px;
  background: var(--card); border-right: 1px solid var(--line);
}
.cap-table thead th.cap-rowh { top: 0; z-index: 4; background: #1C1710; }
/* Body row headers render as <th class="cap-rowh" scope="row">, so these must
   target th (and td) inside tbody, not td alone, or label+help never stack. */
.cap-table tbody .cap-rowh { line-height: 1.35; }
.cap-table tbody .cap-rowh .cap-label { display: block; color: var(--ink); font-weight: 700; }
.cap-table tbody .cap-rowh .cap-help {
  display: block; margin-top: 3px; font-size: 11px; color: var(--muted);
  font-weight: 400; white-space: normal;
}
.cap-table tbody tr:hover td { background: rgba(242,235,217,.04); }
.cap-table tbody tr:hover .cap-rowh { background: #1C1710; }

/* ===== NOTE ===== */
.note {
  background: #241d0f; border: 2px solid var(--edge); border-left: 4px solid var(--amber); border-radius: 0;
  padding: 12px 14px; font-size: 13px; line-height: 1.5; color: var(--bone); margin-top: 14px;
}
.note b { color: var(--amber); }
.note.warn { border-color: var(--edge); border-left-color: var(--terra); }
.note.warn b { color: var(--terra); }
.note + .note { margin-top: 10px; }

/* Inline help dot. Replaces a standing block of explanatory text under the table: the
   explanation is only wanted at the moment someone asks, so it hangs off the thing it
   explains instead of taxing every read of the page. Hover and keyboard focus both open it.

   TOKEN TRAP: this console is a DARK theme and its --ink is #F2EBD9, the primary text colour,
   the opposite of --ink in mapview/the brand guide where it is the near-black #0D0A06. Using
   var(--ink) here for a background produced cream-on-cream and an invisible tooltip. Surfaces
   in this file come from --card / --paper / --dark; --ink and --bone are FOREGROUND only. */
.th-help { display: inline-flex; align-items: center; gap: 6px; }
.helpdot { width: 16px; height: 16px; flex-shrink: 0; padding: 0; cursor: help;
  display: inline-flex; align-items: center; justify-content: center;
  font-family: inherit; font-size: 10px; font-weight: 700; line-height: 1;
  color: var(--dark); background: var(--amber);
  border: 1.5px solid var(--amber); border-radius: 0; position: relative; }
.helpdot:focus-visible { outline: 2px solid var(--amber); outline-offset: 2px; }
.helpdot::after {
  content: attr(data-help); position: absolute; top: calc(100% + 8px); left: 0;
  width: 264px; padding: 10px 12px; z-index: 40;
  background: var(--side); color: var(--bone); border: 2px solid var(--amber);
  font-family: var(--mono); font-size: 11px; font-weight: 400; line-height: 1.55;
  letter-spacing: 0; text-align: left; text-transform: none; white-space: normal;
  box-shadow: 4px 4px 0 rgba(0,0,0,.5);
  opacity: 0; visibility: hidden; transition: opacity .12s ease; pointer-events: none; }
.helpdot:hover::after, .helpdot:focus-visible::after { opacity: 1; visibility: visible; }
/* Near the right edge the panel would overflow the table, so flip it to hang leftwards. */
th:last-of-type .helpdot::after, td:last-child .helpdot::after { left: auto; right: 0; }
@media (max-width: 700px) { .helpdot::after { width: 210px; } }

/* MUS-267 handle field. The prefix sits ABOVE the input at narrow widths: inline it eats over
   half the row and the editable part ends up a few characters wide. */
.handle-row { display: flex; align-items: stretch; gap: 0; flex-wrap: wrap; }
.handle-prefix { display: inline-flex; align-items: center; padding: 0 10px; font-family: var(--mono);
  font-size: 12px; color: var(--muted); background: var(--field); border: 1px solid var(--edge);
  border-right: none; white-space: nowrap; }
.handle-row input { flex: 1 1 180px; min-width: 0; }
.handle-state { margin: 6px 0 0; font-family: var(--mono); font-size: 11px; line-height: 1.5; color: var(--muted); }
.handle-state.ok { color: var(--ok); }
.handle-state.err { color: var(--terra); }
.handle-suggest { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.handle-suggest button { font-family: var(--mono); font-size: 11px; padding: 5px 9px; cursor: pointer;
  background: var(--field); color: var(--ink); border: 1px solid var(--edge); border-radius: 0; }
.handle-suggest button:hover { background: var(--amber); color: var(--dark); border-color: var(--amber); }
@media (max-width: 560px) {
  .handle-prefix { border-right: 1px solid var(--edge); border-bottom: none; flex-basis: 100%; }
  .handle-row input { flex-basis: 100%; }
}

/* MUS-140 restored-draft banner (top of an editor whose in-progress state was
   rebuilt from the session). Subtle, non-blocking, dismiss via Discard draft. */
.draft-note {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  background: #241d0f; border: 2px solid var(--edge); border-left: 4px solid var(--amber);
  border-radius: 0; padding: 10px 12px; margin-bottom: 14px;
}
.draft-note[hidden] { display: none; }
.draft-note .draft-note-txt { flex: 1; min-width: 160px; font-size: 12.5px; line-height: 1.4; color: var(--bone); }
.draft-note .draft-note-txt b { color: var(--amber); }
.draft-note .draft-note-x { flex: none; }

/* ===== PHASE-PREVIEW MINI CARD ===== */
.mini {
  background: var(--card); border: 2px solid var(--edge); border-radius: 0;
  padding: 16px 18px; max-width: 640px;
}
.mini h4 { font-family: var(--mono); font-size: 11px; letter-spacing: .06em; text-transform: uppercase; color: var(--amber); margin-bottom: 8px; }
.mini p { font-size: 13.5px; color: var(--bone); margin: 0; line-height: 1.55; }
.mini p + p { margin-top: 8px; }

/* ===== EDITOR FORM ===== */
.form {
  background: var(--card); border: 2px solid var(--edge); border-radius: 0; padding: 18px;
  display: grid; grid-template-columns: 1fr; gap: 13px 16px;
}
@media (min-width: 680px) { .form { grid-template-columns: 1fr 1fr; } .form .full { grid-column: 1 / -1; } }
/* The artist editor's first row holds name, type and home country side by side. */
.form .row3 { display: grid; grid-template-columns: 1fr; gap: 13px 16px; }
@media (min-width: 680px) { .form .row3 { grid-template-columns: minmax(0, 2fr) minmax(0, 1.3fr) minmax(0, 1.3fr); } }
.form .row3 select { height: 44px; }
/* MUS-713 F7: 11px IS THE FLOOR AND THIS RULE WAS THE LAST 10px LEFT UNDER IT. It reached
   Capacity, Currency and Your booking terms, and every other .fld label in the console with
   them, which is why the fix is here rather than three overrides deep. #section-promotions
   had already made exactly this correction for its own fields; that override now sets the
   same 11px this line does and is left in place rather than deleted, because it is not this
   ticket's to remove and it cannot drift while the two values agree. */
.fld label, .fld-label { display: block; font-family: var(--mono); font-size: 11px; letter-spacing: .05em; text-transform: uppercase; color: var(--muted); margin-bottom: 5px; }
/* MOBILE F1. THE GRID ITEM IS WHAT OVERFLOWS, NOT THE CHIP INSIDE IT.
   A grid item defaults to min-width:auto, which floors its track at the item's min-content width.
   One long booking-alert address therefore widened the Terms pane to 606px against a 375 viewport
   with nothing scrollable between the chip and the edge, putting the label, the input and the Add
   button off screen with no way to reach them.
   min-width:0 on .tk-chiprow and .tk-chip was the previous attempt. Both compute 0px, both are
   INSIDE the item, and neither can reach the track: measured inert, removing them moved nothing.
   They stay because they are what makes the chip ellipsise; this is the same rule applied one
   level up, where the overflow actually is. */
.form > .fld { min-width: 0; }

.req { color: var(--terra); }
/* #tk-door-input JOINS THIS SELECTOR rather than getting its own copy. It is the one console
   text input that does not sit inside a .fld, so this rule never reached it and it rendered 19px
   tall in Arial on grey beside its twin at 44px in Space Mono. A copied block would be a second
   answer to how a console input looks, and the two would drift. */
.fld input, .fld select, .fld textarea, #tk-door-input {
  width: 100%; min-height: 44px; padding: 10px 12px;
  border: 2px solid var(--line); border-radius:0;
  background: var(--field); color: var(--bone); font-family: var(--sans); font-size: 13px;
}
.fld input::placeholder, .fld textarea::placeholder { color: var(--muted); }
.fld textarea { min-height: 90px; resize: vertical; line-height: 1.5; }
/* MUS-816. #tk-door-input JOINS THIS ONE TOO, AND ITS ABSENCE HERE WAS THE WHOLE DEFECT. The CEO
   reported the door invite field's focus ring as "partnerlly hidden", with a screenshot showing it
   cut along the top and running hard into the Invite button beside it.
   IT WAS ADDED TO THE SIZING SELECTOR ABOVE AND NOT TO THIS ONE, so the sizing half of the problem
   was fixed and the focus half was left, and nothing pointed at the gap because the field looks
   correct until somebody focuses it. With no rule of its own it fell through to the global
   :focus-visible near the top of this file, which paints 3px of outline 2px OUTSIDE the border
   box: five pixels of ring on every side, on the only console input that gets an outline at all.
   Two things then clipped it, AND BOTH ARE DESKTOP WIDTHS, which is worth stating precisely
   because the first diagnosis of this said they were general and they are not. .editor-pane is
   overflow-y:auto, and an outline drawn outside the border box is outside the scroll box too, so
   the container clips it vertically; the 720px block below sets that pane to overflow:visible, so
   at 375 the page scrolls instead and the pane clips nothing. And .tk-addrow is a flex row with an
   8px gap, so the ring ate 5 of the 8 and left 3px to the Invite button; the same 720px block
   wraps that row, so on a phone the button sits underneath and there is no horizontal collision to
   have. The screenshot was a desktop one and the fix is width-independent, which is the point:
   the indicator is inside the box now at every width.
   A BORDER IS DRAWN INSIDE THE ELEMENT'S BOX AND CAN NEVER BE CLIPPED BY AN ANCESTOR, which is why
   this is the fix rather than a weakening of it. The outline:none here removes nothing: it swaps a
   partly painted ring for the amber border every other input in this console already uses, so the
   indicator becomes MORE visible, not less.
   DO NOT instead delete the global :focus-visible rule, widen the flex gap, or lift the overflow on
   .editor-pane. The first breaks focus everywhere, the second treats a symptom, and the third is
   load bearing for the console's own scrolling.
   AND THE STANDING CHECK THIS LEAVES BEHIND: anything added to the sizing selector above without
   being added here has the identical defect waiting. Both selectors are asserted as a PAIR in
   tests/ticketing-screen.test.js, so an id can no longer join one and miss the other. */
.fld input:focus, .fld select:focus, .fld textarea:focus, #tk-door-input:focus { outline: none; border-color: var(--amber); }
.hint { font-size: 12.5px; color: var(--muted); margin-top: 6px; line-height: 1.45; }

/* ===== GENRE CHIPS ===== */
.chips { display: flex; flex-wrap: wrap; gap: 6px; }
.chip {
  font-family: var(--mono); font-size: 11px;
  border: 2px solid var(--line); background: var(--field); color: var(--bone);
  padding: 8px 11px; min-height: 44px; border-radius:0; cursor: pointer;
  display: inline-flex; align-items: center;
}
.chip[aria-pressed="true"] { background: var(--amber); color: var(--dark); border-color: var(--dark); }

/* ===== RIGHTS NOTE (dashed) ===== */
.rights-note {
  background: #2a2010; border: 2px solid var(--edge); border-left: 4px solid var(--terra); border-radius: 0;
  padding: 10px 12px; font-size: 12.5px; line-height: 1.5; color: var(--bone);
}
.rights-note b { font-family: var(--mono); font-size: 10px; letter-spacing: .05em; text-transform: uppercase; color: var(--terra); display: block; margin-bottom: 4px; }

/* ===== AVATAR + GALLERY ===== */
.media-row { display: flex; gap: 12px; align-items: flex-start; flex-wrap: wrap; }
.media-preview { width: 92px; height: 92px; flex-shrink: 0; border: 2px solid var(--dark); border-radius: 0; overflow: hidden; position: relative; background: var(--cobalt); }
.media-preview img { width: 100%; height: 100%; object-fit: cover; }
.cubist-tile { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; text-align: center; padding: 6px; }
.cubist-tile span { font-family: var(--disp); font-size: 12px; line-height: 1.05; text-shadow: 2px 2px 0 rgba(13,10,6,.4); word-break: break-word; }
.add-url { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 8px; }
.add-url input { flex: 1; min-width: 160px; }
.gallery-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(92px, 1fr)); gap: 10px; margin-top: 12px; }
.gcell { border: 2px solid var(--dark); border-radius: 0; position: relative; aspect-ratio: 1; overflow: hidden; background: var(--field); }
.gcell img { width: 100%; height: 100%; object-fit: cover; }
.gcell.is-banner { outline: 3px solid var(--amber); outline-offset: -3px; }
.gcell .g-x { position: absolute; top: 0; right: 0; width: 28px; height: 28px; background: var(--terra); color: #fff; border: 0; font-family: var(--mono); font-size: 14px; line-height: 1; cursor: pointer; border-bottom-left-radius:0; }
.gcell .g-banner { position: absolute; bottom: 0; left: 0; right: 0; min-height: 30px; display: flex; align-items: center; justify-content: center; gap: 5px; background: rgba(13,10,6,.82); color: var(--bone); border: 0; font-family: var(--mono); font-size: 9px; letter-spacing: .04em; text-transform: uppercase; cursor: pointer; padding: 6px 4px; }
.gcell.is-banner .g-banner { background: var(--amber); color: var(--dark); }
.gallery-empty { font-size: 12.5px; color: var(--muted); margin-top: 10px; }

/* ===== BANNER FOCAL POINT (MUS-160) ===== */
.focal-panel { margin-top: 14px; border: 2px solid var(--dark); background: var(--field); padding: 12px; }
.focal-panel.hidden { display: none; }
.focal-hint { margin-top: 4px; margin-bottom: 10px; }
.focal-stage { position: relative; width: 100%; background: #000; border: 2px solid var(--dark); display: flex; align-items: center; justify-content: center; overflow: hidden; touch-action: none; min-height: 120px; max-height: 46vh; cursor: crosshair; }
.focal-img { display: block; width: auto; max-width: 100%; max-height: 46vh; -webkit-user-drag: none; user-select: none; pointer-events: none; }
.focal-puck { position: absolute; left: 50%; top: 38%; width: 44px; height: 44px; margin: -22px 0 0 -22px; padding: 0; background: rgba(242,235,217,.92); border: 2.5px solid var(--dark); box-shadow: 3px 3px 0 var(--dark); cursor: grab; display: flex; align-items: center; justify-content: center; touch-action: none; }
.focal-puck::before, .focal-puck::after { content: ''; position: absolute; background: var(--dark); }
.focal-puck::before { left: 50%; top: 6px; bottom: 6px; width: 2px; margin-left: -1px; }
.focal-puck::after { top: 50%; left: 6px; right: 6px; height: 2px; margin-top: -1px; }
.focal-puck-dot { position: relative; width: 10px; height: 10px; background: var(--amber); border: 1.5px solid var(--dark); z-index: 1; }
.focal-puck:hover, .focal-puck:active { background: var(--amber); }
.focal-puck:focus-visible { outline: none; box-shadow: 3px 3px 0 var(--dark), 0 0 0 3px var(--cobalt); background: var(--amber); }
.focal-bar { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; justify-content: space-between; margin-top: 10px; }
.focal-readout { display: flex; flex-direction: column; gap: 3px; }
/* MUS-713 F8: 10px to 11px, the floor. The readout is a flex column beside a Reset button in a
   wrapping .focal-bar, so a wider label wraps rather than pushing anything off screen. */
.focal-rd-label { font-family: var(--mono); font-size: 11px; letter-spacing: .06em; text-transform: uppercase; color: var(--muted); }
.focal-rd-val { font-family: var(--mono); font-size: 13px; letter-spacing: .06em; color: var(--ink); }
.focal-reset { flex: 0 0 auto; }
.focal-previews { display: grid; grid-template-columns: 1fr; gap: 10px; margin-top: 12px; }
.focal-fig { margin: 0; min-width: 0; }
.focal-cap { font-family: var(--mono); font-size: 10px; letter-spacing: .05em; text-transform: uppercase; color: var(--muted); margin-bottom: 4px; }
.focal-crop { width: 100%; border: 2px solid var(--dark); overflow: hidden; background: #000; }
.focal-crop img { width: 100%; height: 100%; object-fit: cover; display: block; }
.focal-crop-phone { aspect-ratio: 5 / 3; }
.focal-crop-desk { aspect-ratio: 11 / 4; }
/* MUS-168: avatar crop shapes (profile picture is shown as a circle or square) */
.focal-crop-circle { aspect-ratio: 1 / 1; border-radius: 50%; }
.focal-crop-square { aspect-ratio: 1 / 1; }
/* MUS-167: zoom slider (banner + avatar). Touch target is the full 44px row height;
   the thumb is a cut-stone amber block on an ink track. */
.focal-zoom { display: flex; flex-direction: column; gap: 4px; margin-top: 12px; }
.focal-zoom-top { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }
.focal-zoom-range { -webkit-appearance: none; appearance: none; width: 100%; height: 44px; margin: 0; background: transparent; cursor: pointer; touch-action: none; }
.focal-zoom-range:focus-visible { outline: none; }
.focal-zoom-range::-webkit-slider-runnable-track { height: 4px; background: var(--dark); }
.focal-zoom-range::-moz-range-track { height: 4px; background: var(--dark); }
.focal-zoom-range::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; width: 26px; height: 26px; margin-top: -11px; background: var(--amber); border: 2.5px solid var(--dark); box-shadow: 2px 2px 0 var(--dark); border-radius: 0; }
.focal-zoom-range::-moz-range-thumb { width: 26px; height: 26px; background: var(--amber); border: 2.5px solid var(--dark); box-shadow: 2px 2px 0 var(--dark); border-radius: 0; }
.focal-zoom-range:focus-visible::-webkit-slider-thumb { box-shadow: 2px 2px 0 var(--dark), 0 0 0 3px var(--cobalt); }
.focal-zoom-range:focus-visible::-moz-range-thumb { box-shadow: 2px 2px 0 var(--dark), 0 0 0 3px var(--cobalt); }
@media (min-width: 620px) { .focal-previews { grid-template-columns: 1fr 1fr; } }

/* ===== VERIFIED TOGGLE ===== */
.toggle-row { display: flex; align-items: center; gap: 10px; min-height: 44px; }
.toggle-row input[type="checkbox"] { width: 22px; height: 22px; flex-shrink: 0; accent-color: var(--amber); cursor: pointer; }
.toggle-row label { font-family: var(--sans); font-size: 13px; color: var(--bone); cursor: pointer; }

/* ===== FEATURED MARK (MUS-344) =====
   The tickbox and its position box are gone with MUS-412: featuring is the Promotions section now,
   where the order is a list rather than a typed number. Their rules went with them. What is left is
   the mark on the events list. */
/* The list mark. Amber on ink, the brand's loudest pair, because it has to be findable while
   scrolling a list of events that all look alike. */
/* 11px for the same stated reason as .pill.unlinked above, and it was missed here: the smaller
   tier is only open to uppercase labels tracked at 0.12em, and .pill carries 0.04em, so this one
   does not qualify either. Two pills added by one change should not arrive at two sizes. */
.pill.feat { background: var(--amber); color: var(--dark); border-color: var(--dark); margin-left: 6px; font-size: 11px; }

/* ===== CO-MANAGERS (folded into artist editor) ===== */
.cm-list { display: flex; flex-direction: column; gap: 8px; margin-top: 10px; }
.cm-row { display: flex; align-items: center; gap: 10px; background: var(--field); border: 1px solid var(--line); border-radius:0; padding: 9px 12px; }
.cm-main { flex: 1; min-width: 0; }
.cm-email { display: block; font-family: var(--mono); font-size: 12.5px; color: var(--ink); word-break: break-word; }
.cm-sub { display: block; font-family: var(--mono); font-size: 10px; color: var(--muted); margin-top: 2px; }
.cm-state { font-size: 12.5px; color: var(--muted); font-style: italic; padding: 8px 0; }
/* Line-up rows with an explicit running number, reorder controls and headliner tag (MUS-96) */
.lu-row { flex-wrap: wrap; }
.lu-num { flex-shrink: 0; width: 30px; height: 30px; display: flex; align-items: center; justify-content: center;
  background: var(--amber); color: var(--dark); border-radius:0; font-family: var(--mono); font-weight: 700; font-size: 13px; }
.lu-ctl { display: flex; flex-wrap: wrap; gap: 6px; width: 100%; }
.lu-move { min-width: 54px; }
/* MUS-406: the unlinked line-up row. 11px rather than the 10px sub-line because this one
   is a sentence an admin has to act on, not a label. */
.lu-row.lu-unres { border-left: 3px solid var(--warn); padding-left: 10px; }  /* 3 + 10 keeps the content on the same line as an ordinary 1 + 12 row */
.lu-unlinked { color: var(--warn-lt); font-size: 11px; }

/* ===== IMAGE UPLOAD CONTROLS (alongside URL inputs) ===== */
.upload-row { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; margin-top: 8px; }
.upload-state { font-family: var(--mono); font-size: 11px; line-height: 1.4; color: var(--muted); flex: 1; min-width: 120px; }
.upload-state.err { color: var(--terra); }
.upload-state.ok { color: #7fce9a; }

/* ===== CO-MANAGER USER SEARCH ===== */
.cm-search { position: relative; }
.cm-results {
  position: absolute; z-index: 30; left: 0; right: 0; top: calc(100% + 4px);
  background: var(--card); border: 2px solid var(--line); border-radius:0;
  overflow-y: auto; max-height: 260px; box-shadow: 0 10px 30px rgba(0,0,0,.55);
}
.cm-result {
  display: block; width: 100%; text-align: left; background: none;
  border: 0; border-bottom: 1px solid var(--line); color: var(--bone);
  padding: 10px 12px; min-height: 44px; cursor: pointer; font-family: var(--sans);
}
.cm-result:last-child { border-bottom: 0; }
.cm-result:hover, .cm-result:focus-visible { background: var(--field); }
.cm-result .r-name { display: block; font-size: 13px; color: var(--ink); }
.cm-result .r-email { display: block; font-family: var(--mono); font-size: 11px; color: var(--muted); margin-top: 2px; word-break: break-word; }
.cm-result[aria-disabled="true"] { opacity: .5; cursor: default; }
.cm-result .r-added { font-family: var(--mono); font-size: 9px; letter-spacing: .04em; text-transform: uppercase; color: var(--warn); margin-left: 6px; }
.cm-empty { padding: 11px 12px; font-family: var(--mono); font-size: 11px; color: var(--muted); }
.cm-selected { display: flex; align-items: center; gap: 10px; background: var(--field); border: 2px solid var(--cobalt); border-radius:0; padding: 9px 12px; margin-top: 8px; }
.cm-selected .s-main { flex: 1; min-width: 0; }
.cm-selected .cm-email, .cm-selected .cm-sub { display: block; }
.cm-selected .s-clear { flex-shrink: 0; }

/* editor forms + queue scroll within their view; header stays fixed */
/* Artist, event and venue editors all scroll via their tab pane (see
   .editor-pane), not the form. The queue keeps its own scroller. */
#queue-list { flex: 1; min-height: 0; overflow-y: auto; }
/* The list status line is a plain message row (its .form-msg class carries a
   grow:1 rule for the editor footer); as a direct child of the list view it must
   NOT grow, or it starves the table of height. */
.view > .form-msg { flex: none; }
/* ===== FORM ACTIONS (sticky thumb zone) ===== */
.form-foot {
  grid-column: 1 / -1; display: flex; flex-direction: column; gap: 10px;
  border-top: 1px solid var(--line); padding-top: 14px; margin-top: 4px;
  position: sticky; bottom: 0; z-index: 30; background: var(--paper);
  padding-bottom: 10px; margin-left: -4px; margin-right: -4px; padding-left: 4px; padding-right: 4px;
}
/* Action row: Delete (destructive) on the left, Cancel + Save grouped on the
   right. Wraps cleanly at narrow widths instead of overflowing. */
/* The status line sits on its own row above the action bar. In this column-flow
   footer it must keep its natural height (not grow), while still spanning the
   full width via the column's default cross-axis stretch. */
.form-foot > .form-msg { flex: 0 0 auto; }
.form-foot-bar { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; }
.form-foot-actions { display: flex; align-items: center; gap: 10px; }
/* Mobile-first: Delete and the Cancel+Save group each take a full row and their
   buttons stretch for comfortable 44px thumb targets. */
.form-foot-bar > .btn.danger { flex: 1 1 100%; min-width: 120px; }
.form-foot-actions { flex: 1 1 100%; }
.form-foot-actions .btn { flex: 1; min-width: 120px; }
/* Desktop: single row, Delete on the left, Cancel + Save grouped on the right. */
@media (min-width: 680px) {
  .form-foot-bar > .btn.danger { flex: 0 0 auto; }
  .form-foot-actions { flex: 0 0 auto; margin-left: auto; }
  .form-foot-actions .btn { flex: 0 0 auto; }
}
.form-msg { flex: 1 1 100%; font-family: var(--mono); font-size: 12px; line-height: 1.5; color: var(--muted); }
.form-msg.err { color: var(--terra); }
.form-msg.ok { color: #7fce9a; }
/* A SAVE THAT PARTLY FAILED IS NOT A SUCCESS MESSAGE WITH A TAIL.
   The event editor used to append its warnings to the end of the success sentence and paint the
   whole line with .ok, so "the ones that were there have been removed" arrived in green, as the
   second half of a sentence beginning "Saved." A person who reads the first two words and stops
   was told nothing. The success line keeps the green it earned; anything that failed goes on its
   own line underneath.
   TERRACOTTA, WHICH IS THIS CONSOLE'S FAILURE COLOUR (.form-msg.err, above). Every one of these
   lines reports a part of the save that did not happen, which is the same fact an error reports,
   and the person has to go and do something about it. Amber was the other candidate and is wrong
   here: .form-msg.warn is used once in this file, to caution somebody about a consequence BEFORE
   they act, so its established meaning is "know this first" rather than "this did not work".
   THE RULE DOES NOT REST ON HUE. A 2px rule down the left edge carries the same message with the
   colour ignored, which matters for anyone who cannot separate the two reds from the green.
   The two state names are .ok and .err on purpose, the same pair .form-msg itself uses two lines
   above, so there is one failure vocabulary on this screen rather than a second one invented here. */
.form-msg .msg-line { display: block; }
.form-msg .msg-line + .msg-line { margin-top: 7px; }
.form-msg .msg-line.ok { color: #7fce9a; }
.form-msg .msg-line.err { color: var(--terra); border-left: 2px solid var(--terra); padding-left: 9px; }

/* ===== ARTIST / EVENT / VENUE EDITORS: tab strip, scrolling panes, pinned action bar, view mode =====
   One shared style set drives all three editors. Each editor root carries the
   .editor-view class plus mode-view / mode-edit; the markup ids differ but the
   classes and behaviour are identical. ===== */
/* Tab strip: clean, extensible; mono labels + amber accent + hard edges. */
.editor-tabs { display: flex; gap: 0; flex: none; border-bottom: 2px solid var(--edge); margin-bottom: 14px; overflow-x: auto; -webkit-overflow-scrolling: touch; }
.editor-tab {
  font-family: var(--mono); font-size: 11px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  background: transparent; color: var(--muted); border: 0; border-bottom: 2px solid transparent;
  padding: 12px 16px; min-height: 44px; margin-bottom: -2px; cursor: pointer; white-space: nowrap;
}
.editor-tab:hover { color: var(--ink); }
.editor-tab.is-active { color: var(--amber); border-bottom-color: var(--amber); }
.editor-tab:focus-visible { outline: 2px solid var(--amber); outline-offset: -2px; }
/* MUS-749. LIVE SITS BESIDE TICKETING RATHER THAN REPLACING IT, so Ticketing needs a sign that
   its work is done or it reads as unfinished work. A check rather than a colour change, because
   colour alone is not a difference everybody can see. */
.editor-tab .tab-tick { color: var(--ok-lt); margin-right: 5px; }
.editor-tab .tab-tick[hidden] { display: none; }
/* MUS-815. THE TICKETING TAB CARRIES THE SELLING STATE, on the CEO's instruction: "i want the
   TICKETING tab to go GREEN with a TICK as it is now and in backets say LIVE".

   THE COLOUR IS NOT THE CARRIER AND HERE IS THE NUMBER. Computed from this file's own :root by
   the WCAG relative luminance definition, --ok-lt #8fe3b3 is 0.6403 and --amber #F5C518 is
   0.5942, which is 1.07:1 between them. That is the identical figure the --warn-lt comment in
   this file already uses to REJECT a colour, "no luminance separation at all, and colour alone
   is a coin flip", and green against amber is the classic confusion pair on top of it. So the
   WORD and the TICK carry the two facts and the mint is confirmation for anyone who can use it.
   Mint on this console's ground is fine on its own terms: #8fe3b3 on --paper #0F0C07 is 12.8:1.

   :not(.is-active) IS LOAD BEARING AND IS NOT A STYLE PREFERENCE. .editor-tab.is-active above and
   .editor-tab.is-live are both specificity 0,2,0, so without the :not the winner would be decided
   by source order alone. This file has already paid twice for that exact trap, written up at
   length above .tk-fig button.v: a declaration that parses, validates, reviews clean and paints
   nothing. Written as :not, the outcome is stated rather than inherited from line numbers.

   THE ACTIVE TAB'S 2px AMBER BOTTOM BORDER IS WHAT KEEPS A MINT TAB FROM READING AS A SECOND
   ACTIVE TAB. It is load bearing now. Do not remove it, do not give the live state a border of
   its own, and do not give any other tab state a colour.

   NO HOVER RULE IS ADDED AND THAT IS DELIBERATE. .editor-tab:hover is 0,1,1 and loses to the rule
   below at 0,3,0, so a live tab does not change colour on hover. Hover is a pointer nicety that
   does not exist at 375px; this is a state. Do not add an override to "fix" it.

   NOTHING ANIMATES. The CEO asked "or maybe even pulse a bit" and the Design Lead ruled against
   it: the only moment a pulse could mark is the state CHANGING, and the person is never looking
   at the strip when it does, because switching on lands them on the overview 40px below. A pulse
   on a resting tab marks a state that lasts for weeks, which is decoration. */
.editor-tab .tab-live { color: var(--ok-lt); }
.editor-tab .tab-live[hidden] { display: none; }
.editor-tab.is-live:not(.is-active) { color: var(--ok-lt); }
/* THE STRIP HAS THREE WIDTHS RATHER THAN ONE, AND EVERY NUMBER BELOW IS STATED WITH THE STATE IT
   BELONGS TO. This file has carried FIVE answers to one question over four rounds, 508, 483, 449,
   463 and 477, because each was one state of the strip reported as the only state and one of them
   was taken through a harness that had lost this stylesheet's :root block and so measured a
   fallback font. A reviewer who measures one state and a reviewer who measures another will
   disagree for ever otherwise, and four already have.

   MEASURED 2026-09-12 FOR MUS-815, in Chromium at 320, 375, 768 and 1280, against the Space Mono
   the console's own font link serves, with the face asserted by a FontFace entry for weight 700
   reaching status 'loaded'. NOT by document.fonts.check, which returns true over a font that is
   not there, and not by comparing rendered widths, because on the build machine Archivo Black and
   Arial Black measure the same string identically.

   MUS-815 TOOK THE FOURTH TAB OUT AND GAVE THE THIRD A SECOND LABEL. Three tabs now: Details,
   QR & Promos and Ticketing, and Ticketing renders three ways. At 375px, where the strip has the
   347 left after the console's own padding, with the 10px padding set below:

     plain, no tick, not selling        last tab ends at 259.59   fits
     tick showing, not selling          last tab ends at 273.50   fits
     tick and "(LIVE)", selling         last tab ends at 325.25   fits

   AND THE NUMBER THAT DECIDED THE PADDING IS NOT THE STRIP WIDTH, IT IS THE FADE. The mask below
   runs from 26px short of the right edge, so at 375 it starts at 321 whatever the content does. At
   the 16px padding this file shipped, the selling strip needs 361 in 347 AND the text of "(LIVE)"
   ends at 345.25, which puts 24.25px of the word the state is carried in under a gradient falling
   to transparent. At 12px it fits at 337.25 and 4.25px of the word is STILL under the fade; the
   build spec predicted 12px would clear it and the measurement says otherwise. At 10px the text
   ends at 315.25 and is clear by 5.75px, and the fade then falls on empty ground where it dims
   nothing. That is why the value is 10 and not 12.

   WHY A PADDING CHANGE IS ALLOWED WHERE A BREAKPOINT CHANGE WAS NOT. The refusal that used to sit
   here was right and its reason still holds: this strip is shared by the artist and venue editors,
   so its BREAKPOINT is not this feature's number to pick. A padding reduction is a different kind
   of change, because it makes every strip narrower by the same amount and can only help the other
   two. Measured at 375 with 10px: the artist strip fits and the venue strip fits, and the shortest
   tab in any of the three is 44px tall, which is the floor MUS-807 exists to protect.

   AT 320px THE SELLING STATE STILL OVERFLOWS, by 33px in 292, and that is correct rather than
   unfinished: it scrolls, and the fade is the only thing that says there is more to the right, so
   both are doing their job at that width. The other two states fit at 320.

   THE 721px NOTE THAT USED TO BE HERE IS DELETED BECAUSE ITS SUBJECT IS GONE. It described a bare
   overflow from 721 to 728 caused by a five tab strip losing the Guest list button's trailing
   padding. Measured now at 768 and 1280: 361.25 of content in 724 and in 1236, so there is no
   overflow at any desktop width in any of the three states.

   ONE THING THIS COMMENT USED TO CLAIM AND THE STYLESHEET NEVER DID: "it is turned off once the
   strip fits". There is no such rule and there never was; the mask below is unconditional at 720
   and under. It does no visible harm now, because at 10px every state's content ends before the
   fade begins, but it is a decoration rather than a measurement. console/promotions.js already
   solves this honestly for its own strip, with syncStripOverflow toggling a class on a wrapper
   from scrollWidth against clientWidth, "never by a tab count, so it cannot lie in either
   direction". Bringing that here means a wrapper around three strips and is raised rather than
   folded into this change.
   The scrollbar is hidden because a 375 strip with one showing loses a row of pixels off every
   tab label. */
.editor-tabs { position: relative; scrollbar-width: none; }
.editor-tabs::-webkit-scrollbar { display: none; }
@media (max-width: 720px) {
  .editor-tabs { -webkit-mask-image: linear-gradient(90deg, #000 calc(100% - 26px), transparent); mask-image: linear-gradient(90deg, #000 calc(100% - 26px), transparent); }
  /* MUS-815. See the measurement above: 10px is what keeps the text of "(LIVE)" clear of the fade
     at 375, and 12px does not. min-height: 44px is untouched and no tab comes near the floor. */
  .editor-tab { padding-left: 10px; padding-right: 10px; }
}
/* A field the flow refused. Terracotta rather than a message alone, because the sentence is at
   the foot of the screen and the field can be anywhere above it. */
.tk-body [aria-invalid="true"], .tk-body .is-bad { border-color: var(--terra); }
/* Scroll region: header + tab strip stay put on top, action bar on the bottom;
   only the active pane scrolls, resolving the old clip. */
.editor-body { flex: 1; min-height: 0; min-width: 0; display: flex; }
.editor-pane { flex: 1; min-height: 0; min-width: 0; overflow-y: auto; overflow-x: hidden; }
.editor-pane:not(.is-active) { display: none; }
/* Action bar pinned to the bottom of the editor region, always visible. */
.editor-actionbar {
  flex: none; display: flex; flex-direction: column; gap: 10px;
  border-top: 2px solid var(--edge); background: var(--paper);
  padding: 12px 0 8px; margin-top: 4px; position: sticky; bottom: 0; z-index: 30;
}
.editor-actionbar > .form-msg { flex: 0 0 auto; }
/* Mode-driven button visibility: Edit in view; Cancel + Save in edit. */
.editor-view.mode-view .edit-only { display: none !important; }
.editor-view.mode-edit .view-only { display: none !important; }

/* VIEW MODE: the same fields, read as clean values (no greyed disabled look). */
.editor-view.mode-view .fld input,
.editor-view.mode-view .fld select,
.editor-view.mode-view .fld textarea {
  border-color: transparent; background: transparent; padding-left: 0; padding-right: 0;
  color: var(--ink); -webkit-text-fill-color: var(--ink); opacity: 1; cursor: default; min-height: 0;
}
.editor-view.mode-view .fld select { appearance: none; -webkit-appearance: none; -moz-appearance: none; }
.editor-view.mode-view .fld textarea { resize: none; }
.editor-view.mode-view .fld input:focus,
.editor-view.mode-view .fld select:focus,
.editor-view.mode-view .fld textarea:focus { border-color: transparent; }
.editor-view.mode-view .fld input::placeholder,
.editor-view.mode-view .fld textarea::placeholder { color: transparent; }
/* Hide edit-only affordances in view mode. Classes are shared across the three
   editors; co-manager modes use .cm-search-mode / .cm-email-mode / .cm-state on
   both the artist and venue editors. The event and venue editors additionally
   hide their in-place search boxes and the map pin picker in view mode (the
   selected venue chip and the read-only coordinate fields still show). */
.editor-view.mode-view .req,
.editor-view.mode-view .rights-note,
.editor-view.mode-view .hint,
.editor-view.mode-view .upload-row,
.editor-view.mode-view .add-url,
.editor-view.mode-view .focal-panel,
.editor-view.mode-view .cm-search,
.editor-view.mode-view .cm-search-mode,
.editor-view.mode-view .cm-email-mode,
.editor-view.mode-view .cm-state,
.editor-view.mode-view #cm-msg,
.editor-view.mode-view #vcm-msg,
.editor-view.mode-view .pin-search,
.editor-view.mode-view .pin-panel,
.editor-view.mode-view .pin-attrib,
.editor-view.mode-view .lu-ctl,
.editor-view.mode-view .gcell .g-x,
.editor-view.mode-view .gcell .g-banner { display: none !important; }

/* A HINT THAT EXPLAINS A LOCKED FIELD IS NOT AN EDIT AFFORDANCE, so it survives view mode.
   The block above hides every .hint because almost all of them instruct somebody who is
   typing. A reader who CANNOT type still has to be told why a box refuses them, and hiding
   that sentence leaves a greyed box and a button with no caption beside it. Opt in per
   element rather than carving the whole class out, because most hints really are edit-only.
   BOTH GUARDS ARE LOAD-BEARING AND THEY COVER DIFFERENT PAINTERS, which is the whole reason
   there are two. Both .hidden and [hidden] are AUTHOR rules carrying !important, declared near
   the top of this file, and this rule beats them on SPECIFICITY alone rather than by origin. So
   an element deliberately hidden is forced back on screen unless the guard names the way it was
   hidden, AND a future author rule more specific than those two would win here instead. The two
   carriers do not agree: one toggles the .hidden CLASS and one sets the hidden ATTRIBUTE.
   A NEW CARRIER GETS A TERM HERE OR IT GETS THE SAME DEFECT. */
.editor-view.mode-view .hint.keep-in-view:not(.hidden):not([hidden]) { display: block !important; }

/* ===========================================================================================
   MUS-828. THE TICKETING PANE, UNLOCKED UNDER A READ-ONLY EVENT, GETS ITS EDITING LOOK BACK.

   WHY THIS RULE HAS TO EXIST AT ALL. The mode lives on the EVENT editor root and the block above
   is written against it, so a ticketing pane that has been unlocked on its own is still sitting
   inside .editor-view.mode-view: every .hint and .req is display:none !important, and every field
   loses its border, its background, its side padding and its 44px min-height. Measured on the
   deployed console with the fields unlocked and no rule: 0 of 3 hints on the Bookings stage on
   screen, and #tk-capacity computing min-height 0px, padding-left 0px, border rgba(0,0,0,0).
   A wizard you can type into that still looks like a page you cannot, with every rule it teaches
   missing, is worse than the locked pane it replaced, because nothing on screen says which it is.

   SCOPED TO THE PANE'S OWN FLAG AND NOTHING WIDER. applyReadOnly in console/main.js is the single
   writer of .is-editing and it writes it from the same editGate answer that decides the lock, so
   the look and the lock cannot disagree. The event's own fields are untouched by every selector
   here, which is the MUS-775 safety line drawn in CSS.

   EVERY VALUE BELOW IS THE BASE RULE'S OWN VALUE, not a new one: .fld input at line 436, focus at
   470, textarea at 442, placeholder at 441, .hint at 471. This rule restores, it does not restyle.
   !important ONLY ON THE TWO DISPLAY LINES, because those are the only two the block above forces.
   =========================================================================================== */
#ev-tab-pane-ticketing.is-editing .hint { display: block !important; }
#ev-tab-pane-ticketing.is-editing .req { display: inline !important; }
#ev-tab-pane-ticketing.is-editing .fld input,
#ev-tab-pane-ticketing.is-editing .fld select,
#ev-tab-pane-ticketing.is-editing .fld textarea {
  border-color: var(--line); background: var(--field);
  color: var(--bone); -webkit-text-fill-color: var(--bone);
  padding-left: 12px; padding-right: 12px; min-height: 44px; cursor: auto;
}
#ev-tab-pane-ticketing.is-editing .fld select { appearance: auto; -webkit-appearance: auto; -moz-appearance: auto; }
#ev-tab-pane-ticketing.is-editing .fld textarea { min-height: 90px; resize: vertical; }
#ev-tab-pane-ticketing.is-editing .fld input:focus,
#ev-tab-pane-ticketing.is-editing .fld select:focus,
#ev-tab-pane-ticketing.is-editing .fld textarea:focus { border-color: var(--amber); }
#ev-tab-pane-ticketing.is-editing .fld input::placeholder,
#ev-tab-pane-ticketing.is-editing .fld textarea::placeholder { color: var(--muted); }
/* Segmented mode/pattern toggles read as static: the chosen option stays, the
   alternatives are hidden, so view mode shows the choice without offering it. */
.editor-view.mode-view .seg2 .chip[aria-pressed="false"],
.editor-view.mode-view #r-pats .chip[aria-pressed="false"] { display: none !important; }
/* A genre already stored on the event but not typical for its type. Shown rather than hidden so
   it cannot be dropped silently on save; dimmed and dashed so it reads as "check this". */
.chip.chip-off-type { opacity: .72; border-style: dashed; }
/* Genre chips read as static selected tags (artist #f-genres, event #e-genres). */
.editor-view.mode-view #f-genres .chip,
.editor-view.mode-view #e-genres .chip { pointer-events: none; }
.editor-view.mode-view #f-genres .chip[aria-pressed="false"],
.editor-view.mode-view #e-genres .chip[aria-pressed="false"] { display: none; }
.editor-view.mode-view .toggle-row input[type="checkbox"] { opacity: 1; cursor: default; }

/* Style picker + branded card preview in the QR tab. */
.qp-styleprev { margin: 12px 0 4px; }
.qp-styleprev .qp-desc { margin-bottom: 8px; }
.qp-styleprev .qp-seg { flex-wrap: wrap; }
.qp-cardprev { margin: 14px 0 4px; }

/* ===== APPROVAL QUEUE ===== */
.queue-count { font-family: var(--mono); font-size: 11px; letter-spacing: .05em; text-transform: uppercase; color: var(--muted); }
.sub-list { display: flex; flex-direction: column; gap: 9px; }
/* flex-shrink:0 so cards keep their natural height inside the scrollable queue
   column; without it the flex column squashes/clips cards instead of scrolling. */
.sub-card { background: var(--card); border: 2px solid var(--edge); border-radius: 0; overflow: hidden; flex-shrink: 0; }
.sub-head {
  display: flex; align-items: center; gap: 10px; width: 100%; text-align: left;
  background: none; border: 0; color: var(--bone); cursor: pointer; padding: 12px; min-height: 56px; font-family: var(--sans);
}
.sub-type {
  font-family: var(--mono); font-size: 9px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  padding: 2px 6px; border: 2px solid; border-radius: 0; flex-shrink: 0;
}
.sub-type.type-artist { background: rgba(27,79,204,.18); color: var(--cobalt-lt); border-color: var(--cobalt); }
.sub-type.type-profile { background: rgba(27,79,204,.18); color: var(--cobalt-lt); border-color: var(--cobalt); }
.sub-type.type-venue { background: rgba(47,143,82,.16); color: var(--ok-lt); border-color: var(--ok); }
.sub-type.type-event { background: rgba(196,81,58,.18); color: var(--terra-lt); border-color: var(--terra); }
.sub-type.type-review { background: rgba(201,138,30,.16); color: var(--warn-lt); border-color: var(--warn); }
.sub-type.type-reply { background: rgba(27,79,204,.18); color: var(--cobalt-lt); border-color: var(--cobalt); }
.sub-type.type-delete { background: var(--terra); color: #fff; border-color: var(--dark); }
.sub-stars { font-family: var(--mono); color: var(--amber); font-size: 14px; letter-spacing: 2px; }
/* MUS-118 blast-radius + mode choice inside a deletion-request card */
.del-impact { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
.del-impact .pill { background: var(--field); color: var(--bone); border-color: var(--edge); }
.del-headline { font-family: var(--sans); font-size: 14px; font-weight: 700; color: var(--terra); margin-top: 10px; }
.del-choice { display: flex; flex-direction: column; gap: 8px; margin-top: 12px; }
.del-opt { display: flex; gap: 10px; align-items: flex-start; padding: 10px 12px; min-height: 44px;
  border: 2px solid var(--line); border-radius:0; cursor: pointer; background: #0d0a06; }
.del-opt input { margin-top: 3px; width: 18px; height: 18px; flex-shrink: 0; }
.del-opt-txt { font-family: var(--sans); font-size: 13px; color: var(--ink); line-height: 1.45; }
.del-opt-txt b { display: block; margin-bottom: 2px; }
.del-opt-txt .del-opt-note { color: var(--muted); font-size: 12px; }
.del-opt:has(input:checked) { border-color: var(--terra); }
.sub-quote { font-family: var(--sans); font-size: 13px; line-height: 1.5; color: var(--bone); background: #0d0a06; border: 1px solid var(--line); border-radius:0; padding: 10px 12px; white-space: pre-wrap; word-break: break-word; margin-top: 10px; }
.sub-quote.empty { color: var(--muted); font-style: italic; }
.sub-title { flex: 1; min-width: 0; font-family: var(--disp); font-size: 13px; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sub-when { font-family: var(--mono); font-size: 10px; color: var(--muted); flex-shrink: 0; display: none; }
@media (min-width: 560px) { .sub-when { display: inline; } }
.sub-caret { flex-shrink: 0; font-family: var(--mono); color: var(--cobalt-lt); transition: transform .1s; }
.sub-head[aria-expanded="true"] .sub-caret { transform: rotate(180deg); }
.sub-body { padding: 12px; border-top: 1px solid var(--line); }
.sub-meta { font-family: var(--mono); font-size: 11px; color: var(--muted); margin-bottom: 8px; }
.sub-facts { display: flex; flex-direction: column; gap: 6px; background: #0d0a06; border: 1px solid var(--line); border-radius:0; padding: 10px 12px; }
.sub-fact { display: flex; gap: 12px; align-items: baseline; flex-wrap: wrap; }
.sub-fact-k { font-family: var(--mono); font-size: 11px; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); flex: 0 0 130px; }
.sub-fact-v { font-family: var(--sans); font-size: 13px; color: var(--ink); flex: 1; min-width: 120px; word-break: break-word; }
.sub-payload {
  font-family: var(--mono); font-size: 11px; line-height: 1.5;
  background: #0d0a06; border: 1px solid var(--line); border-radius:0; padding: 10px;
  overflow: auto; max-height: 280px; white-space: pre-wrap; word-break: break-word; color: #cfe3b8;
}
.sub-actions { display: flex; gap: 10px; flex-wrap: wrap; justify-content: flex-end; margin-top: 12px; }
.sub-actions .btn { flex: 1; min-width: 120px; }
@media (min-width: 560px) { .sub-actions .btn { flex: 0 0 auto; } }
.sub-msg { margin-top: 10px; }
.list-state { font-size: 13px; color: var(--muted); font-style: italic; padding: 24px 10px; text-align: center; }

/* ===== MUSIAC USERS (MUS-115) ===== */
.us-filters { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-bottom: 12px; flex: none; }
.us-chipset { display: flex; flex-wrap: wrap; gap: 6px; }
.us-fchip { font-size: 11.5px; }
.us-fchip[aria-pressed="true"] { background: var(--amber); color: var(--dark); border-color: var(--amber); }
.us-testchip { margin-left: auto; }
#users-table { min-width: 960px; }
.us-name b { color: var(--ink); }
.us-email { display: block; font-family: var(--mono); font-size: 11px; color: var(--muted); margin-top: 2px; word-break: break-word; }
.us-city, .us-signup { color: var(--bone); }
.us-joined { font-family: var(--mono); font-size: 12px; color: var(--muted); white-space: nowrap; }
.us-dash { color: var(--muted); }
.us-cell-pills { display: flex; flex-wrap: wrap; gap: 5px; max-width: 260px; }
.pill.role { background: rgba(27,79,204,.18); color: var(--cobalt-lt); border-color: var(--cobalt); }
.pill.admin { background: var(--plum); color: #fff; border-color: var(--dark); }
.pill.madmin { background: rgba(122,79,217,.18); color: #b79be6; border-color: var(--plum); }
.pill.yes { background: rgba(27,79,204,.18); color: var(--cobalt-lt); border-color: var(--cobalt); }
#users-table .pill { font-size: 11px; }

/* compact one-line "Manages" summary in the table cell */
#users-table tbody tr[data-uid] { cursor: pointer; }
.us-manages {
  display: inline-flex; align-items: center; gap: 8px;
  background: none; border: 0; padding: 4px 0; margin: 0;
  min-height: 44px; cursor: pointer; text-align: left;
  font-family: var(--sans); color: var(--bone);
}
.us-manages .um-count { font-size: 12px; white-space: nowrap; }
.us-manages .um-view { font-size: 11px; color: var(--cobalt); text-decoration: underline; }
.us-manages:hover .um-view, .us-manages:focus-visible .um-view { color: var(--ink); }
/* MUS-142: per-type managed views stack so both fit the narrow cell at 375px */
.us-manages-group { display: flex; flex-direction: column; gap: 4px; align-items: flex-start; }

/* MUS-142: prefiltered-list back bar (Users -> View -> scoped Artists/Venues list) */
.scope-bar {
  display: flex; align-items: center; flex-wrap: wrap; gap: 10px;
  padding: 10px 0 12px; margin: 0 0 4px;
}
.scope-bar.hidden { display: none; }
.scope-back { min-height: 44px; }
.scope-note { font-size: 12px; color: var(--muted); font-family: var(--mono); }

/* MUS-143: "View" affordance on a manager row, and the read-only event managers block */
.cm-view {
  /* MUS-149: min-width:44px + justify-content:center keeps the tap target >=44x44
     (min-height already sets the vertical); the label was rendering ~41px wide. */
  flex-shrink: 0; min-width: 44px; min-height: 44px; display: inline-flex;
  align-items: center; justify-content: center;
  background: none; border: 0; padding: 4px 8px; cursor: pointer;
  font-family: var(--sans); font-size: 12px; color: var(--cobalt); text-decoration: underline;
}
.cm-view:hover, .cm-view:focus-visible { color: var(--ink); }
.evm-list { display: flex; flex-direction: column; gap: 8px; margin-top: 10px; }

/* user-detail modal: header + roles + grouped, scrollable entity lists */
.ud-card { max-width: 480px; display: flex; flex-direction: column; max-height: calc(100vh - 36px); }
.ud-body { flex: 1 1 auto; min-height: 0; overflow-y: auto; display: flex; flex-direction: column; gap: 16px; margin-bottom: 14px; }
.ud-sect { display: flex; flex-direction: column; }
.ud-h { font-size: 12px; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); margin: 0 0 8px; }
.ud-list {
  list-style: none; margin: 0; padding: 6px; max-height: 180px; overflow-y: auto;
  background: var(--field); border: 1px solid var(--line); border-radius:0;
  display: flex; flex-direction: column; gap: 4px;
}
.ud-item { font-size: 12px; color: var(--bone); padding: 7px 8px; border-radius:0; background: var(--card); word-break: break-word; }
.ud-item.ud-gone { color: var(--muted); font-style: italic; }
.ud-empty { font-size: 12px; color: var(--muted); font-style: italic; margin: 0; }
.ud-admin-badges { margin-bottom: 12px; }
.ud-admin-badges .pill { font-size: 11px; }
.ud-admin-ctl { margin-bottom: 8px; }
.ud-note { font-size: 11.5px; color: var(--muted); line-height: 1.4; margin: 0 0 14px; }

/* roles editor modal */
.roles-edit { display: flex; flex-direction: column; gap: 8px; margin-bottom: 14px; }
.ep-body{display:flex;flex-direction:column;gap:16px;max-height:52vh;overflow-y:auto;}
.ep-sect .ud-h{margin:0 0 6px;}
/* 44px minimum: measured at 19px tall, which is a text field nobody can reliably hit on a phone. */
.ep-q{width:100%;box-sizing:border-box;margin-bottom:8px;min-height:44px;padding:10px 12px;font-size:16px;}
.ep-list{border:1px solid var(--line);border-radius:4px;max-height:200px;overflow-y:auto;}
.ep-row{display:flex;align-items:center;gap:10px;padding:9px 11px;border-bottom:1px solid var(--line);cursor:pointer;min-height:44px;box-sizing:border-box;}
.ep-row:last-child{border-bottom:none;}
.ep-row.is-owned{opacity:.62;cursor:default;}
.ep-name{flex:1 1 auto;}
.ep-note{font-size:11px;color:var(--muted);white-space:nowrap;}
.role-toggle {
  display: flex; align-items: center; gap: 12px; width: 100%; text-align: left;
  min-height: 52px; padding: 10px 14px; cursor: pointer;
  background: var(--field); border: 2px solid var(--line); border-radius:0;
  color: var(--bone); font-family: var(--sans);
}
.role-toggle:hover { border-color: var(--muted); }
.role-toggle[aria-pressed="true"] { border-color: var(--cobalt); background: #10233f; }
.role-toggle .rt-main { flex: 1; min-width: 0; }
.role-toggle .rt-name { display: block; font-size: 13.5px; font-weight: 700; color: var(--ink); }
.role-toggle .rt-desc { display: block; font-size: 11.5px; color: var(--muted); margin-top: 2px; line-height: 1.4; }
.role-toggle .rt-state {
  flex-shrink: 0; font-family: var(--mono); font-size: 11px; letter-spacing: .05em; text-transform: uppercase;
  padding: 3px 8px; border-radius: 0; background: var(--card); color: var(--muted); border: 2px solid var(--edge);
}
.role-toggle[aria-pressed="true"] .rt-state { background: var(--cobalt); color: #fff; border-color: var(--cobalt); }
.role-toggle[aria-disabled="true"] { opacity: .55; pointer-events: none; }
.modal-card.wide { max-width: 480px; }
.roles-sub { font-family: var(--mono); font-size: 11px; color: var(--muted); margin: -6px 0 14px; word-break: break-word; }

/* ===== CONFIRM MODAL ===== */
/* MUS-758: THE SCRIM IS THE SCROLL CONTAINER. It is position:fixed, so a card taller than the
   viewport used to push the confirm button and the Cancel button beside it off the bottom with
   nothing anywhere able to scroll: the wheel did nothing, window.scrollTo moved the page behind
   while the fixed scrim stayed put, and scrollTop refused to move off 0. Measured in Chromium at
   375x812 before the fix, last on screen at 1217 characters of body, off at 1218 with the button
   at y 822 in an 812px viewport, and at 3000 characters at y 1336. That also means the dialog
   could not be DISMISSED, because Cancel is on the same row, and html.modal-open sets
   overflow:hidden so the page behind is not the escape either.
   WHICH OF THE THREE DECLARATIONS IS LOAD BEARING, measured as a 2x2 rather than reasoned about,
   because the first version of this comment named the wrong one and a mutant proved it. Columns
   are: is a short dialog still centred, can the heading be read, can the button be reached.
     flex-start + margin:auto   shipped     centred   head yes   button yes
     center     + margin:auto               centred   head yes   button yes
     flex-start + no margin                 NOT       head yes   button yes
     center     + no margin                 centred   head NO, at y -556   button yes
     shipped but overflow-y:visible         centred   head yes   button NO, at y 1966
   SO: overflow-y IS THE FIX, and nothing else in this rule substitutes for it. margin:auto on the
   card is what CENTRES a dialog that fits, which align-items no longer does, because auto margins
   beat align-self in flexbox. And align-items:flex-start is a genuine second line of defence
   rather than a belt: it only bites if margin:auto is ever deleted as decoration, and in that
   state centring a card taller than its container overflows it EQUALLY at both ends, putting the
   heading, the sentence saying what is about to happen, at a negative scroll offset nothing can
   reach. Neither of the two is individually lethal for that property, so saying either one alone
   is what stops it would be the over-claim this project has recorded before. */
.modal-scrim { position: fixed; inset: 0; z-index: 1300; background: rgba(5,4,2,.72); display: flex; align-items: flex-start; justify-content: center; padding: 18px; overflow-y: auto; -webkit-overflow-scrolling: touch; overscroll-behavior: contain; }
/* MUS-123: freeze the console behind an open dialog so touch scroll does not bleed through to the page underneath. */
html.modal-open, body.modal-open { overflow: hidden !important; }
.modal-card { min-width: 0; width: 100%; max-width: 420px; margin: auto; background: var(--card); color: var(--bone); border: 2px solid var(--edge); border-radius: 0; box-shadow: 8px 8px 0 rgba(0,0,0,.5); padding: 22px; }
.modal-card h3 { font-family: var(--disp); font-size: 16px; color: var(--ink); margin-bottom: 10px; }
/* MUS-749 / MUS-742: word-break, NOT overflow-wrap: break-word. The card is a flex item of the
   scrim, and an unbroken token in this paragraph, which is what an event title or an address is,
   still sets its min-content width under break-word, so the card grows past the viewport and the
   thing being confirmed is the part that gets clipped. Measured as identical to no rule at all.
   A confirmation that truncates its own description is a confirmation of nothing. */
/* MUS-843: white-space: pre-line, AND THE CONFIRMATION BODY IS THE REASON. openConfirm escapes
   its whole body into this one paragraph, so a body built as one line per booking collapses
   into a run-on sentence without this, silently and with nothing to see in the JavaScript.
   pre-line RATHER THAN pre-wrap, deliberately: pre-line preserves the newlines and still
   collapses runs of spaces, so every body that carries no newline, which was all of them until
   MUS-843, renders exactly as it did before. pre-wrap would also preserve accidental double
   spaces and stop long text wrapping the way it does today.
   IT LANDS ON THE TWO .roles-sub PARAGRAPHS IN THE USER DIALOGS AS WELL, because they are also
   .modal-card p. Measured as no change: both are built by concatenation from a name and an
   email and carry no newline, and the confirmation body is the only one this console builds
   with line breaks in it. tests/console-bookings.test.js pins the pairing to markPicked. */
.modal-card p { font-size: 14px; line-height: 1.5; margin-bottom: 18px; color: var(--bone); word-break: break-word; white-space: pre-line; }
.modal-card .fld { margin-bottom: 4px; }
/* The + that opens the entity editor. 44px minimum because it is a real control on a
   mobile-first surface, not decoration. */
.ud-h { display: flex; align-items: center; gap: 8px; }
.ud-add {
  margin-left: auto; min-width: 44px; min-height: 44px;
  border: 1px solid var(--edge); background: transparent; color: var(--bone);
  font-size: 18px; line-height: 1; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
}
.ud-add:hover { border-color: var(--cobalt); }
.ud-add:focus-visible { outline: 2px solid var(--cobalt); outline-offset: 2px; }
/* The warning that says "this also removes the role" was rendering in muted grey, identical to an
   ordinary hint, which is the one message in the flow that must not read as an aside. */
/* --amber, not --ochre: ochre is a mapview variable and is not declared in this file, so
   var(--ochre) resolved to nothing and the warning silently rendered in ordinary body colour.
   An undefined custom property fails invisibly, which is why this is worth naming. */
.form-msg.warn { color: var(--amber); }
.modal-actions { display: flex; gap: 10px; flex-wrap: wrap; }
/* A modal action button WRAPS rather than clips. .btn is white-space:nowrap for toolbars, where a
   one-line control is right, but these are flex:1 with a 110px floor, so a long label is squeezed
   below its own text width and the end is simply cut off with no scrollbar and no ellipsis to hint
   at it. Found at 375px on "Remove role and unlink 3": 149px of box, 150px of text. */
.modal-actions .btn { flex: 1; min-width: 110px; white-space: normal; height: auto; padding-top: 8px; padding-bottom: 8px; line-height: 1.25; }

/* ===== GATE (auth states, dark) ===== */
.gate {
  position: fixed; inset: 0; z-index: 1100;
  background: radial-gradient(120% 80% at 18% 0%, #1b160c 0%, #100c07 45%, #0a0806 100%);
  display: flex; align-items: center; justify-content: center; padding: 22px;
}
.gate.hidden { display: none !important; }
.gate-card {
  width: 100%; max-width: 420px; position: relative; overflow: hidden;
  background: var(--card); color: var(--bone);
  border: 2px solid var(--edge); border-radius: 0; box-shadow: 8px 8px 0 rgba(0,0,0,.55);
  padding: 26px 22px 22px;
}
.gate-corner { position: absolute; top: 0; right: 0; width: 76px; height: 76px; background: var(--amber); clip-path: polygon(100% 0,100% 100%,0 0); }
.gate-card.deny .gate-corner { background: var(--terra); }
.gate-kick { font-family: var(--mono); font-size: 10px; letter-spacing: .14em; text-transform: uppercase; color: var(--amber); margin-bottom: 8px; position: relative; }
.gate-card.deny .gate-kick { color: var(--terra); }
.gate-card h1 { font-family: var(--disp); font-size: clamp(18px, 5vw, 23px); line-height: 1.12; color: var(--ink); margin-bottom: 10px; }
.gate-card p { font-size: 14px; color: var(--muted); margin-bottom: 18px; line-height: 1.5; }
.gate-spinner { display: inline-block; width: 18px; height: 18px; border: 3px solid rgba(242,235,217,.2); border-top-color: var(--amber); border-radius: 50%; animation: spin .8s linear infinite; vertical-align: middle; margin-right: 8px; }
/* The front door. A FIXED card at every width: it does not stretch to fill a phone, because the
   card is the thing, and a full-bleed version of it stops reading as a door and starts reading as
   an app that has already let you in. Same max-width as the other gate cards. */
.gate-card.door { padding: 0; }
/* INK HEADER, NOT AMBER. An amber ground forces the wordmark to drop its amber IA and forces the
   pin mark into a recolour the guide does not define. Ink keeps the standard bone-and-amber lockup
   and the canonical on-dark mark, and leaves amber doing its job on the Sign in button.
   No opacity anywhere in the lockup: solid tokens only. */
.door-top { background: var(--dark); color: var(--bone); padding: 16px 18px 15px; border-bottom: 2px solid var(--edge); }
.door-lock { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; }
/* 40px is 1.4x the 28px wordmark, which is the guide ratio, added as a written rule on
   2026-09-05 after it existed only as a drawing: the identity hero pairs a 72px pin with a 52px
   wordmark and nothing said so, so this was built at 32/28 by nobody deciding. Costs nothing here,
   because the two text lines govern the lockup height. */
.door-lock svg { width: 40px; height: 40px; flex: none; display: block; }
/* THE BACKSTAGE LOCKUP (MUS-397). Every BACKSTAGE value here is the brand guide specimen,
   not a new choice: Space Mono 700, 12px, 4px tracking, uppercase, --muted, a 2px rule 8px above.
   TWO THINGS READING THE CSS CANNOT RECOVER.
   MUSIAC IS 28px BECAUSE THE RULING WAS ALIGN TO THE GUIDE, and the guide specimen is 28px / 2px.
   THE OBJECTION THAT LOST, kept because it was argued from a measurement rather than a preference:
   the design lead built this at 24px on the ground that the specimen assumes NO PIN while this card
   has a 32px one, so at 28px the wordmark is 135.97px against 115.25px, 18 percent wider beside an
   unchanged pin, and the pin stops reading as part of the lockup. Measured at 320, 375 and 414
   before the change: 28px costs 4px of card height and nothing else. No overflow, the pin gap holds
   at 10px, and BACKSTAGE stays the narrower line so the rule still belongs to the wordmark.
   THE RULE SPANS THE WORDMARK ONLY BECAUSE .door-wm IS SHRINK-TO-FIT, a flex item that is never
   stretched, so its width is max-content and MUSIAC is deliberately the wider line, 135.97 against
   BACKSTAGE 102.11. Give .door-wm flex:1,
   flex-grow:1 or width:100% and the rule grows to 253px, which is .door-lock at 295px less
   the 32px pin and the 10px gap. Neither number is the card, which measures 335px.
   align-items:stretch alone does NOTHING, measured: the cross axis of a row flex container is
   vertical, so it governs height. It bites only if flex-direction also becomes column, at 295px. 9px was tried and refused: it is under the guide 11px floor and the
   10px exception needs redundancy this screen does not have. */
.door-wm { font-family: var(--disp); font-size: 28px; letter-spacing: 2px; line-height: 1; color: var(--bone); }
.door-wm .am { color: var(--amber); }
.door-wm small { display: block; font-family: var(--mono); font-size: 12px; font-weight: 700; letter-spacing: 4px; text-transform: uppercase; color: var(--muted); border-top: 2px solid var(--muted); margin-top: 8px; padding-top: 2px; }
.gate-card .door-lead { font-family: var(--mono); font-size: 13px; line-height: 1.4; color: var(--bone); margin: 0; }
/* 44x44 minimum tap target (brand guide, Interface rules). These footer links measured 222x16 and
   263x33 at 375px, so they were comfortably under it. inline-flex plus min-height gives the target
   without moving the text baseline. */
.door-foot { border-top: 2px solid var(--edge); padding: 4px 18px 6px; font-size: 11px; color: var(--muted); display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.door-foot a { color: var(--cobalt-lt); display: inline-flex; align-items: center; min-height: 44px; }
.door-body { padding: 14px 18px 16px; }
.door-note { font-family: var(--mono); font-size: 11px; line-height: 1.45; color: var(--muted); margin: 10px 0 0; }
.door-sells { list-style: none; display: grid; gap: 10px; margin: 0 0 18px; padding: 0; }
.door-sells li { font-size: 13px; line-height: 1.45; color: var(--ink); padding-left: 20px; position: relative; }
.door-sells li::before { content: ''; position: absolute; left: 0; top: 5px; width: 9px; height: 9px; background: var(--cobalt); border: 2px solid var(--dark); }
/* ── THE FRONT DOOR AS A LANDING PAGE ───────────────────────────────────────────────────────────
   A centred card on a plain field said "internal tool" no matter what the words on it were. This is
   the one screen a business decides the product on, so it gets the brand's own language: cut
   geometric fragments, hard ink edges, no radius, asymmetry, and the display face at a size it can
   actually carry. The card stays a FIXED card and stays the focal point; everything around it is
   there to make the card mean something.
   Scrolls rather than centring, because a landing page is taller than a viewport on a phone. */
.gate.landing { display: block; overflow-y: auto; padding: 0; background: #0A0806; }
.lp { position: relative; z-index: 2; max-width: 1060px; margin: 0 auto; padding: 34px 20px 54px;
  display: grid; gap: 30px; align-content: center; }
@media (min-width: 900px) {
  .lp { grid-template-columns: minmax(0,1fr) 340px; gap: 54px; min-height: 100vh; align-items: center; padding: 40px 24px; }
}
/* 10px is the guide FLAT EXCEPTION and it needs all five of its conditions, not four. This met
   uppercase, .22em tracking, contrast at 12.27:1 and redundancy, and shipped at weight 400. One
   property was the whole gap. The six kind labels below it fail redundancy as well, which no
   weight can fix, so those need 11px and are MUS-561 rather than a quiet edit here. */
.lp-eyebrow { font-family: var(--mono); font-size: 10px; font-weight: 700; letter-spacing: .22em; text-transform: uppercase;
  color: var(--amber); margin-bottom: 14px; }
.lp-h { font-family: var(--disp); font-size: clamp(31px, 7.6vw, 60px); line-height: 1.02;
  /* POSITIVE, per the guide's display spec of 3px at 52px. It was -.02em, which is the default
     look of every modern landing page and the reason this read as off-brand: the wordmark and the
     headline were set to opposite instincts. */
  letter-spacing: .045em;
  color: var(--ink); margin: 0 0 14px; }
/* ONE lead paragraph in Crimson Pro italic, and only one. The guide reserves that face for lead
   copy; stretching it across a whole intro is what once put three faces on a single page. */
.lp-lead { font-family: var(--serif); font-style: italic; font-size: clamp(17px, 4.4vw, 22px);
  line-height: 1.4; color: var(--ink); margin: 0 0 14px; max-width: 40ch; }
/* The one word that is the promise, cut out of the headline in amber the way the brand cuts a shape
   out of a pin. Not a highlight box; the colour IS the emphasis. */
.lp-h em { font-style: normal; color: var(--amber); }
.lp-copy .door-sells { margin-bottom: 16px; max-width: 46ch; }
.lp-copy .door-sells li { font-size: 14px; color: var(--ink); }
.lp-copy .door-sells li::before { background: var(--amber); border-color: var(--amber); }

/* The cut fragments. Fixed to the viewport and behind everything, clipped so nothing can scroll the
   page sideways. Deliberately asymmetric and deliberately few: this is a signature, not a pattern. */
.lp-art { position: fixed; inset: 0; z-index: 1; overflow: hidden; pointer-events: none; }
.lp-art i { position: absolute; display: block; }
.lp-art .f1 { top: -8%; left: -6%; width: 46vw; height: 46vw; background: var(--cobalt); opacity: .17;
  clip-path: polygon(0 0, 100% 0, 34% 100%); }
.lp-art .f2 { bottom: -14%; right: -10%; width: 52vw; height: 52vw; background: var(--terra); opacity: .15;
  clip-path: polygon(100% 0, 100% 100%, 0 78%); }
.lp-art .f3 { top: 22%; right: 6%; width: 15vw; height: 15vw; min-width: 92px; min-height: 92px;
  background: var(--amber); opacity: .13; clip-path: polygon(50% 0, 100% 100%, 0 100%); }
.lp-art .f4 { bottom: 16%; left: 8%; width: 12vw; height: 12vw; min-width: 76px; min-height: 76px;
  background: #2A6E6B; opacity: .18; clip-path: polygon(0 0, 100% 26%, 74% 100%, 0 72%); }
/* Two hairlines, the same cut lines that divide the logo's fragments.
   KEPT CLEAR OF THE COPY. At their original placement (l1 vertical at left 26%, l2 horizontal at
   top 58%) the horizontal line ran through the first bullet at 1024 and the diagonal ran through
   the lead paragraph at 375. At 16% amber that reads as a rule struck through a sentence rather
   than as background geometry. The geometry was right and only the placement was wrong, so both
   are moved into empty ground: l1 into the left gutter, l2 below where the copy ends. */
.lp-art .l1, .lp-art .l2 { background: var(--amber); opacity: .16; }
.lp-art .l1 { top: 0; left: 4%; width: 2px; height: 100%; transform: rotate(9deg); }
.lp-art .l2 { top: 88%; left: 0; width: 100%; height: 2px; transform: rotate(-4deg); }
@media (prefers-reduced-motion: no-preference) { .gate.landing .gate-card { transition: transform .12s ease; } }

/* Event kinds as chips. Wraps at any width, so it needs no breakpoint of its own and reads the same
   on a phone as on a desktop. */
.door-kinds { list-style: none; display: flex; flex-wrap: wrap; gap: 5px; margin: 0 0 16px; padding: 0; }
.door-kinds li { font-family: var(--mono); font-size: 10px; letter-spacing: .08em; text-transform: uppercase;
  color: var(--muted); border: 2px solid var(--edge); padding: 4px 7px; }
/* The gate buttons are anchors as well as buttons now, so the request link matches the sign-in. */
a.btn { display: block; text-decoration: none; }
@keyframes spin { to { transform: rotate(360deg); } }

/* =====================================================================
   EVENTS (Phase 1 event creation)
   New CSS is limited to: the list type swatch (.tycol), the two-chip
   segmented radio layout, the dark-retinted proto-schedule preview
   components (.tl / .wk / .day / .tog / .summary) and the MapLibre pin
   panel. Everything derives from the existing :root tokens; the only new
   hex pair is the preview-block green (#3fae63 / #2f8f52) carried from
   the approved proto-schedule prototype.
   ===================================================================== */
.tycol { width: 9px; height: 9px; border-radius:0; display: inline-block; margin-right: 7px; flex-shrink: 0; }
.seg2 { display: flex; gap: 6px; flex-wrap: wrap; }
.price-range { display: flex; gap: 8px; margin-top: 8px; }
.price-range input { flex: 1; min-width: 0; }

/* schedule inset panel (dark equivalent of the prototype .sched) */
.sched-inset { background: var(--field); border: 2px solid var(--line); border-radius:0; padding: 12px; margin-top: 8px; }
.sched-desc { font-size: 12.5px; color: var(--muted); margin: 8px 0 2px; line-height: 1.45; }
.sched-inset .fld input, .sched-inset .fld select { background: var(--card); }
.sched-row { display: flex; gap: 12px; flex-wrap: wrap; margin-top: 10px; }
.sched-row > .fld { flex: 1; min-width: 140px; }

/* dark preview panel (proto .preview retinted to console tokens) */
.prev { background: var(--side); border: 2px solid var(--edge); border-radius: 0; padding: 14px; color: var(--ink); margin-top: 12px; }
.prev h4 { font-family: var(--disp); font-size: 12px; text-transform: uppercase; letter-spacing: .05em; margin: 0 0 10px; color: rgba(242,235,217,.85); display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.prev .badge { display: inline-block; font-family: var(--mono); font-size: 11px; font-weight: 700; letter-spacing: .03em; padding: 2px 8px; border: 2px solid var(--ok); border-radius: 0; background: rgba(47,143,82,.16); color: var(--ok-lt); }
/* single-continuous timeline (proto .tl) */
.tl { position: relative; height: 64px; background: rgba(242,235,217,.05); border-radius:0; overflow: hidden; }
.tl .daymark { position: absolute; top: 0; bottom: 0; border-left: 1px dashed rgba(242,235,217,.15); }
.tl .daymark span { font-family: var(--mono); font-size: 9px; color: rgba(242,235,217,.5); padding: 3px 4px; position: absolute; top: 0; white-space: nowrap; }
.tl .live { position: absolute; top: 24px; height: 24px; background: linear-gradient(90deg,#3fae63,#2f8f52); border-radius:0; border: 1px solid rgba(255,255,255,.4); display: flex; align-items: center; }
.tl .live b { font-family: var(--mono); font-size: 9px; color: #04210f; padding: 0 6px; }
/* recurring week grid (proto .wk) */
.wk { display: grid; grid-template-columns: 34px repeat(7,1fr); gap: 4px; }
.wk .hdr { font-family: var(--mono); font-size: 11px; color: rgba(242,235,217,.6); text-align: center; padding-bottom: 4px; }
.wk .axis { position: relative; height: 200px; }
.wk .axis span { position: absolute; right: 3px; font-family: var(--mono); font-size: 11px; color: rgba(242,235,217,.4); transform: translateY(-50%); }
@media (max-width: 400px) { .wk .axis span.h6 { display: none; } }
.wk .col { position: relative; height: 200px; background: rgba(242,235,217,.05); border-radius:0; overflow: hidden; }
.wk .col .grid { position: absolute; left: 0; right: 0; height: 1px; background: rgba(242,235,217,.08); }
.wk .blk { position: absolute; left: 3px; right: 3px; background: linear-gradient(180deg,#3fae63,#2f8f52); border-radius:0; border: 1px solid rgba(255,255,255,.35); }
.wk .blk .bt { font-family: var(--mono); font-size: 9px; color: #04210f; padding: 1px 3px; font-weight: 700; white-space: nowrap; display: block; overflow: hidden; }
.next-occ { font-family: var(--mono); font-size: 11px; color: rgba(242,235,217,.6); margin-top: 10px; line-height: 1.5; }
/* what-gets-stored summary strip (proto .summary) */
.summary { margin-top: 12px; background: rgba(245,197,24,.12); border: 1px solid rgba(245,197,24,.5); border-radius:0; padding: 11px 13px; }
.summary .lab { font-family: var(--mono); font-size: 11px; letter-spacing: .1em; text-transform: uppercase; color: var(--amber); }
.summary .txt { font-size: 13.5px; margin-top: 3px; color: var(--bone); line-height: 1.5; }

/* per-day cards (proto .day retinted; whole header row is the 44px toggle target) */
.days { display: grid; grid-template-columns: repeat(7,1fr); gap: 8px; margin-top: 8px; }
@media (max-width: 640px) { .days { grid-template-columns: repeat(4,1fr); } }
@media (max-width: 480px) { .days { grid-template-columns: repeat(2,1fr); } }
.day { border: 2px solid var(--line); border-radius:0; background: var(--field); overflow: hidden; }
.day.on { border-color: var(--cobalt); box-shadow: 0 0 0 2px rgba(27,79,204,.25); }
.day .dh { display: flex; align-items: center; justify-content: space-between; gap: 6px; width: 100%; min-height: 44px; padding: 8px 9px; background: none; border: 0; cursor: pointer; color: var(--bone); font-family: var(--sans); }
.day .dn { font-family: var(--mono); font-weight: 700; font-size: 12px; color: var(--bone); }
.day.off .dn { color: var(--muted); }
.day .tog { width: 34px; height: 19px; border-radius:0; background: #4a3c1a; position: relative; flex: 0 0 auto; display: inline-block; transition: background .12s; }
.day.on .tog { background: var(--cobalt); }
.day .tog::after { content: ""; position: absolute; top: 2px; left: 2px; width: 15px; height: 15px; border-radius: 0; background: #fff; transition: left .12s; }
.day.on .tog::after { left: 17px; }
.day .times { display: none; flex-direction: column; gap: 6px; padding: 0 9px 9px; }
.day.on .times { display: flex; }
.day .times label { display: block; font-family: var(--mono); font-size: 11px; letter-spacing: .04em; text-transform: uppercase; color: var(--muted); margin: 0 0 2px; }
.day .times input { width: 100%; min-width: 0; min-height: 44px; border: 2px solid var(--line); border-radius:0; background: var(--card); color: var(--bone); font-family: var(--mono); font-size: 11.5px; padding: 5px 7px; }
.day .times input:focus { outline: none; border-color: var(--amber); }

/* MapLibre pin picker panel (inline, lazy-loaded) */
.pin-panel { position: relative; border: 2px solid var(--line); border-radius:0; overflow: hidden; height: 240px; margin-top: 8px; background: var(--side); }
@media (max-width: 480px) { .pin-panel { height: 260px; } }
.pin-panel .pin-map { position: absolute; inset: 0; }
.pin-panel .pin-geo { position: absolute; top: 8px; right: 8px; z-index: 5; }
.pin-cap { position: absolute; left: 0; bottom: 0; z-index: 5; font-family: var(--mono); font-size: 11px; color: rgba(242,235,217,.85); background: rgba(13,10,6,.72); padding: 4px 9px; border-top-right-radius:0; pointer-events: none; }
.cv-grid { display: flex; gap: 8px; flex-wrap: wrap; }
.cv-grid > .fld { flex: 1; min-width: 140px; }

/* Pin picker: place/address search box, editable coordinates, attribution.
   The results dropdown reuses the shared .cm-results / .cm-result / .cm-empty
   styles so the picker matches the artist/venue lookups in this console. */
.pin-search { position: relative; margin-top: 8px; z-index: 6; }
.pin-search-input {
  width: 100%; min-height: 44px; padding: 10px 12px;
  border: 2px solid var(--line); border-radius:0;
  background: var(--field); color: var(--bone); font-family: var(--sans); font-size: 13px;
}
.pin-search-input::placeholder { color: var(--muted); }
.pin-search-input:focus { outline: none; border-color: var(--amber); }
.pin-coords { display: flex; gap: 8px; margin-top: 8px; }
.pin-coords > .fld { flex: 1; min-width: 0; margin: 0; }
.pin-coords label { display: block; font-family: var(--mono); font-size: 11px; letter-spacing: .04em; text-transform: uppercase; color: var(--muted); margin: 0 0 4px; }
.pin-coords input {
  width: 100%; min-width: 0; min-height: 44px; padding: 8px 10px;
  border: 2px solid var(--line); border-radius:0;
  background: var(--field); color: var(--bone); font-family: var(--mono); font-size: 12px;
}
.pin-coords input:focus { outline: none; border-color: var(--amber); }
.pin-coord-err { margin-top: 6px; min-height: 0; }
.pin-attrib { font-family: var(--mono); font-size: 11px; color: var(--muted); margin: 6px 0 0; line-height: 1.4; }
.pin-results .cm-empty.err { color: var(--terra); }
.cm-result.active { background: var(--field); }

/* =====================================================================
   ANALYTICS DASHBOARD (P3, admin spec section 8)
   All colours derive from the existing :root tokens. The chart is drawn as
   inline SVG (no external library, CSP-safe). Tracking-dependent tiles are
   marked with .collecting / the .an-collecting note so an empty tile reads
   as intentional, not broken.
   ===================================================================== */
.an-scroll { flex: 1; min-height: 0; overflow-y: auto; overflow-x: hidden; padding-right: 2px; }
.an-generated { font-family: var(--mono); font-size: 11px; letter-spacing: .04em; color: var(--muted); }
.an-block { margin-bottom: 22px; }
.an-block:last-child { margin-bottom: 4px; }
.an-block-hd { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 10px; }
.an-block-hd h4 { font-family: var(--mono); font-size: 11px; letter-spacing: .08em; text-transform: uppercase; color: var(--amber); }
.an-ctl-lab { font-family: var(--mono); font-size: 11px; letter-spacing: .05em; text-transform: uppercase; color: var(--muted); }
.an-select { min-height: 44px; padding: 8px 10px; border: 2px solid var(--line); border-radius:0; background: var(--field); color: var(--bone); font-family: var(--sans); font-size: 13px; max-width: 180px; }
.an-select:focus { outline: none; border-color: var(--amber); }
.an-grain { display: inline-flex; gap: 4px; border: 2px solid var(--line); border-radius:0; padding: 3px; background: var(--field); }
.an-grain button { min-height: 44px; min-width: 44px; padding: 6px 12px; border: 0; border-radius:0; background: none; color: var(--muted); font-family: var(--mono); font-size: 11px; letter-spacing: .04em; text-transform: uppercase; cursor: pointer; }
.an-grain button[aria-pressed="true"] { background: var(--amber); color: var(--dark); font-weight: 700; }

/* headline + stat-block grid */
.an-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 10px; }
.an-grid-wide { grid-template-columns: repeat(auto-fill, minmax(170px, 1fr)); }
.an-card { background: var(--card); border: 2px solid var(--edge); border-radius: 0; padding: 13px 14px; min-width: 0; }
.an-card.hero { border-color: var(--amber); background: linear-gradient(180deg, rgba(245,197,24,.10), rgba(245,197,24,.02)); grid-column: span 2; }
@media (max-width: 460px) { .an-card.hero { grid-column: 1 / -1; } }
.an-k { font-family: var(--mono); font-size: 11px; letter-spacing: .05em; text-transform: uppercase; color: var(--muted); display: block; }
.an-v { font-family: var(--disp); font-size: 26px; line-height: 1.05; color: var(--ink); margin-top: 6px; word-break: break-word; }
.an-card.hero .an-v { font-size: 34px; color: var(--amber); }
.an-sub { font-family: var(--mono); font-size: 11px; color: var(--muted); margin-top: 6px; line-height: 1.55; }
.an-sub b { color: var(--bone); font-weight: 700; }
/* Active-users breakdown: one value+label per line (no run-on inline sentence). */
.an-sub .an-rows { display: flex; flex-direction: column; gap: 2px; margin-bottom: 6px; }
.an-card.collecting { border-style: dashed; }
.an-card.collecting .an-v { color: var(--muted); }

/* the "collecting" note (verbatim, no dev language) */
.an-collecting { display: inline-flex; align-items: center; font-family: var(--mono); font-size: 10.5px; letter-spacing: .02em; color: var(--warn); background: rgba(201,138,30,.10); border: 1px solid rgba(201,138,30,.42); border-radius:0; padding: 4px 10px; line-height: 1.4; }

/* time-series panel + chart */
.an-panel { background: var(--card); border: 2px solid var(--edge); border-radius: 0; padding: 14px; }
.an-chart { width: 100%; min-height: 60px; }
.an-chart svg { width: 100%; height: auto; display: block; }
.an-state { font-family: var(--mono); font-size: 11.5px; color: var(--muted); line-height: 1.5; }
.an-state.err { color: var(--terra); }

/* ranked top lists */
.an-tops { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 12px; }
.an-toplist { background: var(--card); border: 2px solid var(--edge); border-radius: 0; padding: 13px 14px; min-width: 0; }
.an-toplist h5 { font-family: var(--sans); font-weight: 700; font-size: 13px; color: var(--ink); }
.an-tl-rows { margin-top: 10px; display: flex; flex-direction: column; gap: 9px; }
.an-tl-row { display: grid; grid-template-columns: 16px minmax(0,1fr) auto; align-items: center; column-gap: 9px; }
.an-tl-rank { font-family: var(--mono); font-size: 11px; color: var(--muted); text-align: right; }
.an-tl-main { min-width: 0; }
.an-tl-label { display: block; font-size: 12.5px; color: var(--bone); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.an-tl-bar { height: 5px; margin-top: 5px; border-radius:0; background: var(--field); overflow: hidden; }
.an-tl-fill { height: 100%; background: var(--cobalt); border-radius:0; min-width: 2px; }
.an-tl-val { font-family: var(--mono); font-size: 12px; color: var(--ink); font-weight: 700; }
.an-tl-state { font-family: var(--mono); font-size: 11px; color: var(--muted); margin-top: 9px; line-height: 1.5; }
.an-tl-state.err { color: var(--terra); }

/* ===== RESPONSIVE: collapse sidebar into a drawer ===== */
@media (max-width: 720px) {
  .app-shell { grid-template-columns: minmax(0, 1fr); }
  .mtop { display: flex; }
  .side {
    position: fixed; top: 0; bottom: 0; left: 0; width: 264px; max-width: 86vw; z-index: 70;
    transform: translateX(-100%); transition: transform .2s ease; overflow-y: auto;
  }
  .app-shell.nav-open .side { transform: translateX(0); }
  .app-shell.nav-open .side-scrim { display: block; }
  /* Mobile keeps normal page flow (drawer nav + sticky top bar): undo the
     desktop fixed-viewport containment so the page scrolls as one column. */
  html, body { height: auto; overflow-x: hidden; overflow-y: auto; }
  .app-shell { height: auto; min-height: 100dvh; overflow: visible; }
  .side { height: auto; }
  .main { height: auto; display: block; overflow: visible; padding: 0; min-width: 0; }
  .main > section { flex: none; display: block; overflow: visible; padding: 16px 14px 90px; min-width: 0; }
  .view { display: block; overflow: visible; min-width: 0; }
  .main-hd { min-width: 0; }
  .table-wrap { overflow-x: auto; overflow-y: visible; }
  #artist-form, #event-form, #queue-list, #an-scroll, #promo-scroll { overflow: visible; }
  /* MUS-713 F5 / MUS-712. THE ACTION BAR IS FIXED HERE AND IT HAS NEVER PINNED WITHOUT THIS.
     The comment this replaces said it "stays pinned at the viewport bottom via its own
     position:sticky", and it never did, on any editor, since the day the bar was written.
     WHY STICKY CANNOT WORK. A sticky box is constrained to its containing block. The bar is
     the LAST child of .editor-view, so parentRect.bottom minus barRect.bottom is 0: there is
     no slack for it to travel through, and it therefore moves 1:1 with the page. Measured at
     375x812 on all five ticketing steps: #tk-save at 1284, 1210, 1939, 1488 and 1564 against
     a viewport of 812, with Back to event and Switch on off screen beside it. Confirmed
     pre-existing on the Details bar, which predates the ticketing work.
     WHY FIXED RATHER THAN SLACK. The only slack available would be padding below the bar
     inside .editor-view, which is dead space a person has to scroll past to reach the end of
     a form. #section-bulk .bulk-flow made this same move for this same reason and records
     that no ancestor here creates a fixed containing block: no transform, filter,
     perspective, contain or will-change on the chain from html down to .editor-view.
     WHY THE PANE GETS PADDING. A fixed bar is out of flow, so without it the last control on
     the form sits underneath the bar and cannot be reached. --ev-bar-h is measured and
     published per editor by initActionBarPin in console/main.js; the 190px fallback is for
     the frame before the first measurement and for a browser with no ResizeObserver, and it
     is deliberately taller than the tallest bar measured rather than the average. */
  .editor-body { display: block; }
  .editor-pane { overflow: visible; padding-bottom: calc(var(--ev-bar-h, 190px) + var(--tk-bk-h, 0px) + 14px); }
  .editor-view .editor-actionbar {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 50;
    margin-top: 0;
    padding: 12px 14px calc(8px + env(safe-area-inset-bottom, 0px));
  }
  /* MUS-713 R4 F1. THE SECOND TIER. Mark paid and send tickets had the identical defect one
     element away, and it is worse than the action bar's because it grows with every booking:
     measured at 375x812 with three bookings the button sat at 1250 and with eight at 1819, off
     screen at scroll 0 and still off screen at scroll 400, reachable only at the foot of the
     page. document.elementFromPoint at its centre returned nothing at either scroll position.
     TWO FIXED BARS WOULD STACK ON EACH OTHER, so this one is not fixed to the bottom: it is
     fixed to the TOP of the action bar, at the height that bar publishes. --tk-bk-h is this
     bar's own measured height and the pane above reserves both, so the last booking in the list
     is still reachable rather than sitting under two bars. The 0px fallback is deliberate: this
     bar appears only when something is ticked, so reserving room for it by default would be
     dead space on every screen that never shows it.
     WHY THIS IS ALSO position:fixed AND NOT MORE SLACK. The block above says sticky cannot pin
     because the bar sits at its containing block's bottom edge. That is true of the action bar
     and it is NOT the mechanism here, which is worth writing down rather than repeating: the
     same sticky rule on this bar DOES pin at 1280, where it holds at 640 through every scroll
     position, because there the scroll container is .editor-pane. At 720px and below the pane's
     overflow is lifted and html and body take overflow-y:auto, so body becomes the scroll
     container and its scrollport is the whole 1894px page. The sticky view rectangle is then the
     entire document and the bar is already inside it, so nothing shifts. Proven by lifting
     overflow on html and body and nothing else: the bar moved to 728, which is the viewport
     bottom less its own 84px height, and went back where it started when the lift was removed.
     BOTH AXES had to be lifted, because overflow-x:hidden with
     overflow-y:visible computes the y axis straight back to auto. That rule is MUS-703 and it is
     not lifted here: it is what keeps horizontal overflow off this console. */
  .editor-view .tk-bk-bar {
    position: fixed; left: 0; right: 0; bottom: var(--ev-bar-h, 190px); z-index: 49;
    margin-top: 0;
  }
  th { position: static; }
  .search { max-width: none; }
}
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation: none !important; transition: none !important; }
}

/* ===== MUS-213 sortable columns + MUS-228 per-column filters ===== */
/* Per-column filter row: one typed control per column, aligned under the header. */
tr.col-filters th { padding: 4px 6px 8px; background: var(--card); border-bottom: 2px solid var(--edge); vertical-align: top; }
tr.col-filters .cf-in { width: 100%; box-sizing: border-box; font-family: var(--mono); font-size: 11px; border: 2px solid var(--edge); background: #0b0906; color: var(--ink); padding: 5px 6px; min-height: 30px; }
tr.col-filters select.cf-in { cursor: pointer; }
tr.col-filters .cf-date { display: flex; flex-direction: column; gap: 4px; }
tr.col-filters .cf-in:focus { outline: none; border-color: var(--amber); }
/* MUS-228: single-field date-range filter. One text field ("from to to") plus a
   calendar toggle that opens a small popover with From / To date inputs. The
   popover is position:fixed (computed from the button on open) so it never clips
   inside the scrolling table header. */
tr.col-filters .cf-daterange { display: flex; gap: 4px; align-items: stretch; position: relative; }
tr.col-filters .cf-daterange .cf-date-text { flex: 1 1 auto; min-width: 0; }
tr.col-filters .cf-cal-btn {
  flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center;
  min-width: 32px; min-height: 30px; border: 2px solid var(--edge); background: #0b0906;
  color: var(--muted); cursor: pointer; padding: 0 6px;
}
tr.col-filters .cf-cal-btn:hover { border-color: var(--amber); color: var(--amber); }
tr.col-filters .cf-cal-btn:focus-visible { outline: 2px solid var(--amber); outline-offset: -2px; }
.cf-date-pop {
  position: fixed; z-index: 90; width: 220px; box-sizing: border-box;
  background: var(--card); border: 2px solid var(--amber); padding: 12px;
  display: flex; flex-direction: column; gap: 10px; box-shadow: 0 10px 28px rgba(0,0,0,.55);
}
.cf-date-pop[hidden] { display: none; }
.cf-date-pop .cf-pop-lbl {
  font-family: var(--mono); font-size: 10px; letter-spacing: .05em; text-transform: uppercase;
  color: var(--muted); display: flex; flex-direction: column; gap: 4px;
}
.cf-date-pop .cf-pop-lbl input {
  width: 100%; box-sizing: border-box; font-family: var(--mono); font-size: 12px;
  border: 2px solid var(--edge); background: #0b0906; color: var(--ink); padding: 8px 8px; min-height: 40px;
}
.cf-date-pop .cf-pop-lbl input:focus { outline: none; border-color: var(--amber); }
.cf-date-pop .cf-pop-clear { align-self: flex-start; min-height: 40px; }
.list-count { font-size: 11px; color: var(--muted); font-variant-numeric: tabular-nums; margin: 0 0 8px; }
th.sortable { cursor: pointer; user-select: none; white-space: nowrap; }
th.sortable:hover { color: var(--amber); }
th.sortable .caret { opacity: .45; font-size: 9px; margin-left: 3px; }
th.sortable.sorted { color: var(--amber); }
th.sortable.sorted .caret { opacity: 1; }

/* ===== MUS-209 Bulk upload (scoped to #section-bulk) ===== */
#section-bulk { padding: 0; }
#section-bulk .bulk-head { flex: none; background: var(--paper); border-bottom: 1px solid var(--line); }
#section-bulk .bulk-title-row { display: flex; align-items: flex-start; gap: 14px; padding: 16px 22px 12px; flex-wrap: wrap; }
#section-bulk .bulk-title { font-family: var(--disp); font-size: 19px; color: var(--ink); line-height: 1.1; }
#section-bulk .bulk-sub { font-size: 12px; color: var(--muted); margin-top: 5px; max-width: 62ch; line-height: 1.5; }
/* .bulk-barbtns is DELETED, not hidden. It was the forward action pinned to the top right of the
   header by margin-left:auto, and moving the control while leaving the old slot behind would give
   this flow two forward affordances to keep in step, which is the drift this project has now paid
   for three times. See .bulk-flow at the foot of this block. */
#section-bulk .bulk-steps { display: flex; gap: 0; align-items: stretch; background: var(--dark); border-top: 1px solid var(--line); }
#section-bulk .b-step { flex: 1; display: flex; align-items: center; justify-content: center; gap: 8px; padding: 11px 8px; font-size: 11px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--muted); border-right: 1px solid var(--line); }
#section-bulk .b-step:last-child { border-right: none; }
#section-bulk .b-step .n { width: 20px; height: 20px; border-radius: 50%; border: 2px solid var(--muted); display: flex; align-items: center; justify-content: center; font-size: 11px; flex-shrink: 0; }
#section-bulk .b-step.on { color: var(--amber); background: rgba(245,197,24,.07); } #section-bulk .b-step.on .n { border-color: var(--amber); color: var(--amber); }
#section-bulk .b-step.done { color: var(--ok-lt); } #section-bulk .b-step.done .n { border-color: var(--ok-ink); background: var(--ok-ink); color: #fff; }
#section-bulk .bulk-scroll { flex: 1; min-height: 0; overflow-y: auto; overflow-x: hidden; padding: 18px 22px 44px; position: relative; }
#section-bulk .err-rail { position: absolute; width: 12px; right: 4px; pointer-events: none; z-index: 5; }
#section-bulk .err-rail .dot { position: absolute; right: 0; width: 12px; height: 12px; background: var(--terra); border: 2px solid var(--paper); border-radius: 50%; cursor: pointer; pointer-events: auto; box-shadow: 0 0 0 1px var(--terra); }
#section-bulk .err-rail .dot:hover { background: #e8927d; }
/* 11px, not 9.5px. The floor exists so a label is readable on a phone, and this one names the step
   you are on, on all four screens.

   EVERY LABEL ON THESE SCREENS IS NOW 11px, and the reason is worth keeping. The first pass left
   .b-step, .b-step .n, .chip and .flagpip at 10 to 10.5px on the grounds that they were tracked
   uppercase labels and might be allowed to sit at 10px under the tier MUS-244 proposes. Measured
   against the actual condition, all four missed it: that tier asks for 0.12em of tracking and they
   carry 0.030em to 0.042em, three to four times under. A label that is small AND tight is the exact
   thing the floor exists to stop, so the ruling turned out not to be the question. Nothing on these
   screens now depends on MUS-244 either way. */
/* NOT SCOPED TO #section-bulk ANY MORE, and that is a MOVE rather than a copy. Measured before
   doing it: this was declared in exactly one rule and used only by bulk.js, so dropping the id
   prefix cannot change a shipped pixel, while copying the declarations under a second name for
   the promotions part headings is how two visually identical labels drift apart. */
.sec-label { font-size: 11px; letter-spacing: .14em; text-transform: uppercase; color: var(--muted); margin: 2px 0 10px; border-left: 3px solid var(--amber); padding-left: 8px; }
/* Where "Reading your file", "none of these rows could be used" and every other file-reading
   message lands. Hidden while empty so it costs step 1 nothing: MUS-373's acceptance is that the
   screen fits one 375x667 phone with no scroll, and an always-present empty block would eat into
   that for no reason. */
#section-bulk .parse-msg { font-size: 12.5px; line-height: 1.5; color: var(--muted); margin-top: 14px; }
#section-bulk .parse-msg:empty { display: none; }
#section-bulk .parse-msg.err { color: var(--terra-lt); }
#section-bulk .card { background: var(--card); border: 2px solid var(--edge); padding: 18px; margin-bottom: 16px; }
#section-bulk .card-hd { font-family: var(--disp); font-size: 14px; color: var(--ink); margin-bottom: 5px; }
#section-bulk .card-sub { font-size: 12.5px; color: var(--muted); margin-bottom: 14px; line-height: 1.5; }
#section-bulk .resolvebar { position: sticky; top: -18px; z-index: 6; background: #1c1710; border: 2px solid var(--edge); margin: -4px 0 14px; padding: 11px 14px; display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
#section-bulk .rb-prog { display: flex; align-items: center; gap: 9px; font-size: 12.5px; font-weight: 700; color: var(--ink); }
#section-bulk .rb-track { width: 120px; height: 8px; border: 2px solid var(--edge); background: var(--dark); }
#section-bulk .rb-track span { display: block; height: 100%; background: #2f8f52; width: 0; transition: width .2s; }
/* min-height 44px, and it is NOT decoration. "Jump to next" is deleted from this bar, and its
   button was what gave .rb-flags its height: without this the bar collapses from 103px to 78px at
   375 at the exact moment the last name resolves, pulling the whole list up 25px under a finger
   that has just pressed something. That is the defect MUS-395 fixed from the other side, and
   deleting a button is a perfectly ordinary way to reintroduce it. */
#section-bulk .rb-flags { margin-left: auto; min-height: 44px; display: flex; align-items: center; gap: 9px; font-size: 12px; font-weight: 700; color: #e8927d; }
/* .rb-jump is DELETED. It read as step navigation, was labelled "next", and went to the next
   flagged ROW instead, competing for the word the real forward control needed. Its behaviour
   survives as the blocked state of that control, where the label finally matches what it does. */
/* THE RIGHT-HAND SLOT ALWAYS RESERVES THE HEIGHT OF A CONTROL, whether or not there is one in it.
   Found while measuring the receipt: this bar is 44px tall while it holds the jump button and about
   28px shorter once everything is resolved, so the render that resolves the last name also pulls
   the entire list up by 28px. That is the whole screen moving under a finger at the exact moment
   somebody has just pressed something, and it defeated the rule that a button may only ever move
   DOWN. Matching the two states costs nothing and is not a magic number: 44px is the tap target the
   jump button already has. */
#section-bulk .rb-alldone { margin-left: auto; min-height: 44px; display: flex; align-items: center; font-size: 12.5px; font-weight: 700; color: var(--ok-lt); }
#section-bulk .ev.has-flag { border-color: var(--terra); box-shadow: 0 0 0 1px var(--terra); }
/* 11px, matching the .chip beside it and everything else on these screens. It was 9.5px, then 10px
   on the assumption that a tracked uppercase label may sit there; at 0.040em it is not tracked
   anywhere near enough for that to apply. See the note on .sec-label. */
#section-bulk .ev-head .flagpip { font-size: 11px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: #fff; background: var(--terra); border: 2px solid var(--dark); padding: 2px 7px; }
#section-bulk .ent.flag { background: rgba(196,81,58,.07); margin: 0 -12px; padding: 9px 12px; border-left: 3px solid var(--terra); }
/* A RESOLVED ROW HAS TO LOOK RESOLVED. .ent.resolved carried no rule at all, so the entire feedback
   for a name that had just been decided was one 11.5px line of green text inside an otherwise
   identical row. Press a button that resolves eight of them and the screen barely moves, which is
   most of why it kept being raised. Geometry mirrors .ent.flag exactly, opposite colour, so the two
   states read as one language rather than as two unrelated ideas. */
#section-bulk .ent.resolved { background: rgba(42,127,73,.07); margin: 0 -12px; padding: 9px 12px; border-left: 3px solid var(--ok-ink); }
#section-bulk .b-h2 { font-family: var(--disp); font-size: 15px; color: var(--ink); margin-bottom: 4px; }
#section-bulk .b-lead { font-size: 13px; color: var(--muted); margin-bottom: 14px; } #section-bulk .b-lead b { color: var(--amber); }
#section-bulk .drop { border: 2px dashed var(--edge); background: var(--card); padding: 40px 20px; text-align: center; margin-top: 14px; }
#section-bulk .drop .big { font-family: var(--disp); font-size: 16px; color: var(--ink); margin-bottom: 6px; }
#section-bulk .drop .hint { font-size: 12px; color: var(--muted); margin-bottom: 16px; }
#section-bulk .legend { background: var(--card); border: 2px solid var(--edge); padding: 9px 12px; margin-bottom: 14px; font-size: 11px; line-height: 2; color: var(--muted); }
/* THE GLOBAL .chip RULE IS FOR GENRE CHIPS, WHICH ARE REAL BUTTONS: 44px tall, a pointer, and
   inline-flex so the label sits in the middle. These are static status labels. Overriding `display`
   killed the centring and left the 44px and the pointer behind, so every one of them was a 44px
   slab with its text jammed against the top and a cursor promising something to press. Measured at
   375px: a 37x44 block holding one 13px line, 6px above the text and 25px below. */
#section-bulk .chip { display: inline-block; font-size: 11px; font-weight: 700; letter-spacing: .03em; text-transform: uppercase; padding: 2px 7px; border: 2px solid var(--dark); margin: 1px 2px; min-height: 0; cursor: inherit; }
#section-bulk .c-pub { background: #173a2a; color: var(--ok-lt); border-color: #173a2a; }
#section-bulk .c-draft { background: #3a2f10; color: var(--amber); border-color: #3a2f10; }
#section-bulk .c-new { background: var(--cobalt); color: #fff; border-color: var(--dark); }
#section-bulk .c-match { background: #173a2a; color: var(--ok-lt); border-color: #173a2a; }
#section-bulk .c-dup { background: #2a2416; color: var(--muted); border-color: #2a2416; }
#section-bulk .c-issue { background: var(--terra); color: #fff; border-color: var(--dark); }
#section-bulk .ev { background: var(--card); border: 2px solid var(--edge); margin-bottom: 12px; }
#section-bulk .ev-head { display: flex; align-items: center; gap: 10px; padding: 10px 13px; background: var(--field); border-bottom: 2px solid var(--edge); }
#section-bulk .ev-head .t { font-family: var(--disp); font-size: 13px; color: var(--ink); flex: 1; min-width: 0; }
#section-bulk .ev-head .w { font-size: 11px; color: var(--muted); text-align: right; }
#section-bulk .nest { padding: 6px 12px 10px; }
#section-bulk .ent { display: flex; align-items: flex-start; gap: 9px; padding: 9px 0; border-bottom: 1px dashed var(--line); }
#section-bulk .ent:last-child { border-bottom: none; }
#section-bulk .ent .ic { width: 20px; text-align: center; font-size: 14px; flex-shrink: 0; margin-top: 1px; }
#section-bulk .ent .eb { flex: 1; min-width: 0; }
#section-bulk .ent .en { font-weight: 700; font-size: 12.5px; color: var(--ink); }
#section-bulk .ent .st { font-size: 11.5px; color: var(--muted); margin-top: 3px; }
#section-bulk .ent .st b { color: var(--ink); }
#section-bulk .ent .conf { color: var(--muted); }
#section-bulk .ctrls { display: flex; gap: 6px; margin-top: 6px; flex-wrap: wrap; align-items: center; font-size: 11px; color: var(--muted); }
/* 44px and 11.5px. These are the buttons that decide whether a name is linked to something that
   already exists or created fresh, and they were a 32px tall 10.5px sentence-case target: the
   smallest controls on the screen doing the most consequential thing on it. The label is sentence
   case, so the uppercase-label tier MUS-244 proposes does not cover it under any reading. */
#section-bulk .mini { font-family: var(--mono); font-size: 11.5px; font-weight: 700; border: 2px solid var(--edge); padding: 6px 10px; min-height: 44px; cursor: pointer; background: var(--field); color: var(--ink); }
#section-bulk .mini.yes { background: var(--ok-ink); color: #fff; border-color: var(--dark); }
#section-bulk .namein { font-family: var(--mono); font-size: 11.5px; border: 2px solid var(--edge); padding: 7px 8px; min-height: 44px; width: 220px; max-width: 56vw; background: #0b0906; color: var(--ink); }
#section-bulk .done-tag { font-size: 11.5px; font-weight: 700; color: var(--ok-lt); }
#section-bulk .warn-line { color: #e8b84b !important; }
/* MUS-373: the twelve-row column table and the four-sentence amber date note that used to sit
   between the heading and the drop zone are gone, and their rules with them. At 375px the table
   forced a horizontal scroll inside a scroll container, and the note described the parser rather
   than telling anyone what to write. The template carries the columns now.

   The acceptance target is that step 1 fits one 375x667 screen with nothing to scroll. Cutting the
   table got it to within 17px of that before the console's own topbar is even counted, so the drop
   zone and the scroll gutter give back their generous desktop padding on a phone. Measured, not
   guessed: 524px of content against 507px of room, and 450px after this. */
@media (max-width: 420px) {
  #section-bulk .bulk-scroll { padding: 14px 16px 20px; }
  #section-bulk .drop { padding: 16px 14px; }
  #section-bulk .drop .hint { margin-bottom: 12px; }
}
/* ===== MUS-385: a change to something that already exists =====
   THREE THINGS HAVE TO BE TOLD APART AT A GLANCE, and only one of them is reversible. A new draft
   is harmless. A change to a live record is not. Emptying a field on a live record is the one the
   the blank-means-empty rule makes easy to do by accident, because the downloadable template
   carries every column, so somebody correcting one cell has asked to clear the rest. It therefore
   gets the terracotta the rest of this console reserves for a problem, its own left rule, and its
   own count in the block at the top of the step. Nothing here relies on colour alone: a removal
   also reads the word "emptied", and the count beside it says "emptied" too. */
#section-bulk .chip.c-upd { background: var(--amber); color: var(--dark); border-color: var(--dark); }
#section-bulk .rev.upd { border-left: 4px solid var(--amber); }
/* A CHANGE THAT WILL NOT HAPPEN MUST NOT LOOK LIKE ONE THAT WILL, AND MUST STILL BE READABLE.
   Unticking used to leave the amber rule, the amber chip and the terracotta removal band all at
   full strength, so the only difference between a card that would rewrite a live record and one
   that would not was a 24px box.

   THE FIRST FIX FOR THAT USED OPACITY AND TOOK THE ONE WORD THAT SAYS IT WILL NOT HAPPEN DOWN TO
   2.40:1, under both the 4.5:1 text floor and the 3:1 large-text floor, while its "updating"
   counterpart sat at 11.97:1. Opacity is the wrong tool here: it cannot be undone on a child, so a
   label inherited the dimming meant for the text around it. Colour and shape do the same job and
   cost no legibility, and colour does not inherit into the chip because the chip sets its own.

   THE RULE IS DASHED, not merely darker, because on a card taller than the phone neither the chip
   nor the tick is on screen at all: a six column change list builds a 663px card in a 667px
   viewport. The rule is the only thing that runs the full height of a card, so it has to carry the
   state on its own, and a shape difference survives being the only cue. It was var(--edge) at
   1.24:1 against the card behind it, which is not a cue at all. */
#section-bulk .rev.upd.off { border-left: 4px dashed var(--muted); }
#section-bulk .rev.upd.off .nm, #section-bulk .rev.upd.off .mt { color: var(--muted); }
#section-bulk .rev.upd.off .chg-now { color: var(--muted); font-weight: 400; }
/* THE OFF STATE HAD TO REACH THIS TOO. .rev.upd.off recoloured the name, the meta line and the new
   value and left .chg-kept alone, so on an unticked card the amber "left as they are" sentence was
   the BRIGHTEST text on it at 10.04:1 while the name and the change it will not make dropped to
   6.74. The card shouted loudest about the thing that was not going to happen. */
#section-bulk .rev.upd.off .chg-kept { color: var(--muted); }
#section-bulk .rev.upd.off .chg-line.clr { background: transparent; border-left-color: var(--edge); }
/* COLOUR ONLY. Restyling this to plain 12px changed how the row wrapped, so a removal line lost a
   line of height and the whole card shrank 18px on the press that turned it off, pulling every card
   below it up under the finger. Type metrics are layout; only colour is safe to change on a state
   flip. */
#section-bulk .rev.upd.off .chg-line.clr .chg-now { color: var(--muted); }
#section-bulk .chip.c-off { background: #3a3323; color: #e4dcc6; border-color: var(--dark); }
/* Words, not only colour and shape, at the head of the list they apply to.
   ITS HEIGHT IS RESERVED WHETHER OR NOT IT SAYS ANYTHING, because adding a line to the change list
   on the press that turns a card off pushed every card below it down, under the finger that had
   just tapped. That is the defect a bar on the previous step was fixed for, reintroduced by the fix
   for a different one. The same trick as .bu-counts: the box is always there, the words are not. */
#section-bulk .chg-off { font-size: 12px; line-height: 19px; font-weight: 700; color: #e4dcc6; margin-bottom: 7px; min-height: 19px; }
#section-bulk .chg { margin-top: 8px; border-top: 1px dashed var(--line); padding-top: 7px; }
#section-bulk .chg-line { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 8px; font-size: 12px; line-height: 1.55; padding: 2px 0; }
#section-bulk .chg-col { font-weight: 700; color: var(--ink); min-width: 84px; }
/* A LONG UNBROKEN VALUE IS A URL, AND THIS SCREEN IS FULL OF THEM. A ticket link, a source and a
   socials handle all arrive as one word with no space in it, so normal wrapping cannot help and a
   180 character value ran 1255px past the right edge of a 375px phone. Measured, not imagined: the
   fixture's own artist source is a url. */
#section-bulk .chg-was, #section-bulk .chg-now, #section-bulk .chg-kept { min-width: 0; overflow-wrap: anywhere; }
#section-bulk .chg-was { color: var(--muted); text-decoration: line-through; }
#section-bulk .chg-arr { color: var(--muted); }
#section-bulk .chg-now { color: var(--ok-lt); font-weight: 700; }
#section-bulk .chg-line.clr { background: rgba(196,81,58,.12); border-left: 3px solid var(--terra); margin: 2px -9px; padding: 4px 9px; }
#section-bulk .chg-line.clr .chg-now { color: var(--terra-lt); text-transform: uppercase; letter-spacing: .1em; font-size: 11px; }
#section-bulk .chg-line.kept .chg-kept { color: #e8b84b; }
#section-bulk .chg-same { font-size: 11.5px; color: var(--muted); margin-top: 5px; }
#section-bulk .chg-note { font-size: 11.5px; color: var(--muted); margin-top: 7px; }
#section-bulk .b-upd { background: var(--card); border: 2px solid var(--amber); padding: 12px 14px; margin-bottom: 12px; }
#section-bulk .bu-hd { font-family: var(--disp); font-size: 14px; color: var(--ink); line-height: 1.3; }
/* The height is reserved whether or not anything is ticked. Without it, unticking the last change
   collapses this block and pulls the whole list up under a finger that has just pressed something,
   which is the defect the resolve bar on step 2 was fixed for. */
#section-bulk .bu-counts { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 8px; min-height: 27px; }
#section-bulk .bu-set, #section-bulk .bu-clr, #section-bulk .bu-held { font-size: 11.5px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; padding: 4px 9px; border: 2px solid var(--dark); }
/* Not terracotta. Nothing is being destroyed, so it must not borrow the colour this console uses
   for something going wrong; it is a statement that a cell was read and left. */
#section-bulk .bu-held { background: #2a2416; color: #e4dcc6; }
#section-bulk .bu-set { background: #173a2a; color: var(--ok-lt); }
#section-bulk .bu-clr { background: var(--terra); color: #fff; }
#section-bulk .bu-note { font-size: 12px; color: var(--muted); margin-top: 8px; line-height: 1.5; }
/* Said and not shown. The arrow between an old value and a new one means nothing to anybody not
   looking at it, and the word costs no room. */
#section-bulk .sr-only { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }
@media (max-width: 720px) {
  /* The column name takes its own line, so a long value gets the whole width rather than a
     leftover strip beside an 84px label. */
  #section-bulk .chg-col { min-width: 100%; }
  #section-bulk .chg-line { gap: 2px 8px; }
}

#section-bulk .b-sum { background: var(--card); border: 2px solid var(--edge); padding: 11px 13px; margin-bottom: 12px; font-size: 12.5px; line-height: 1.6; color: var(--ink); }
#section-bulk .b-sum b { color: var(--amber); font-variant-numeric: tabular-nums; }
#section-bulk .b-err ul li { margin-bottom: 2px; }

#section-bulk .skip-tag { font-size: 11.5px; font-weight: 700; color: var(--muted); }
#section-bulk .selbar { display: flex; align-items: center; gap: 12px; background: var(--field); color: var(--ink); padding: 9px 13px; border: 2px solid var(--edge); margin-bottom: 12px; font-size: 12px; font-weight: 700; flex-wrap: wrap; cursor: pointer; }
#section-bulk .rev { display: flex; align-items: flex-start; gap: 10px; background: var(--card); border: 2px solid var(--edge); padding: 10px 13px; margin-bottom: 8px; }
/* .rev.has-flag HAD NO RULE. The only has-flag rule is scoped to .ev, the step 2 card, so on step 3
   the class was a dead selector read solely by the error-rail jump: a row that cannot be created
   looked exactly like one that can. The error rail already points at these, so the row it points at
   has to be recognisable when you arrive. */
#section-bulk .rev.has-flag { border-color: var(--terra); box-shadow: 0 0 0 1px var(--terra); }
#section-bulk .rev[data-ri] { cursor: pointer; }
#section-bulk .rev-said { background: #2a2416; border: 2px solid var(--terra); border-top: none; color: var(--terra-lt); font-size: 12px; line-height: 1.45; padding: 8px 12px; margin: -8px 0 8px; }
/* THE MOST CONSEQUENTIAL CONTROL IN THE FLOW WAS THE SMALLEST TARGET IN IT. This box decides
   whether an event gets created, and it was a 24px square whose only handler was bound to itself.
   Two changes, and both are needed for different reasons.

   The box still PAINTS at 24px, because a 44px square would dominate a compact row, and takes a
   44px tap through a transparent ::before. That is the pattern the map already uses for .wm-toggle,
   and it is the reason a probe has to measure the HIT AREA rather than getBoundingClientRect: a box
   measurement calls this a defect after it is fixed.

   The whole row is clickable as well, which is what a person actually aims at on a phone. The
   handler lives on the row alone rather than on both, so a tap on the box cannot toggle twice by
   bubbling into a second listener. */
#section-bulk .tick { position: relative; width: 24px; height: 24px; border: 2px solid var(--edge); background: #0b0906; flex-shrink: 0; cursor: pointer; display: flex; align-items: center; justify-content: center; font-size: 13px; color: #fff; margin-top: 1px; }
#section-bulk .tick::before { content: ''; position: absolute; top: 50%; left: 50%; width: 44px; height: 44px; transform: translate(-50%, -50%); }
#section-bulk .tick.on { background: var(--ok-ink); border-color: var(--dark); }
#section-bulk .rev .nm { font-family: var(--disp); font-size: 12.5px; color: var(--ink); }
#section-bulk .rev .mt { font-size: 11px; color: var(--muted); margin-top: 2px; }
#section-bulk .prog { background: var(--card); border: 2px solid var(--edge); padding: 20px; }
#section-bulk .pline { display: flex; align-items: center; gap: 12px; padding: 11px 0; border-bottom: 1px solid var(--line); font-size: 13px; color: var(--ink); }
#section-bulk .pline .plabel { font-family: var(--disp); flex: 1; font-size: 13px; }
#section-bulk .pcount { font-variant-numeric: tabular-nums; font-weight: 700; color: var(--amber); }
#section-bulk .pbar { height: 12px; border: 2px solid var(--edge); background: #0b0906; margin-top: 14px; }
#section-bulk .pbar span { display: block; height: 100%; background: #2f8f52; width: 0; transition: width .2s; }
#section-bulk .success { background: #173a2a; color: #dff5e8; border: 2px solid #2f8f52; padding: 22px; text-align: center; }
#section-bulk .success .big { font-family: var(--disp); font-size: 20px; margin-bottom: 6px; color: var(--ok-lt); }
#section-bulk .b-err { background: rgba(196,81,58,.14); border: 2px solid var(--terra); color: var(--terra-lt); padding: 12px 14px; margin-bottom: 12px; font-size: 12px; line-height: 1.5; }
#section-bulk .btn2 { font-family: var(--mono); font-weight: 700; font-size: 11px; border: 2px solid var(--edge); background: var(--field); color: var(--ink); padding: 9px 11px; min-height: 44px; cursor: pointer; }

/* ===== step 2: what the bulk buttons did, and how to take it back =====
   TWO SLOTS IN ONE ZONE, receipt first. The margin lives on the ZONE and not on either slot's top,
   which is what makes the receipt's top edge land exactly where the pressed button's top edge was:
   whichever slot is first, it starts at the same y. Any button still left to press is then pushed
   DOWN, never up, so nothing new arrives under a finger that is already moving toward the old one. */
#section-bulk .bulkacts { margin-bottom: 14px; }
#section-bulk .bulk-btnrow { display: flex; gap: 8px; flex-wrap: wrap; }
#section-bulk .bulk-applied { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; background: #173a2a; border: 2px solid var(--ok); color: #dff5e8; padding: 10px 13px; margin-bottom: 10px; font-size: 12.5px; line-height: 1.5; }
#section-bulk .ba-said { font-weight: 700; color: var(--ok-lt); }
/* Its own line, so the Undo button can sit hard right on the first one without the note being
   squeezed into whatever is left over. */
#section-bulk .ba-note { flex-basis: 100%; color: #dff5e8; }
#section-bulk .bulk-undo { margin-left: auto; font-family: var(--mono); font-weight: 700; font-size: 11px; letter-spacing: .04em; text-transform: uppercase; min-height: 44px; padding: 9px 14px; border: 2px solid var(--ok-lt); background: transparent; color: #dff5e8; cursor: pointer; }
#section-bulk .bulk-undo:hover { background: rgba(143,227,179,.16); }
#section-bulk .bulk-undo:focus-visible { outline: 3px solid var(--amber); outline-offset: 2px; }

/* ===== the flow bar: one forward control, at the foot of the work, on every step =====

   TWO ANCHORING MECHANISMS ON PURPOSE, because one cannot serve both widths.

   DESKTOP is a flex sibling with no positioning at all. #section-bulk is already a flex column with
   .bulk-head at flex:none and .bulk-scroll at flex:1, so a third child at flex:none sits at the
   bottom of the section by ordinary layout. It cannot be carried away by a scroll container for the
   simple reason that it is not inside one.

   PHONE is position FIXED, not sticky, and that distinction is the whole reason this ticket does
   not have to wait for MUS-394. body { overflow-x: hidden } below 720px makes body a scroll
   container that never scrolls, so sticky has nothing to stick against: measured at top 19,172 in a
   667px viewport on a long file. Fixed measures 604 to 667 at any scroll position on a 22,000px
   page. Checked before relying on it: no ancestor of #section-bulk creates a fixed containing block,
   so there is no transform, filter, perspective, contain or will-change to be defeated by. */
#section-bulk .bulk-flow { flex: none; display: flex; align-items: center; gap: 12px; padding: 10px 22px; background: var(--paper); border-top: 2px solid var(--edge); }
#section-bulk .bf-mid { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
/* THE POSITION NEVER WRAPS AND THE STEP NAME GIVES WAY TO IT. With the real Space Mono at 375px,
   "Step 3 of 4" wrapped to two lines and "Review events" truncated to "Review ev...", so the one
   thing that replaced the deleted step rail read as two broken fragments beside a clipped word, and
   the bar grew 66px to 75px. There is no room on a 375px bar for Back, a two line label and a
   forward control, and the step NAME is the redundant half: the panel heading two inches above
   already says it. So it is hidden where it cannot fit, and the position, which is the half nothing
   else carries, is told never to wrap.
   MEASURED WITH THE SHIPPED WEBFONT, not the harness fallback, which is a different face and is
   narrower. See the note in tests/e2e/helpers/bulk-harness.js. */
#section-bulk .bf-where { font-size: 11px; letter-spacing: .14em; text-transform: uppercase; color: var(--amber); font-weight: 700; white-space: nowrap; }
#section-bulk .bf-what { font-size: 11px; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
@media (max-width: 480px) { #section-bulk .bf-what { display: none; } }
/* THE ANNOUNCED SENTENCE IS SPOKEN, NOT SHOWN, and that split was forced by measurement rather than
   chosen. Putting "Step 2 done. Review events." into the visible line gave 163px of text in a 119px
   box at 375, so the most important sentence in this flow was being clipped on the primary device.
   The visible signal for a sighted person is the control itself going amber and gaining its shadow,
   which is the design; the sentence is what a screen reader needs, and it belongs where length costs
   nothing. */
#section-bulk .bf-said { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }
/* THREE TIERS, AND NEVER THE disabled ATTRIBUTE. The control that shipped carried native disabled
   and this console has no :disabled rule anywhere, so blocked and ready computed IDENTICALLY, string
   for string: full amber, opacity 1, hard offset shadow, cursor pointer. Pressing it did nothing and
   said nothing, which teaches "this is not related to me" in one tap.
   Losing the hard shadow is what separates blocked from live. aria-disabled is not used either: it
   would be a lie, because the blocked control works and takes you to the reason. */
#section-bulk .bf-go { min-height: 44px; padding: 11px 16px; font-family: var(--mono); font-weight: 700; font-size: 11px; letter-spacing: .04em; text-transform: uppercase; cursor: pointer; background: var(--amber); color: var(--dark); border: 2px solid var(--dark); box-shadow: 4px 4px 0 rgba(0,0,0,.55); }
#section-bulk .bf-go.blocked { background: var(--field); color: var(--ink); border-color: var(--edge); box-shadow: none; }
#section-bulk .bf-back { min-height: 44px; min-width: 66px; padding: 11px 14px; font-family: var(--mono); font-weight: 700; font-size: 11px; letter-spacing: .04em; text-transform: uppercase; cursor: pointer; background: transparent; color: var(--muted); border: 2px solid var(--edge); box-shadow: none; }
#section-bulk .bf-go:focus-visible, #section-bulk .bf-back:focus-visible { outline: 3px solid var(--amber); outline-offset: 2px; }
#section-bulk .bf-working { min-height: 44px; display: flex; align-items: center; font-family: var(--mono); font-weight: 700; font-size: 11px; letter-spacing: .04em; text-transform: uppercase; color: var(--muted); }

@media (max-width: 720px) {
  #section-bulk .bulk-flow { position: fixed; left: 0; right: 0; bottom: 0; z-index: 50; padding: 10px 14px calc(10px + env(safe-area-inset-bottom, 0px)); }
  /* Room for the bar, or it covers the end of the list and the last row can never be reached. */
  #section-bulk .bulk-scroll { padding-bottom: calc(84px + env(safe-area-inset-bottom, 0px)); }
  /* THE STEP RAIL IS NOT NAVIGATION AND IT COSTS 57px OF THE FIRST SCREEN. Three of its four
     transitions are conditional, so making its cells tappable would produce three dead controls.
     On a 22,715px page it was visible for 223px of it. The bar carries the step count in words
     instead, permanently, which is what was actually being looked for in the question about whether
     there was a fifth step. */
  #section-bulk .bulk-steps { display: none; }
}

/* MOTION IS ADDITIVE AND THE STILL STATE IS THE DESIGN. Everything above renders complete without
   this block: the green field, the border and the words are all there on frame one. Declared only
   inside no-preference rather than declared and then cancelled, because the blanket
   `animation: none !important` under reduce has already cost this project a focus ring that
   disappeared entirely, and a receipt that never paints is the same defect with worse consequences. */
@media (prefers-reduced-motion: no-preference) {
  #section-bulk .bulk-applied { animation: bulk-applied-in .18s ease-out; }
  @keyframes bulk-applied-in { from { opacity: .45; transform: translateY(-3px); } to { opacity: 1; transform: none; } }
  /* The blocked to ready change is a settle on two properties, declared only here. It is a
     transition rather than a keyframe on purpose: a keyframe would have to be cancelled under
     reduce, and the blanket `animation: none !important` in the reduced-motion block would then
     strand the amber ready state at its from-value and it would never paint. That exact shape has
     already cost this project a focus ring. Nothing moves in this transition either: the bar keeps
     its position and height, and the control is right anchored so a wider label grows leftwards,
     away from a thumb resting where the old one was. */
  #section-bulk .bf-go { transition: background-color .16s ease-out, box-shadow .16s ease-out, color .16s ease-out; }
}

/* ===== MUS-86/223/224: PROMOTE + QR + CAMPAIGNS ===== */
.qp-wrap { margin-top: 22px; border-top: 2px solid var(--edge); padding-top: 18px; }
.qp-head { font-family: var(--disp); font-size: 15px; color: var(--ink); margin-bottom: 2px; }
.qp-head .am { color: var(--amber); }
.qp-desc { font-family: var(--mono); font-size: 11px; color: var(--muted); margin-bottom: 16px; line-height: 1.5; }
.qp-card { background: var(--card); border: 2px solid var(--edge); border-radius: 0; padding: 14px; margin-bottom: 14px; }
.qp-card > h5 { font-family: var(--mono); font-size: 11px; letter-spacing: .08em; text-transform: uppercase; color: var(--amber); margin-bottom: 10px; }
.qp-linkrow { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 12px; }
.qp-linkrow input { flex: 1; min-width: 160px; font-family: var(--mono); font-size: 12px; background: #0b0906; border: 2px solid var(--edge); color: var(--bone); padding: 10px; min-height: 44px; }
.qp-qrline { display: flex; align-items: flex-start; gap: 14px; flex-wrap: wrap; }
.qp-qr { border: 3px solid var(--dark); background: #fff; padding: 6px; width: 104px; height: 104px; flex-shrink: 0; }
.qp-qr canvas { width: 92px; height: 92px; image-rendering: pixelated; }
.qp-qrmeta { font-family: var(--mono); font-size: 12px; color: var(--muted); min-width: 180px; flex: 1; line-height: 1.5; }
.qp-qrmeta b { color: var(--bone); }
.qp-newrow { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 14px; }
.qp-newrow input { flex: 1; min-width: 180px; font-family: var(--sans); font-size: 13px; background: #0b0906; border: 2px solid var(--edge); color: var(--bone); padding: 10px; min-height: 44px; }
.qp-tblwrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }
table.qp-tbl { width: 100%; min-width: 520px; border-collapse: collapse; font-size: 12.5px; }
table.qp-tbl th { text-align: left; font-family: var(--mono); font-size: 10px; letter-spacing: .05em; text-transform: uppercase; color: var(--muted); border-bottom: 2px solid var(--edge); padding: 8px; }
table.qp-tbl td { border-bottom: 1px solid var(--edge); padding: 10px 8px; vertical-align: middle; }
table.qp-tbl .qp-cn { color: var(--ink); font-weight: 700; }
table.qp-tbl .qp-cu { font-family: var(--mono); font-size: 11px; color: var(--cobalt-lt); word-break: break-all; margin-top: 3px; }
table.qp-tbl .num { font-family: var(--mono); text-align: right; color: var(--muted); }
table.qp-tbl .num b { color: var(--amber); }
.qp-st { display: inline-block; font-family: var(--mono); font-size: 9.5px; text-transform: uppercase; letter-spacing: .05em; padding: 3px 7px; border: 1.5px solid var(--edge); white-space: nowrap; }
.qp-st.active { border-color: var(--ok); color: var(--ok-lt); }
.qp-st.paused { border-color: var(--muted); color: var(--muted); }
.qp-st.archived { border-color: #5a4a2a; color: #a48a4a; }
.qp-actions { display: flex; gap: 6px; flex-wrap: wrap; }
.qp-mini { font-family: var(--mono); font-size: 10.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .03em; border: 2px solid var(--edge); background: var(--field); color: var(--ink); padding: 8px 10px; min-height: 40px; cursor: pointer; }
.qp-mini.del { border-color: var(--terra); color: var(--terra-lt); }
.qp-mini[aria-disabled="true"] { opacity: .5; pointer-events: none; }
.qp-rowmsg { font-family: var(--mono); font-size: 11px; color: var(--terra); margin-top: 6px; }
.qp-rowqr { margin-top: 12px; }
.qp-empty { font-family: var(--mono); font-size: 11.5px; color: var(--muted); padding: 6px 0; }
/* QR analytics filters (reuses an-block cards + table-wrap) */
.qp-filters { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; margin-bottom: 14px; }
.qp-seg { display: flex; border: 2px solid var(--edge); }
.qp-seg button { font-family: var(--mono); font-size: 10.5px; text-transform: uppercase; letter-spacing: .04em; background: transparent; color: var(--muted); border: 0; padding: 10px 12px; min-height: 44px; cursor: pointer; }
.qp-seg button[aria-pressed="true"] { background: var(--cobalt); color: #fff; }
.qp-filters select { font-family: var(--mono); font-size: 11px; background: #0b0906; border: 2px solid var(--edge); color: var(--bone); padding: 10px; min-height: 44px; max-width: 100%; }
table.qp-an { width: 100%; min-width: 560px; border-collapse: collapse; font-size: 12.5px; }
table.qp-an th { text-align: left; font-family: var(--mono); font-size: 9.5px; letter-spacing: .05em; text-transform: uppercase; color: var(--muted); border-bottom: 2px solid var(--edge); padding: 8px; position: sticky; top: 0; background: var(--card); }
table.qp-an th.num, table.qp-an td.num { text-align: right; font-family: var(--mono); }
table.qp-an td { border-bottom: 1px solid var(--edge); padding: 10px 8px; }
table.qp-an td.num b { color: var(--amber); }
table.qp-an tr.qp-grp td { background: #0b0906; font-family: var(--mono); font-size: 10px; text-transform: uppercase; letter-spacing: .05em; color: var(--amber); border-bottom: 2px solid var(--edge); }
table.qp-an tr.qp-tot td { border-top: 2px solid var(--edge); font-weight: 700; color: var(--ink); }

/* =====================================================================
   MUS-412 PROMOTIONS. Scoped to #section-promotions and .pr-* / .promo-* so it cannot reach any
   other screen. Every value here is a documented token; no colour is coined.
   ===================================================================== */

/* The section is a fixed-viewport region like every other one: the header and the tab strip stay
   put and only the panel scrolls. Without this the content below the fold is CLIPPED on desktop,
   because .main > section is overflow:hidden, which is unreachable content rather than a nicety.
   DELIBERATELY A CLASS AND NOT AN ID. The mobile shell drops the whole thing back into normal page
   flow with `#promo-scroll { overflow: visible }`, and an id-and-class selector here would outrank
   that rule wherever it sat in the file, leaving a mobile override that could never fire. */
.promo-scroll { flex: 1; min-height: 0; overflow-y: auto; overflow-x: hidden; padding-right: 2px; }
#section-promotions .promo-sub { flex: none; margin: 2px 0 12px; max-width: 62ch; }

/* THE PANEL IS THE CONTAINER, NOT THE VIEWPORT. A media query on viewport width is a lie inside
   this shell: at 719px the panel is about 690px wide and at 721px it is about 470px, because the
   sidebar appears. A viewport breakpoint gets that pair backwards at every width, so every row
   layout below switches on the panel's OWN width. */
#promo-panel { container: promo / inline-size; }

/* Tab strip. .editor-tabs and .editor-tab are reused verbatim; the wrapper exists only to carry the
   overflow affordance, because a fade drawn on the scroller itself scrolls away with the content. */
#section-promotions .tabstrip-wrap { position: relative; flex: none; margin-bottom: 14px; }
#section-promotions .editor-tabs { margin-bottom: 0; }
#section-promotions .tabstrip-wrap::after {
  content: ''; position: absolute; top: 0; right: 0; bottom: 2px; width: 30px;
  pointer-events: none; opacity: 0; transition: opacity .12s ease;
  background: linear-gradient(90deg, rgba(15,12,7,0), var(--paper));
}
#section-promotions .tabstrip-wrap.is-scrollable::after { opacity: 1; }

.promo-lead { margin: 0 0 16px; max-width: 68ch; }
.promo-days-help { margin: -4px 0 12px; max-width: 62ch; }

/* ---- LIVE ---- */
.pr-list { display: flex; flex-direction: column; gap: 10px; }
.pr-row {
  background: var(--card); border: 2px solid var(--edge); padding: 12px;
  display: grid; grid-template-columns: 30px minmax(0, 1fr);
  column-gap: 10px; row-gap: 10px; align-items: start;
}
.pr-num {
  width: 30px; height: 30px; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  background: var(--amber); color: var(--dark);
  font-family: var(--mono); font-weight: 700; font-size: 13px;
}
.pr-main { min-width: 0; }
/* Two lines then an ellipsis. Six rows have to be comparable at a glance to be reordered, and a row
   whose height depends on its title makes that harder. The full title is on every control's
   accessible name, and the date line underneath disambiguates two similar bookings. */
.pr-title {
  font-family: var(--sans); font-weight: 700; font-size: 13px; line-height: 1.3; color: var(--ink);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; overflow-wrap: anywhere;
}
.pr-when { font-family: var(--mono); font-size: 11.5px; line-height: 1.45; color: var(--bone); margin-top: 5px; }
.pr-run { font-family: var(--mono); font-size: 11.5px; line-height: 1.45; color: var(--muted); margin-top: 3px; }
.pr-ctl { grid-column: 1 / -1; display: flex; align-items: center; gap: 16px; }
.pr-ctl .btn.sm { min-width: 44px; }

/* MUS-412 round 2: how long THIS run lasts, on the row rather than in one box for the whole
   product, because run periods are going to be charged differently.
   The control is 44px tall so it clears the tap target floor on a phone, and the row wraps rather
   than scrolling sideways: this console has already shipped a twelve column table that ran off the
   side of a 375px screen, and a detector that could not see it. */
.pr-days { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 6px; }
.pr-days-lab {
  font-family: var(--mono); font-size: 11px; letter-spacing: .06em; text-transform: uppercase;
  color: var(--muted);
}
.pr-days-in {
  width: 72px; min-height: 44px; padding: 4px 8px; border: 2px solid var(--edge); border-radius: 0;
  background: var(--field); color: var(--ink); font-family: var(--mono); font-size: 13px;
}
.pr-days-in:focus { outline: 2px solid var(--amber); outline-offset: 1px; }
/* Says where the number came from. "14" alone cannot tell you whether somebody chose it for this
   event or it is simply what MUSIAC does by default. */
/* SPOKEN, NOT SHOWN. Lifted verbatim from the bulk screens rule above rather than written again,
   because a second definition of the same technique drifts. It clips, which is what the overflow
   probe exempts, and it costs no layout height beside a 44px control. */
.pr-days .vh { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }

.pr-days-note { font-family: var(--mono); font-size: 11px; color: var(--muted); }

/* MUS-412 round 3: the date range on a row, and the breadcrumb to the ended runs view.
   The fields are native date inputs so every platform brings its own picker and nothing here
   reimplements a calendar. 44px tall so they clear the tap target floor on a phone, and the row
   wraps rather than scrolling sideways. */
/* A LABEL AND ITS FIELD ARE ONE FLEX ITEM, so they cannot be separated by a wrap. */
.pr-date-pair { display: inline-flex; align-items: center; gap: 8px; }
/* THE TAIL ALWAYS TAKES ITS OWN LINE, in every state. The two date pairs fill the first line at
   375px, so a message could only ever wrap onto a second one, and a row that grows at the moment
   Save is pressed moves the destructive control under a finger. Reserving the line costs one row
   of height and makes all six tail states identical: the day count, the Save, and the four
   refusals. This is the third attempt at this row and the first one where the height cannot move. */
.pr-days-tail { flex: 1 1 100%; min-height: 44px; display: flex; align-items: center; }
.pr-date-in {
  min-height: 44px; padding: 4px 8px; border: 2px solid var(--edge); border-radius: 0;
  background: var(--field); color: var(--ink); font-family: var(--mono); font-size: 13px;
}
.pr-date-in:focus { outline: 2px solid var(--amber); outline-offset: 1px; }
.promo-crumbs { display: flex; align-items: center; gap: 8px; margin: 0 0 14px; min-height: 44px; }
.promo-crumb-sep { color: var(--muted); }
.promo-crumb-here { font-family: var(--mono); font-size: 12px; color: var(--muted); }
.promo-ended-link { margin: 18px 0 0; min-height: 44px; display: flex; align-items: center; }
/* NOT A FULL WIDTH LINE. .form-msg carries flex: 1 1 100%, which puts it on a second row however
   short it is, so the row grew 25px at the instant Save was pressed and moved the End run control
   and the whole list below it under a finger that had just pressed something. Measured at 375px:
   190px resting, 215px with any message. That is the 2026-08-16 lesson in a new place, and the
   product-wide control fifty lines below already solved it with a 44px floor.
   THE FLOOR IS A FLOOR AND NOT A CAP, which an earlier version of this comment got wrong. min-height
   cannot stop a wrapped message growing the box: measured at 375px, a two line message made this
   87px rather than 44. What actually keeps the row still is that every string rendered here is short
   enough to fit one line. The reason for a failure goes in the panel message above, which has a full
   line for it. Keep anything put in this slot to a couple of words. */
/* POSITIONED, so the hint span below has a containing block. Absolutely positioned with none,
   it resolved against the document instead: its static position sits deep inside a panel that
   scrolls, so the PAGE grew to reach it and it overlaid controls that then could not be
   pressed. Desktop only, because that is the width where this panel is a scroll container.
   The rule it was copied from works because its own section is positioned. */
/* ITS OWN ROW IN THE GRID, exactly as .pr-ctl has. Two date fields plus their labels and a day
   count do not fit beside the move and end controls at tablet width, and competing for that
   track put a control under a neighbour so one of its corners could not be pressed. Stacking is
   also the honest reading: the dates describe the run, the controls act on it. */
.pr-days { min-height: 44px; position: relative; grid-column: 1 / -1; }
.pr-days .form-msg { flex: 0 1 auto; margin: 0; font-size: 11.5px; }
/* THE DESTRUCTIVE CONTROL IS THE ONE THAT MUST NOT BE MIS-TAPPED, so it gets the whole remaining
   width on a phone. The two move controls are cheap to get wrong and one press undoes either. */
.pr-rm { margin-left: auto; }
/* A CONTROL WITH NOWHERE TO GO IS REMOVED, NOT DISABLED, and its slot is kept so nothing shifts
   between rows. visibility:hidden takes it out of the tab order and the accessibility tree while
   holding the space. The console's aria-disabled treatment measures about 2.58:1 on this ground,
   which is not a state anybody can read. */
.pr-move.is-gone { visibility: hidden; }
@container promo (min-width: 520px) {
  .pr-row { grid-template-columns: 30px minmax(0, 1fr) auto; align-items: center; row-gap: 0; }
  .pr-ctl { grid-column: auto; }
  .pr-rm { margin-left: 24px; }
}

/* EVERY LABEL IN THIS SECTION IS 11px. The guide opens the 10px tier only to an uppercase label
   tracked 0.12em or more, and .fld-label carries 0.05em, so it does not qualify. This is the same
   correction four labels on the bulk screens already took. Scoped here rather than swept across the
   console, because that sweep is its own change and not something to slip into a new feature. */
#section-promotions .fld label, #section-promotions .fld-label { font-size: 11px; }
/* THE SAME CORRECTION, AND IT WAS FOUND BY MEASURING RATHER THAN BY READING. The "already featured"
   chip in the shared picker ships at 9px with 0.04em, which is under the floor twice over: too
   small for any tier, and nowhere near the 0.12em the small tier is conditional on. Scoped here for
   the same reason the labels above are, because raising it everywhere is a console-wide sweep and
   not something to slip into a new feature. */
#section-promotions .cm-result .r-added { font-size: 11px; }

/* ---- ADD ---- */
.promo-add { margin-top: 16px; border-top: 2px solid var(--edge); padding-top: 16px; }
.promo-add .cm-results { max-height: 300px; }
.pr-result-when { display: block; font-family: var(--mono); font-size: 11px; color: var(--muted); margin-top: 2px; }

/* ---- PARTS ---- */
.promo-part { margin-top: 26px; padding-top: 22px; border-top: 2px solid var(--edge); }

/* ---- DAY SETTING ---- */
.promo-days-row { display: flex; flex-wrap: wrap; align-items: flex-start; gap: 14px; }
.promo-days-fld { flex: 0 0 auto; width: 128px; }
.promo-days-fld input { font-family: var(--mono); }
/* THE SLOT RESERVES A CONTROL'S HEIGHT WHETHER OR NOT ONE IS IN IT. The button is present only when
   there is a valid new value to store, so the slot swaps between a control and a sentence, and a
   44px floor stops the panel jumping at the moment somebody presses something. */
.promo-days-slot { flex: 1 1 240px; min-width: 0; min-height: 44px; display: flex; align-items: center; }
.promo-days-slot .form-msg { margin: 0; }

/* ---- ENDED ---- */
.pr-exrow {
  background: var(--card); border: 2px solid var(--edge); padding: 11px 12px;
  display: grid; grid-template-columns: minmax(0, 1fr); row-gap: 4px; align-items: start;
}
.pr-exrow .pr-title { -webkit-line-clamp: 2; }
.pr-ex-main { min-width: 0; }
/* WHY THE RUN ENDED, and it is not optional: "the days ran out" invites somebody to lengthen the
   setting to bring the run back, which for a finished event is false. Same treatment as .pr-when,
   because it is the same kind of fact about the row rather than a warning. */
.pr-ex-reason { font-family: var(--mono); font-size: 11.5px; line-height: 1.45; color: var(--bone); margin-top: 3px; }
.pr-ex-run { font-family: var(--mono); font-size: 11.5px; line-height: 1.45; color: var(--muted); }
.pr-ex-act { justify-self: start; margin-top: 5px; }
@container promo (min-width: 520px) {
  .pr-exrow { grid-template-columns: minmax(0, 1fr) auto auto; column-gap: 16px; align-items: center; row-gap: 0; }
  .pr-ex-act { justify-self: end; margin-top: 0; }
}

/* MUS-443: THE WHEN COLUMN GOT WORSE BECAUSE ITS CONTENT GOT SHORTER, which is not obvious and is
   why this rule exists rather than something tidier. The old value ended in "Asia/Colombo", a long
   token that reserved about 112px under table-layout:auto. Replacing it with a real zone name,
   "AEST" or "GMT+5:30", let the column collapse to 83px and hand the width to its neighbours, so
   the DATE began breaking across two lines, which it never did before.
   Measured at 375px on both builds in one browser, same 74 rows: 56 of 60 cells wrapped to four
   lines or more against 16 of 60, and the table grew 1251px.
   The answer is NOT to pad the string back out. A floor stops the column collapsing below what a
   date needs, so a break can only fall between the date and the time rather than inside "Tue 28
   Jul". Scoped to the events list so no other table inherits a floor it did not ask for.
   120 IS MEASURED, NOT CHOSEN. A first attempt at 96 was applied, took effect, and was still
   too small: 48 of 60 cells at four lines or more. A sweep of nine floors at 375px found 104
   is the cliff where the date stops breaking, and 120 is where row heights return to exactly
   what they were before this ticket, tbody 6570px against 6570px. Below 104 the date breaks;
   above 128 it buys almost nothing and costs horizontal scroll. Page overflow stayed at 0 for
   every value tested, because the cost lands inside the table's own scroller. */
#ev-view-list .table-wrap th:nth-child(4),
#ev-view-list .table-wrap td:nth-child(4) { min-width: 120px; }

/* MUS-797: THE EVENTS LIST TITLE CLAMP, MOVED OUT OF AN INLINE style ATTRIBUTE.
   It was written into the markup by renderEvList in console/main.js, and an inline declaration
   beats every selector in this file, so no media query could widen or unclamp the title without
   !important. Scoped to #ev-view-list tbody rather than to a bare .name-txt: the artists list and
   the venues list render the same class from their own builders and neither is being changed. */
#ev-view-list tbody .name-txt {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 260px;
}

/* ============================================================================
   MUS-797: AT 720px AND BELOW THE EVENTS LIST STOPS BEING A TABLE.

   WHAT THIS REPLACES AND WHY THE PREVIOUS ANSWER WAS THE WRONG SHAPE. The rule here before this
   one made the ACTIONS CELL sticky to the right edge of the table's own scroller, which did bring
   Guest list from x=667 to x=134 and was a real improvement. It also left every other fact about
   the row behind a sideways drag, and one of those facts is the only thing that tells two rows
   apart. Measured against UAT: 45 of 45 publicly readable events have titles longer than six
   characters, median 24 and longest 81, and 14 of those 45 share their first six characters with
   another event. TWO PAIRS HAVE IDENTICAL FULL TITLES AT THE SAME VENUE and differ only by date
   ("Cold Sweat @ Irish Pub" at one venue on 2026-07-22 and on 2026-08-31). So giving the title
   its full width back cannot fix the identification problem: both rows would still read the same
   word for word. Only the DATE separates them, and the When column sat at x=506 in a 343px
   window, unreachable without a horizontal drag in the sticky state and in the unsticky one
   alike. The sticky rule did not create that. It exposed it.

   THE SHAPE. Each row becomes three bands: the title over two lines with the status badge in a
   fixed slot at the right, then the date and the venue, then the actions. Every decision below is
   a decision rather than syntax, so each one is written down.

     NO overflow-x: hidden ON .table-wrap, EVER. table { min-width: 0 } is what removes the
     scroll, by letting the table be as narrow as the phone instead of 620px. Hiding the overflow
     would remove the SYMPTOM and blind the mobile gate to the next regression, which is MUS-703
     exactly: console.css hides the console's own horizontal overflow at this width, so
     documentElement.scrollWidth reads 375 whatever the content does.

     THE STATUS BADGE IS POSITIONED, NOT IN FLOW, and the title reserves 112px for it. Flexbox
     cannot start a new band without a 100% flex-basis, and a 100% basis item consumes its whole
     line, so an in-flow badge either steals the date's band from the venue or takes a band of its
     own and pushes the date down to a fourth. Both were built and both broke. 112 is the measured
     worst case, not a round number: "Private link" renders at 104.08px, the widest of the five
     labels evListStatusPill can produce (On the map 89.73, In review 82.56, Draft 53.88, Past
     46.70), and the remainder is the gap to the badge.

     .pill.feat IS HIDDEN AT THIS WIDTH. Featured is a paid-promotion state, fifth in priority
     behind the five above, and it is visible on the Promotions tab and on the event itself.
     Keeping it here makes the badge slot two labels wide and non-deterministic, and the two-badge
     row is what pushed the date into a fourth band.

     THE TYPE COLUMN IS HIDDEN. The coloured tile at the head of the row already encodes it.

     VENUE GETS flex: 1 1 0 AND AN ELLIPSIS. THE DATE DOES NOT. A basis of 0 is what keeps the
     venue on the date's line instead of orphaning it onto one of its own, and the venue is what
     clips when the line runs out, because THE DATE IS THE DISAMBIGUATOR and must never be
     touched. #ev-view-list .table-wrap td:nth-child(4) { min-width: 120px } above is outside any
     media query and is load-bearing for this band: it is the floor that stops the date breaking
     across two lines (MUS-443 measured it at 104 and set it at 120).

     padding-left, NEVER margin-left, ON ANY ITEM WITH A 100% BASIS. A margin sits OUTSIDE the
     basis, so 100% plus a margin is wider than the line; the first pass overflowed the wrap by
     exactly the 21px it had been given. 39px is the thumbnail (30px plus its 9px margin) and 33px
     is the same figure less the 6px of padding .lnk already carries, so all three bands line up
     on the title text rather than on the tile.

   WHAT IT COSTS, ruled acceptable by the Design Lead. The per-column filter row goes at this
   width. #ev-search above the table already searches title, venue, artists and status, and every
   filter except Title was behind the same horizontal scroll anyway. The Title sort header
   survives as a full-width 44px control.

   THE DEAD RESET IS NOT HERE, and this is the one place this rule differs from the direction as
   written. The direction opened by setting position: static, box-shadow: none and background:
   none on th:last-child and td.td-actions, which undoes the sticky block it also replaces, so all
   three declarations are dead the moment that block is deleted. One of them is worse than dead:
   #ev-view-list .table-wrap td.td-actions scores (1,2,1) against tr.row-clickable:hover td at
   (0,2,2), so background: none WINS over the row hover tint and the actions cell alone would stay
   untinted while the rest of the row lit up. Measured on a pointer at 720px with that declaration
   present: the actions band computes rgba(0, 0, 0, 0) while the title band beside it computes
   rgba(245, 197, 24, 0.07). Without it both compute rgba(245, 197, 24, 0.07).
   ============================================================================ */
@media (max-width: 720px) {
  #ev-view-list table { min-width: 0; width: 100%; }

  #ev-view-list thead tr { display: block; }
  #ev-view-list thead th { display: none; }
  #ev-view-list thead tr:first-child th:first-child { display: block; width: 100%; min-height: 44px; }
  #ev-view-list thead tr.col-filters { display: none; }

  #ev-view-list tbody tr {
    position: relative; display: flex; flex-wrap: wrap; align-items: baseline;
    column-gap: 7px; row-gap: 3px;
    padding: 10px 12px; border-bottom: 1px solid var(--line);
  }
  #ev-view-list tbody tr:last-child { border-bottom: 0; }
  #ev-view-list tbody td { display: block; padding: 0; border-bottom: 0; }

  #ev-view-list tbody td:nth-child(2) { display: none; }                 /* type: the tile says it */
  #ev-view-list tbody td:nth-child(1) { flex: 1 0 100%; order: 1; padding-right: 112px; }
  #ev-view-list tbody td:nth-child(4) { order: 2; padding-left: 39px; }
  #ev-view-list tbody td:nth-child(3) { flex: 1 1 0; min-width: 0; order: 3; color: var(--muted);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  #ev-view-list tbody td:nth-child(3)::before { content: '\00B7'; color: var(--line); margin-right: 7px; }
  #ev-view-list tbody td.td-actions   { flex: 1 0 100%; order: 4; padding-left: 33px; }

  #ev-view-list tbody td:nth-child(5) { position: absolute; top: 10px; right: 12px; }
  #ev-view-list tbody .pill.feat { display: none; }

  /* overflow-wrap: anywhere IS LOAD BEARING AND IT IS NOT A TIDY-UP OF word-break. THE TITLE IS A
     FLEX ITEM HERE (flex: 1) and a flex item is never sized below its MIN-CONTENT width, which an
     unbroken token sets to the whole token. So one title with no spaces in it widens the table
     inside .table-wrap and every row's status badge and actions band go back behind the sideways
     drag this whole block exists to remove. Measured at 375x667 with the console's own fonts
     asserted loaded, .table-wrap scrollWidth against clientWidth on a single-token title:
       20 chars 343/343   22 chars 350/343   30 chars 414/343   40 chars 493/343
       160 chars 1448/343, and 160 is e-title's own maxlength, so it is reachable rather than
       hypothetical. With this declaration every one of those five reads 343/343 and the badge
       stays at 347.00.
     THE TWO SPELLINGS ARE NOT INTERCHANGEABLE. overflow-wrap: break-word wraps the token VISUALLY
     and leaves the min-content contribution at the full token width, so the row overflows exactly
     as far as it does with no rule at all; measured here as 414/343 at 30 characters, identical to
     the unfixed stylesheet. Only overflow-wrap: anywhere and word-break: break-word reduce
     min-content. min-width: 0 does not fix it either, also measured at 414/343.
     NONE OF THE THREE INSTRUMENTS A GATE USUALLY REACHES FOR CAN SEE THIS. documentElement
     .scrollWidth reads 375 throughout (MUS-703, this file clamps html at this width) and no
     bounding rectangle moves, because .table-wrap scrolls inside itself. The wrap's own
     scrollWidth against its clientWidth is the only instrument that sees it.
     The buyer's sheet carries the same declaration on #mu-bk h2 and .sub for the same reason
     (public/lib/booking-sheet.js), arrived at there first and worth keeping in step. */
  #ev-view-list tbody td .name-txt {
    white-space: normal; max-width: none; flex: 1; overflow-wrap: anywhere;
    display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
  }
  #ev-view-list tbody .name-cell { align-items: flex-start; }

  /* THE THREE ROWS THAT ARE NOT EVENTS. Loading, the empty state and a failed load all render as
     one <td colspan="6">, which is nth-child(1) and would otherwise take the title band's 112px
     reservation for a badge that is not there and lose a quarter of its width to it. Declared
     after that rule and at the same specificity, so source order settles it. */
  #ev-view-list tbody td[colspan] { flex: 1 0 100%; order: 0; padding-right: 0; }
}

/* MUS-797: the guest list's own scope bar carries the event's name as a real heading, so the
   screen a person lands on says which event they are looking at. The scope bar was built for a
   back button and a note (MUS-142) and had never held a heading, so a bare h3 arrived with the
   browser's default margins and pushed the bar open by a line and a half. Matched to .main-hd h3
   rather than invented, because it is the same thing in the same place at the top of a view. */
.scope-bar h3 { font-family: var(--disp); font-size: 18px; letter-spacing: -.01em; color: var(--ink); margin: 0; }


/* ============================================================================
   MUS-612 / MUS-613: the Ticketing tab, its step rail and the two built steps.

   THE RAIL IS DECLARED SEPARATELY FROM #section-bulk .b-step ON PURPOSE, and this is the
   one place in this file where duplicating a look is the right call rather than the drift
   hazard the .sec-label comment above warns about. The two differ in the two ways MUS-612
   names, and both differences are behavioural rather than cosmetic:

     THE CELLS ARE BUTTONS HERE. In bulk they are divs because bulk upload is a PIPELINE:
     you cannot review a file you have not uploaded, so three of its four transitions are
     conditional and tappable cells would be three dead controls. Ticketing setup is not a
     pipeline. Capacity, price, terms and the door team are INDEPENDENT settings on a record
     that already exists, so every cell is a live destination.

     IT STAYS VISIBLE AT 375px, where the bulk rail is display:none below 720px. That rule
     exists because the bulk rail is not navigation and cost 57px of the first screen. This
     one IS navigation, and hiding it on the only viewport that matters would answer the
     request by removing it.

   Values are deliberately identical to the bulk rail: 11px 700 uppercase at .04em, 20px
   circle, amber for where you are, green with a check for what is done. 11px is the floor
   from MUS-244 and is not negotiable downward. */
.tk-steps { display: flex; gap: 0; align-items: stretch; background: var(--dark); border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); overflow-x: auto; overflow-y: hidden; -webkit-overflow-scrolling: touch; scrollbar-width: none; }
.tk-steps::-webkit-scrollbar { display: none; }
/* THE WORSE OFFENDER WAS THE SILENT ONE. The tab strip above this has carried a fade since it
   was written; this rail needs 610px in the 347 a phone leaves, hides its scrollbar for the
   same reason, and had nothing at all saying there was more to the right. Five stages with two
   of them off screen and no edge treatment reads as five stages of which three exist.
   SAME GRADIENT AND SAME 26px AS .editor-tabs, AND DELIBERATELY NOT THE SAME BREAKPOINT. This
   rule said 720px to match the tab strip and that left the rail overflowing BARE across 72
   widths, 721px to 792px, which three reviewers measured independently. The two strips need
   different widths and so cannot share a number: at 721px the shell gains its 208px sidebar and
   the panel drops from 692px to 469px, which is over what the tabs need and under what the rail
   needs. Measured with the shipped faces, at 375px the rail needs 610 in 347 and the five tabs
   need 477 in 347 with the Ticketing tick showing, or 463.09 without it; from 721px up those
   same two numbers hold, because neither strip's content width depends on the viewport, and the
   panel passes 542 at 794px. Hence 793. Re-measured 2026-09-11: at 793 the panel is 541 and the
   rail 542.39, at 794 the panel is 542 and the rail stops scrolling.
   542 IS A CONTENT NUMBER AND IT WILL ROT, AND SO IS 610, AND SO ARE BOTH TAB FIGURES. 542 is
   five stage labels at 11px with a 20px circle each; 610 is the same five cells against the
   122px min-width the 720px block gives them. Change a label, add a stage, change the cell
   min-width, change the type face, or change the sidebar from 208px, and every number in this
   comment and the one above it is wrong.
   NOTHING IN THE SUITE FAILS WHEN ANY OF THEM GOES STALE, and that is the standing hole rather
   than a note in passing. They are measured by hand in a browser and written down here, so the
   only thing keeping them true is somebody choosing to re-measure. A rail cell's glyph does NOT
   affect this: .n is a fixed 20px circle, asserted rather than assumed, so a tick, an
   exclamation and a digit all give 610 at 375px.
   THE FADE ALSO SHOWS WITH NOTHING HIDDEN, from 638px to 720px, which is 83 widths. Written
   here as 637 and reported by a gate as 637 to 720 and 793, 85 widths; both counted two widths
   where something IS hidden. At 637 the panel is 609 against the rail's 610, and at 793 it is
   541 against 542.39, so the fade at each of those is doing its job on a single pixel rather
   than idling. Swept every integer width from 300 to 1300 with the shipped face asserted loaded.
   That is the price of a
   viewport rule standing in for "can this actually scroll", which CSS cannot ask. It was already
   the price before this change and the tab strip pays it too. The alternative is a second and
   third magic number to bracket the band, which rots in two directions instead of one.
   .editor-tabs KEEPS 720 AND HAS THE SAME GAP, and the measured band is 721px to 728px rather
   than the 721 to 727 written here, and only while the Ticketing tick shows. The full
   measurement and what the 8px actually costs are with the strip itself, above .editor-tabs,
   rather than repeated here, because two copies of a number are how the four answers above
   happened. Not changed there either, and for the same reason: that strip is shared by the
   artist, venue and event editors with different tab counts, so its number is not this
   feature's to pick.
   A mask rather than the ::after the tab strip uses, because this rail is the scroller itself
   and an absolutely positioned overlay inside a scroller scrolls away with the content. */
@media (max-width: 793px) {
  .tk-steps { -webkit-mask-image: linear-gradient(90deg, #000 calc(100% - 26px), transparent); mask-image: linear-gradient(90deg, #000 calc(100% - 26px), transparent); }
}
/* padding is 12px rather than 11px because 11px on an 11px line plus the 20px circle
   resolves to a 42px cell, two under the 44px tap floor. Measured by mobile-qa at 375px
   as 122x42; these are the only tappable controls in the pane that were under it. */
.tk-step { flex: 1 0 auto; min-width: 108px; display: flex; align-items: center; justify-content: center; gap: 8px; padding: 12px; font-size: 11px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--muted); background: none; border: none; border-right: 1px solid var(--line); cursor: pointer; white-space: nowrap; font-family: inherit; }
.tk-step:last-child { border-right: none; }
.tk-step .n { width: 20px; height: 20px; border-radius: 50%; border: 2px solid var(--muted); display: flex; align-items: center; justify-content: center; font-size: 11px; flex-shrink: 0; }
.tk-step.on { color: var(--amber); background: rgba(245,197,24,.07); }
.tk-step.on .n { border-color: var(--amber); color: var(--amber); }
.tk-step.done { color: var(--ok-lt); }
.tk-step.done .n { border-color: var(--ok-ink); background: var(--ok-ink); color: #fff; }
/* MUS-749: A STAGE THAT HAS STOPPED BEING VALID, which is a different fact from a stage
   nobody has filled and must not look like one. It was green, something was cleared, and it
   went back. A blank cell here sends an organiser hunting for a step they already did.
   Only reachable once a row exists, because nothing can stop being green that never was. */
.tk-step.bad { color: var(--terra-lt); }
.tk-step.bad .n { border-color: var(--terra); background: var(--terra); color: #fff; }
/* Where you are wins over both, because a cell cannot be both where you are and behind you. */
.tk-step.on.bad { color: var(--amber); }
.tk-step:focus-visible { outline: 2px solid var(--amber); outline-offset: -2px; }
.tk-step[disabled] { cursor: default; opacity: .55; }
/* MUS-616: the tab is disabled in substance, everything inside it is taken away, and it is
   still reachable so it can say why. Dimmed rather than hidden, because a tab that vanishes
   when a field is filled reads as a bug. */
.editor-tab.is-blocked { opacity: .5; }

.tk-body { padding: 18px 0 4px; }
.tk-hd { font-family: var(--disp); font-size: 15px; color: var(--ink); margin-bottom: 12px; }
.tk-step-pane.hidden { display: none; }
.tk-soon { font-size: 12.5px; line-height: 1.55; color: var(--muted); max-width: 52ch; }

/* The capacity source line. It is a hint rather than a warning: a pre-filled number is help,
   not a problem, and MUS-613 requires it to say where the number came from. */
.tk-body .hint { font-size: 12px; line-height: 1.5; color: var(--muted); margin-top: 6px; }
.tk-body .hint.from-venue { color: var(--ok-lt); }

/* One choice of three, per event. Buttons rather than radios so the description is part of
   the target: a person choosing how they take money is reading, not skimming. */
.tk-choice { display: grid; gap: 10px; max-width: 560px; }
.tk-opt { display: block; text-align: left; padding: 13px 15px; background: var(--card); border: 2px solid var(--edge); cursor: pointer; font-family: inherit; color: var(--ink); }
.tk-opt:hover:not([disabled]) { border-color: var(--muted); }
.tk-opt[aria-checked="true"] { border-color: var(--amber); background: rgba(245,197,24,.07); }
.tk-opt[disabled] { cursor: default; opacity: .6; }
.tk-opt-t { display: block; font-size: 13.5px; font-weight: 700; margin-bottom: 3px; }
.tk-opt-d { display: block; font-size: 12px; line-height: 1.45; color: var(--muted); }

.tk-warn { font-size: 12.5px; line-height: 1.5; color: var(--muted); margin-top: 12px; max-width: 52ch; }
.tk-warn.locked { color: var(--terra-lt); }

/* ---- MUS-84: the shut Ticketing tab says what to do and SHOWS the control ----
   A picture of the dropdown rather than a sentence about it, because the reader is looking for
   a control they have not found yet and a description is one more thing to translate.
   ZERO RADIUS AND A REAL BORDER, like every other surface in this console. It has to read as a
   diagram of the control, never as a second control: nothing in it is clickable, it is
   aria-hidden, and the instruction above it and the button below it carry the whole meaning for
   anybody who is not looking at it. */
/* A READ-ONLY FIELD HAS TO LOOK READ-ONLY, AND UNTIL NOW NOTHING IN THIS PRODUCT DID.
   Mobile QA measured the greyed price box PIXEL IDENTICAL to an editable one, sha256 equal on both
   crops, and found ZERO readonly selectors in any stylesheet under public/. The code had been
   setting input.readOnly for three separate features and every one of them was invisible. "Greyed,
   showing the real ticketing value" is the CEO's own decision on this feature and it was the half
   that did not exist.
   THE CURSOR MATTERS AS MUCH AS THE COLOUR. A text caret over a field that refuses typing is the
   part that reads as broken rather than as locked, so the pointer changes too.
   NOT :disabled. A disabled input is skipped by the keyboard and is not read out, so somebody
   navigating by keyboard would never learn the field or its value exist. */
input[readonly], textarea[readonly] {
  background: var(--paper);
  color: var(--muted);
  border-style: dashed;
  cursor: default;
}
/* A LOCKED FIELD STILL TAKES FOCUS, AND IT MUST NOT LOOK EDITABLE WHILE IT HAS IT. Read-only
   rather than disabled is deliberate, so the keyboard reaches it and a screen reader reads it
   out. The focus rule then painted the same amber border an editable box gets, so tabbing into a
   locked field made it indistinguishable from one that accepts typing.
   The ring stays, because a focused field must be findable; it is the muted edge, not the amber
   one, so the field says "you are here" without saying "type". */
input[readonly]:focus, textarea[readonly]:focus {
  border-color: var(--muted);
  border-style: dashed;
}
/* A LOCKED FIELD IS GREYED, NEVER EMPTIED. Hiding placeholder text on every read-only input is a
   global answer to a one-field problem, and it reaches screens nobody tested: an empty locked box
   explains nothing to the person looking at it. Where one field genuinely needs no placeholder,
   the script that locks it clears that attribute itself.
   THE RULE IS STATED AND NOT SPELLED. A test asserts the selector is absent from this file, and a
   comment quoting it is how a fix trips the check that guards it. */

.tk-blocked-line { font-size: 13.5px; font-weight: 700; color: var(--ink); margin: 0 0 12px; }
.tk-blocked-mock { display: inline-block; border: 2px solid var(--edge); background: var(--card); padding: 10px 0 0; margin: 0 0 14px; min-width: 216px; }
.tk-blocked-mock-label { display: block; font-size: 11px; letter-spacing: .08em; text-transform: uppercase; color: var(--muted); padding: 0 12px 8px; }
.tk-blocked-mock-opt { display: block; font-family: var(--mono); font-size: 12.5px; color: var(--muted); padding: 7px 12px; border-top: 1px solid var(--edge); }
.tk-blocked-mock-opt.is-pick { background: var(--amber); color: #0F0C07; font-weight: 700; }
.tk-blocked-mock[hidden] { display: none; }
/* THE BUTTON SAT FLUSH AGAINST THE MOCKUP AT 0.0px, measured at 375, because an inline-block
   diagram and a button are both inline-level and simply ran on. It is its own block with room
   above it, which is what the code beside it already claimed.
   AND IT CLEARS THE FIXED ACTION BAR. Mobile QA measured #ev-actionbar, fixed at 76px tall,
   covering this control at 5 of 38 scroll offsets, where a centre tap landed on Cancel. A button
   whose press does something else is worse than one that is hard to reach. */
#tk-blocked-go { display: block; margin-top: 14px; }
.tk-warn.locked { padding-bottom: 88px; }

.tk-offer { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-top: 14px; padding: 11px 14px; background: var(--card); border: 2px solid var(--edge); font-size: 12.5px; line-height: 1.5; color: var(--muted); }
.tk-offer.hidden { display: none; }

.tk-done { display: flex; align-items: center; gap: 14px; padding: 13px 15px; background: var(--card); border: 2px solid var(--edge); margin-bottom: 4px; }
.tk-done[hidden] { display: none; }
.tk-done-txt { font-size: 13px; font-weight: 700; color: var(--ok-lt); letter-spacing: .04em; text-transform: uppercase; }

.tk-msg { font-size: 12.5px; line-height: 1.5; color: var(--muted); margin-bottom: 14px; }
.tk-msg:empty { display: none; }
.tk-msg.err { color: var(--terra-lt); }
.tk-msg.ok { color: var(--ok-lt); }

/* MUS-707: .tk-actions is GONE from the ticketing pane and its buttons live in the editor's own
   action bar. The rule is kept as a comment rather than deleted silently, because the class name
   appears in tests and in two other tickets, and a reader finding neither the markup nor an
   explanation would reasonably assume it was lost by accident. */
.tk-switch { margin-bottom: 4px; }

/* ---- MUS-614 / MUS-615: the money, the two addresses and the door ---- */
.tk-hd.sub { font-size: 12px; letter-spacing: .08em; text-transform: uppercase; color: var(--muted); margin: 22px 0 10px; font-family: inherit; font-weight: 700; }
.tk-warn[hidden] { display: none; }
.tk-body textarea { width: 100%; font-family: inherit; resize: vertical; }

/* A chip is a value that exists, with the only control that can remove it sitting inside it.
   Nothing here saves on blur: MUS-615 requires an explicit action, because a half-typed address
   saved silently is a notification that vanishes. */
/* min-width:0 IS THE WHOLE FIX AND IT IS NOT COSMETIC. .tk-chip span is white-space:nowrap, so
   its flex automatic minimum size is the WHOLE address; inside .form, which is a grid, the
   chip's max-width:100% cannot resolve against an indefinite track, so the track expands to
   min-content and drags the pane to 635px against a 375 viewport. And because this console sets
   overflow-x:hidden at 720px and below, the overflow is UNREACHABLE: the labels, the input and
   the Add button are cut off mid-sentence with no way to scroll to them. Measured by bisection:
   a 38 character address overflows, 31 does not. The Door pane escaped it only because the chip
   row is a block child there rather than a grid item. */
.tk-chiprow { display: flex; flex-wrap: wrap; gap: 8px; margin: 4px 0 10px; min-width: 0; max-width: 100%; }
.tk-chiprow:empty { display: none; }
.tk-chip { display: inline-flex; align-items: center; gap: 8px; padding: 7px 10px; background: var(--card); border: 2px solid var(--edge); font-size: 12.5px; color: var(--ink); max-width: 100%; min-width: 0; }
/* MUS-713 F6. THE ADDRESS WRAPS RATHER THAN ELLIPSISING, so the organiser can read what they
   typed. It was white-space:nowrap with overflow hidden and no title attribute: measured
   scrollWidth 291 into clientWidth 240, not scrollable, and a touch screen has no hover, so
   an address over about 31 characters could only be deleted and retyped to check it. A title
   attribute is the usual answer and is the wrong one here for exactly that reason.
   THIS IS ALSO SAFER FOR THE OVERFLOW THE OLD RULE CAUSED, not merely equal to it. With
   overflow-wrap:anywhere the chip's min-content width is one character rather than the whole
   address, so the flex minimum that once dragged the Terms pane to 606px cannot arise at all.

   CORRECTED MUS-713 R4 F3, AND THE CORRECTION IS PLACED AGAINST THE SENTENCE RATHER THAN OVER
   IT. What stood here said ".form > .fld { min-width: 0 } stays: it is the rule that reaches the
   grid TRACK, which is where the overflow actually was, and this one cannot replace it." That
   is a claim nobody had watched change a number, and it is wrong. Ablated one declaration at a
   time at 375x812 with an 81 character alert address and BOTH overflow axes lifted, document
   scrollWidth reads:
       as shipped                                    375
       minus .form > .fld min-width                  375
       minus .tk-chiprow min-width                   375
       minus .tk-chip min-width                      375
       minus .tk-chip span overflow-wrap             375
       minus .tk-chip span min-width                 375
       minus BOTH declarations on .tk-chip span      394
       minus all five                                397
   SO THE LOAD-BEARING THING IS A PAIR, NOT A RULE, and neither half of it is the one the old
   sentence named. min-width:0 and overflow-wrap:anywhere on .tk-chip span each stop the same
   push on their own, so removing either alone moves nothing; remove both and the pane overflows
   by 19px with no way to scroll to it. Both stay, and the score is a claim about the PAIR.
   THE OTHER THREE STAY TOO, AND ARE NOT SCORED AS COVERAGE THEY DO NOT HAVE. .form > .fld,
   .tk-chiprow and .tk-chip each move no number while the pair above is present, and .form > .fld
   is worth 3px only once every other guard is gone. They are kept because they are correct in
   their own right and because .form > .fld governs every field in four editors, which is a blast
   radius nobody has measured. Nothing here claims they are what the fix rests on. */
.tk-chip span { min-width: 0; white-space: normal; overflow-wrap: anywhere; }
.tk-chip.gone { opacity: .5; text-decoration: line-through; }
/* 44px, because it is a real target on a phone and a 12px cross is not. The visible glyph stays
   small; the hit area is the padding.
   THE COMMENT ABOVE SAID 44 AND THE CODE SAID 30, which is this project's own recorded defect
   class written by the person who records it. Two gates measured it independently. */
.tk-chip button { min-width: 44px; min-height: 44px; padding: 6px; margin: -11px -9px -11px 0; background: none; border: none; color: var(--muted); font-family: inherit; font-size: 15px; line-height: 1; cursor: pointer; }
.tk-chip button:hover { color: var(--terra-lt); }
.tk-chip button:focus-visible { outline: 2px solid var(--amber); }

.tk-addrow { display: flex; gap: 8px; align-items: stretch; max-width: 520px; }
.tk-addrow input { flex: 1; min-width: 0; }
.tk-addrow .btn { flex: 0 0 auto; }

.tk-cando { margin: 18px 0 14px; padding: 14px 16px; background: var(--card); border: 2px solid var(--edge); }
.tk-cando-h { font-size: 11px; letter-spacing: .1em; text-transform: uppercase; color: var(--muted); margin: 0 0 6px; font-weight: 700; }
.tk-cando-h + ul { margin: 0 0 14px; padding-left: 18px; }
.tk-cando ul:last-child { margin-bottom: 0; }
.tk-cando li { font-size: 12.5px; line-height: 1.65; color: var(--ink); }
.tk-cando ul.no li { color: var(--muted); }

/* ---- MUS-616: the switch ---- */
.tk-switch { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; margin-top: 18px; padding: 14px 16px; background: var(--card); border: 2px solid var(--edge); }
.tk-switch[hidden] { display: none; }
.tk-switch.on { border-color: var(--amber); background: rgba(245,197,24,.07); }
/* MUS-749: the DOOR stage reached with work still outstanding behind it. It names the stage and
   prints that stage's OWN sentence, and the primary control beside it carries the person there.
   It is never a disabled switch with an explanation, which is the defect that was reported. */
/* THE GROUND IS THE EDGE COLOUR AT LOW ALPHA, not a hex nobody can trace back to a token.
   It was #26130f, an opaque brown coined here, and this console already had three near
   identical browns doing one job with none of them more correct than the others, which is
   an open item in the brand guide. A wash cannot join that set: there is exactly one right
   answer for --terra at 12 per cent and a reviewer can check it without a colour picker.
   SAME CHANNELS AS .tk-cancel SEVENTY LINES DOWN, rgba(196, 81, 58, .06), which is --terra
   and is this console's failure wash already. The alpha is SOLVED rather than picked: over
   --paper #0F0C07 it composites to rgb(37,20,13) against the approved rgb(38,19,15), so the
   appearance the CEO signed off is kept and only the sourcing changes. */
.tk-switch.bad { border-color: var(--terra); background: rgba(196, 81, 58, .12); }
.tk-switch.bad .tk-switch-t { color: var(--terra-lt); }
.tk-switch-body { flex: 1 1 220px; min-width: 0; }
.tk-switch-t { font-size: 13px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--ink); }
.tk-switch-d { font-size: 12.5px; line-height: 1.5; color: var(--muted); margin-top: 4px; }
.tk-switch .btn { flex: 0 0 auto; }

/* ---- MUS-621 / MUS-622: the bookings list ---- */
.tk-bookings { margin-top: 22px; padding-top: 18px; border-top: 1px solid var(--line); }
.tk-bookings[hidden] { display: none; }
.tk-bk-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.tk-bk-actions { display: flex; gap: 8px; flex-wrap: wrap; }
.tk-bk-sum { font-size: 12.5px; line-height: 1.5; color: var(--muted); margin-bottom: 12px; }
/* MUS-713 F3: a read that failed is not a list that is empty, and it does not read as one.
   Same terracotta as .tk-bk-msg.err directly below, so this screen has one failure colour. */
.tk-bk-sum.err { color: var(--terra-lt); }
/* The same fact in the door list, where an empty row of chips used to be the only signal. */
.tk-load-err { display: block; font-size: 12.5px; line-height: 1.5; color: var(--terra-lt); }
.tk-bk-msg { font-size: 12.5px; line-height: 1.5; color: var(--muted); margin-bottom: 10px; }
.tk-bk-msg:empty { display: none; }
.tk-bk-msg.err { color: var(--terra-lt); }
.tk-bk-msg.ok { color: var(--ok-lt); }
.tk-bk-rows { display: grid; gap: 8px; }
.tk-bk-row { display: flex; align-items: flex-start; gap: 12px; padding: 11px 13px; background: var(--card); border: 2px solid var(--edge); }
.tk-bk-row.paid { border-color: rgba(143,227,179,.4); }
/* The tick box is on the LEFT of every row that CAN be marked paid, and a row already paid has
   none at all. A disabled tick box on a settled row reads as something you might yet do. */
/* 44px, AND THIS IS THE CONTROL THAT SENDS AN UNRECALLABLE EMAIL, so it is the last one in the
   product that should be hard to hit. The box stays 18px because a 44px checkbox looks wrong;
   the LABEL is the target and it is 44px. Same pattern as #section-bulk .tick 500 lines up,
   which was already right and was not copied. */
.tk-bk-row label.tick { display: flex; align-items: center; justify-content: center; min-width: 44px; min-height: 44px; margin: -11px -4px -11px -11px; cursor: pointer; flex: 0 0 auto; }
.tk-bk-row input[type="checkbox"] { width: 20px; height: 20px; accent-color: var(--amber); cursor: pointer; }
.tk-bk-main { flex: 1 1 auto; min-width: 0; }
/* MUS-738: THE BUYER'S NAME IS OFTEN AN EMAIL ADDRESS, and an email address is one unbroken token.
   lib/booking-sheet.js falls back to the address when the sign-in method supplies no full name,
   which is every method except Google: password, one-time code, and the magic link chosen on
   MUS-654. So this is the default path rather than an edge case.
   WITHOUT THE BREAK the name sets the min-content width of .tk-bk-main, .tk-bk-row grows instead
   of wrapping, and the row runs off the side of a phone. Measured at 375px with both overflow axes
   lifted: an ordinary 35 character address put the row 30px past the edge and a 196 character name
   1,225px past it. The sibling below has carried this rule since it was written, which is why the
   longer email in the meta line never overflowed: same row, same width, one declaration apart.
   DO NOT "MODERNISE" THIS TO overflow-wrap: break-word. It looks like the same thing and is not:
   overflow-wrap:break-word wraps the text but leaves the MIN-CONTENT contribution at the width of
   the unbroken token, so the flex row is sized by it exactly as before. Measured as a mutant
   against the guard in tests/e2e/console-booking-row.spec.js: overflow-wrap:break-word failed both
   overflow tests, at the same 30px and 1,225px as no rule at all. overflow-wrap:anywhere would
   work, since it is what word-break:break-word means; break-word does not. */
.tk-bk-name { font-size: 13.5px; font-weight: 700; color: var(--ink); word-break: break-word; }
.tk-bk-meta { font-size: 12px; line-height: 1.55; color: var(--muted); margin-top: 3px; word-break: break-word; }
.tk-bk-state { flex: 0 0 auto; font-size: 11px; letter-spacing: .08em; text-transform: uppercase; color: var(--muted); padding-top: 2px; }
.tk-bk-state.is-paid { color: var(--ok-lt); }
.tk-bk-state.is-cancelled { color: var(--terra-lt); }
/* MUS-719 IS THE REASON THIS RULE EXISTS, and without it the ticket was only half done. The class
   was being emitted with no rule behind it, so Lapsed computed to the base --muted and a place
   already RELEASED, possibly resold, was the same pixels as a place still being HELD. The ticket's
   own premise is "a lapsed booking looked exactly like a live one": the word changed and the glance
   did not, and the state column is the only thing that varies as an organiser scans the list.
   BARE TEXT, NOT A CHIP, deliberately. Paid and Cancelled are bare text here too, so giving one
   row of four a ground would break the rhythm of the list to fix a colour problem.
   --warn-lt rather than --warn: see the token block at the top. --warn is 1.07:1 from --muted,
   which is the same colour-alone coin flip that was measured to fail elsewhere this session. */
.tk-bk-state.is-lapsed { color: var(--warn-lt); }
.tk-bk-bar { position: sticky; bottom: 0; z-index: 3; display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; margin-top: 12px; padding: 12px 14px; background: var(--dark); border: 2px solid var(--amber); }
.tk-bk-bar[hidden] { display: none; }
.tk-bk-bar span { font-size: 12.5px; color: var(--ink); }

/* MUS-628: THE ONE CONTROL ON THIS SCREEN THAT CANNOT BE TAKEN BACK.
   IT IS DRAWN AS A PLACE RATHER THAN AS A BUTTON. A red button loose at the foot of a list is
   pressed by somebody who meant to press Refresh; a bordered block with its own heading, its own
   count and a second press inside it is not. Same reason the delete control sits in the action
   bar on Details and nowhere else, MUS-707.
   TERRACOTTA AT 55 PER CENT ON A 6 PER CENT WASH, which is the console's failure colour used as
   an EDGE rather than as text: nothing here is a failure yet, and the wash is what stops the
   block reading as an error message the organiser has to clear. Every word inside it keeps the
   ordinary text colours, so the sentence is read rather than the box. */
.tk-cancel { margin-top: 20px; padding: 14px; border: 2px solid rgba(196, 81, 58, .55); background: rgba(196, 81, 58, .06); }
.tk-cancel[hidden] { display: none; }
.tk-cancel .tk-hd { margin-bottom: 8px; }
.tk-cancel-note { font-size: 12.5px; line-height: 1.5; color: var(--muted); margin: 0 0 12px; }
.tk-cancel-form { margin-top: 14px; }
.tk-cancel-form .fld label { display: block; margin-bottom: 6px; }
.tk-cancel-acts { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 12px; }


/* ==========================================================================================
   MUS-749: THE LIVE TAB. The switched-on overview, and it edits nothing: every Edit on it
   deep links the wizard at its own stage, which is the one editor the CEO asked for.
   375 FIRST, and the two places that matter are called out beside their rules.
   ========================================================================================== */
/* MUS-815: THE TWO LAYERS OF THE TICKETING TAB. .tk-layer CARRIES NO display DECLARATION OF ANY
   KIND, deliberately and permanently. The hidden attribute does the work through the user agent
   default and through the [hidden] rule near the top of this file; the moment a rule sets display
   here the attribute stops working and the next person reaches for !important. min-width:0 is the
   only thing it needs, so that a long unbroken token inside either layer cannot push the pane
   sideways. */
.tk-layer { min-width: 0; }
.tk-live { padding: 4px 0 4px; }
/* MUS-814: THE ONE PRIMARY CONTROL ON THE OVERVIEW, full width at 375px so it is a thumb target
   rather than a word. .btn already carries min-height:44px, which is the floor MUS-807 exists to
   protect; nothing here reduces it.
   IT SHARES A SLOT WITH .tk-live-empty AND THE TWO CAN NEVER BOTH BE FILLED: the button renders
   only when somebody has booked and the empty state only when nobody has. Same bottom margin as
   the empty state, which keeps the GAP below the slot at 16px either way.
   IT DOES NOT KEEP THE LAYER THE SAME HEIGHT AND THIS COMMENT USED TO CLAIM IT DID. Measured at
   375x667: the button is 44px, the empty state is 110.14, so the layer is 66.14px taller with
   nobody booked. The two start at the same coordinate, 383.39 in view mode; they do not end at
   one.
   EMPTY, IT MUST OCCUPY NOTHING. The slot is always in the markup and is filled by renderLive, so
   :empty is what keeps a 16px gap from appearing on an event with no bookings. The slot sits at
   the TOP of .tk-live-main now, directly under the banner, so that stray gap would open between
   the banner and the figures rather than above the fact rows, which is where this sentence used
   to point. The rule is unchanged; only what it protects moved. */
.tk-live-primary { margin-bottom: 16px; }
.tk-live-primary:empty { margin-bottom: 0; }
.tk-live-primary .btn { width: 100%; }
.tk-live-banner { padding: 12px 14px; border: 2px solid var(--edge); background: var(--card); margin-bottom: 16px; }
.tk-live-banner b { display: block; font-size: 13px; font-weight: 700; margin-bottom: 4px; line-height: 1.4; }
.tk-live-banner span { display: block; font-size: 12.5px; color: var(--muted); line-height: 1.55; }
/* BOTH BANNER GROUNDS ARE THEIR OWN EDGE COLOUR AT LOW ALPHA, for the reason given on
   .tk-switch.bad above. Was #0f1f16, a second green ground beside the #173a2a the bulk
   screens use, coined here and traceable to nothing. --ok-ink is rgb(42,127,73); at 16 per
   cent over --paper Chromium PAINTS rgb(19,30,17) against the approved rgb(15,31,22).
   THE NUMBER IS THE SAMPLED PIXEL, NOT THE ARITHMETIC, and the first version of this comment
   said 19,30,18 because it was worked out rather than looked at. The blend rounds down where
   the arithmetic rounds up. Sampled as the mode of a 20 by 20 clip inside the border, uniform
   at 400 of 400 pixels, at device pixel ratio 1 and again at 2.
   The alphas differ between the three states because the hues do: each one is solved to
   land on the appearance already approved, not chosen for tidiness. */
.tk-live-banner.selling { border-color: var(--ok-ink); background: rgba(42, 127, 73, .16); }
.tk-live-banner.selling b { color: var(--ok-lt); }
/* SWITCHED ON IS NOT SELLING. A paid event waits for an admin and is off the map until then, so
   this state is amber and says so rather than being drawn as a success. */
/* .attention IS THE NAME AND .waiting IS THE OLD ONE, KEPT ALIVE ON PURPOSE FOR ONE ROUND.
   THE CLASS WAS A TRAP. This amber tone carries TWO of the Live tab's five states, review and
   draft, and the draft one's own copy says nothing is waiting on anybody. A banner classed
   'waiting' that says nothing is waiting is a sentence and a class name contradicting each other
   in the same box. The three tones do NOT map one to one onto the states: liveState answers
   selling, review, draft, closed or off, and those five fold into three tones because a tone
   answers only "can this be booked", which has three useful answers. So the right name is a tone
   and not a state: selling, attention, off. COUNT CORRECTED after the state set grew from four
   to five; the three tone decision is unchanged and was never the thing that moved.
   BOTH SELECTORS ARE LIVE BECAUSE THE RENAME LANDS IN TWO FILES HELD BY TWO PEOPLE. The class is
   chosen in console/main.js, which is not mine this round. A rename here alone would strip the
   amber off the one banner that exists to say an event is NOT selling, on a money screen, and
   nothing in the suite would go red for it. So this is the expand half of expand-then-contract,
   which is the shape this project already requires of a migration: the new name works now, the
   old one keeps working, and neither ordering can break.
   THE CONTRACT HALF IS OWED AND IS A ONE LINE DELETE. Once main.js emits 'attention', drop the
   two .waiting selectors below. Leaving both is its own smaller trap and should not outlive the
   round that needed it. */
/* #C98A1E WAS --warn RETYPED AS A LITERAL, in two places, and #221a0c was a FOURTH brown two
   or three steps off the #241d0f this file already uses for an amber warning ground at
   lines 275, 306 and 365. Nobody can tell those two apart. --warn is rgb(201,138,30); at 11
   per cent over --paper Chromium PAINTS rgb(35,25,9) against the approved rgb(34,26,12).
   ALL THREE CHANNELS OF THE FIRST VERSION WERE WRONG. It said 36,26,10, which was arithmetic
   and not a measurement, and two reviewers reading the same screen returned two more answers
   one channel apart. Sampled the same way as the green above: the mode of a 20 by 20 clip
   inside the border, uniform at 400 of 400 pixels, at device pixel ratio 1 and again at 2. */
.tk-live-banner.attention,
.tk-live-banner.waiting { border-color: var(--warn); background: rgba(201, 138, 30, .11); }
.tk-live-banner.attention b,
.tk-live-banner.waiting b { color: var(--warn); }
.tk-live-banner.off b { color: var(--ink); }

.tk-live-cols { display: block; }
.tk-live-main { min-width: 0; }
.tk-live-side { margin-top: 20px; padding-top: 16px; border-top: 1px solid var(--line); }
/* MUS-814/MUS-813: .tk-live-sidehd IS DELETED WITH THE ELEMENT IT STYLED. All four of its
   branches were sentences about the guest list, which has moved into the main column, and what is
   left in the rail is a door to the setup and the switch: two controls that are not
   time-dependent, under a header that changed with the state, which would have been decoration
   that moves. */
.tk-live-acts { display: flex; flex-direction: column; gap: 8px; }
.tk-live-acts .btn { width: 100%; }
@media (min-width: 900px) {
  .tk-live-cols { display: grid; grid-template-columns: minmax(0, 1fr) 260px; gap: 28px; align-items: start; }
  .tk-live-side { margin-top: 0; padding-top: 0; border-top: 0; }
}

/* TWO COLUMNS AT BOTH WIDTHS, on purpose. Four at desktop leaves each figure about 90px of
   inner width against a display face, which is where a wrapped number came from. The asymmetry
   the brand asks for is carried by the headline cell's double width and its amber, never by a
   column count that changes under you. */
.tk-figs { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); border: 2px solid var(--edge); background: var(--card); margin-bottom: 16px; }
/* THE ROW RULE HANGS OFF THE TOP OF A CELL, NOT THE BOTTOM, and that is the fix rather than
   a tidy up. It was border-bottom on every cell with `:nth-last-child(1), :nth-last-child(2)`
   clearing the last two, which assumes the last two cells SHARE A ROW. They do not: cell 4
   is forced to span both columns directly above, so cell 3 sits in row 2 beside cell 2 and
   lost its border while cell 2 kept one. Measured at 375 and at 1280: both cells at the same
   top, Bookings border-bottom 1px, Taken 0px, so the hairline ran half way across row 2 and
   stopped. In a brand built on deliberate outlines that reads as a rendering fault.
   HUNG OFF THE TOP THE DEPENDENCY MOVED RATHER THAN WENT AWAY, and the first version of this
   paragraph claimed it could not happen again whatever the cell count. That is false. Counting
   from the end needs to know how the LAST row is made up; counting from the start needs every
   row after the first to be FULLY POPULATED. Cell 4 spans both columns, so at two columns the
   counts that fill every row are 1, 3, 4, 6 and 8, and 2, 5 and 7 leave the rule running half
   way across the last row: measured 172 of 343 at 375px and 368 of 736 at 1280.
   IT CANNOT HAPPEN ON THIS SCREEN, which is why this is a note rather than a rule change.
   renderLive() in console/main.js empties the grid and makes exactly FOUR unconditional
   figure() calls, and four is the only count that ships. THE FUNCTION IS NAMED RATHER THAN A
   LINE NUMBER ON PURPOSE: this paragraph has carried three different line numbers in one day,
   because every edit to renderLive moves them and nothing checks. Read that function before
   trusting this paragraph: a fifth figure, or one made conditional, puts a half width hairline
   back on the screen. */
.tk-fig { padding: 12px 14px; border-right: 1px solid var(--line); border-top: 1px solid var(--line); min-width: 0; }
.tk-fig:nth-child(1) { grid-column: 1 / -1; background: var(--dark); }
/* At two columns the fourth figure is alone on its row and an empty half reads as a dead box. */
.tk-fig:nth-child(4) { grid-column: 1 / -1; }
.tk-fig:first-child { border-top: 0; }
.tk-fig .k { display: block; font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .14em; color: var(--muted); }
/* A NUMBER NEVER WRAPS, AND THAT IS NOT THE SAME RULE AS "THIS LINE NEVER WRAPS".
   overflow-wrap on a display face broke 189,000 across two lines at desktop, which is unreadable
   and was invisible at 375 where the column is wider. The rule that prevents it is
   `overflow-wrap: normal`, which refuses a break inside a word. IT IS LOAD BEARING; do not
   "modernise" it to break-word or anywhere.
   MUS-749 H4: white-space: nowrap was doing something else entirely and doing it wrong. It
   refused the break after the CURRENCY CODE too, so "LKR 2,500" ran out of a 143px half-cell at
   146px of content and every larger amount ran further, up to 224px at LKR 10,000,000. The
   bounding rectangle never moved, so rectangles could not see it and only scrollWidth against
   clientWidth could. Breaking after "LKR" is legitimate and breaking inside "2,500" is not, which
   is exactly the pair `white-space: normal` with `overflow-wrap: normal` expresses.
   AND WRAPPING ALONE IS NOT ENOUGH, because "10,000,000" is one unbroken word wider than the cell
   at 26px whatever the wrap rule says. figureScale in lib/ticketing-setup.js decides the step and
   is unit tested; these two rules are what it selects. A size step rather than a break, because a
   number split across two lines is a different number to read. */
.tk-fig .v { display: block; font-family: var(--disp); font-size: 26px; line-height: 1.1; margin-top: 6px; font-variant-numeric: tabular-nums; white-space: normal; overflow-wrap: normal; word-break: normal; color: var(--ink); background: none; border: 0; padding: 0; text-align: left; }
.tk-fig .v.sm { font-size: 20px; }
.tk-fig .v.xs { font-size: 15px; }
.tk-fig:nth-child(1) .v { font-size: 32px; color: var(--amber); }
.tk-fig:nth-child(1) .v.sm { font-size: 26px; }
.tk-fig:nth-child(1) .v.xs { font-size: 20px; }
button.v { cursor: pointer; font-family: var(--disp); }
/* MUS-749 M1. OWED IS A BUTTON WHENEVER IT IS ABOVE ZERO, and it measured 146 by 29 against the
   44px floor with padding: 0 inherited from the figure it is styled as. A figure that is also a
   control has to be both, so the type stays and the TAP AREA is what changes: the flex box fills
   the cell, which also stops the 146px content sticking 3px out of a 143px cell.
   Specificity: `.tk-fig .v` above is 0,2,0 and would beat a bare `button.v`, so the class is
   carried here rather than on that selector. */
/* AMBER, AND NO UNDERLINE. CEO ruling, taken with the Design Lead's objection in front of them:
   this pane's headline figure is already amber, so a second amber risks weakening both. Recorded
   because the objection was sound and the decision was still the CEO's to make.
   THE COLOUR IS ON THE COMPOUND SELECTOR AND THAT IS LOAD BEARING. `.tk-fig .v` two rules up sets
   color: var(--ink) at 0,2,0. A bare `button.v` is 0,1,1 and LOSES, so the same declaration
   written there would parse, validate, review clean and paint nothing. That is the trap the
   comment above already names, and it was verified by reading the computed colour off the
   element rather than by reading this file.
   WHY AMBER CAN CARRY THE AFFORDANCE ALONE ON A PHONE. cursor: pointer does not exist on a touch
   screen, so with the underline gone the colour is the whole visual signal, and the brand guide
   makes --amber the CTA colour. The non-visual affordance was never the underline: the button
   carries aria-label "<amount> owed, open the guest list" from console/main.js, and the 44px tap
   area below is unchanged.
   AND THE HONEST QUALIFICATION, measured rather than argued. Amber TEXT elsewhere in this console
   means WHERE YOU ARE, not TAP ME: .editor-tab.is-active and .tk-step.on are both amber, and the
   console's actual primary control is amber as a GROUND with dark text (.btn.pri). So this is a
   third job for one colour on one pane. It is separated here by SIZE and by GROUND rather than by
   a second colour: the headline is 32px on --dark and this is 26px on --card. Measured at 375px
   the two sit 228px apart and never share a glance. NO BORDER, CHEVRON OR CARET WAS ADDED: the
   guide defines no arrow motif, and inventing one to prop up a colour decision would coin brand
   language inside a bug fix. */
/* AND THE BORDER, WHICH IS WHAT ACTUALLY MAKES IT READ AS A CONTROL. The colour above was the
   CEO's ruling; this is the designer call they then asked for, and it is here because the
   paragraph above measured the colour failing on its own.
   THE MEASUREMENT THAT CHANGED IT. Of 56 rules in this file that set amber as TEXT, 22 are a
   state (hover, focus, current, checked) and 34 are at rest, and before this one exactly ZERO of
   the 34 sat on an interactive element. Amber text at rest had never meant "tappable" here; it
   means WHERE YOU ARE. The console's real control signal is amber as a GROUND with dark text,
   .btn.pri, one of 23 such rules. So on a pane whose other amber is a headline, the colour alone
   was a coin flip, and cursor: pointer does not exist on a phone to break the tie.
   IT COINS NOTHING. This is the brand guide's OUTLINE button, .btn-o, which is border: 2px solid
   with zero radius, applied to this object. No chevron, caret or arrow was invented, because the
   guide defines no arrow motif and inventing brand language inside a bug fix is how a design
   system grows rules nobody agreed. The distinction between the headline figure and this one is
   now carried by SHAPE rather than by a second colour.
   THE GEOMETRY IS SOLVED, NOT CHOSEN. margin-left: -10px cancels exactly what the border and
   padding add inside it, 2px + 8px, so the NUMERAL still starts at the cell's 14px padding and
   stays optically aligned with its own label and its sub. Measured at 375 and 900, with the
   ordinary value and with LKR 10,000,000: ink left 14px against label 14px and sub 14px in all
   four, tap 44px high in all four, and the box never leaves the cell.
   width: auto rather than the 100% this rule used to carry, because an Outline button hugs its
   label and a full width band reads as a highlighted row. The 100% was there for the 44px floor,
   which min-height now holds on its own; max-width keeps the longest value inside the cell.
   THE COLOUR AND THE BORDER ARE BOTH ON THE COMPOUND SELECTOR FOR THE SAME REASON. `.tk-fig .v`
   sets color: var(--ink) AND border: 0 at 0,2,0. A bare `button.v` is 0,1,1 and loses both, so
   either declaration written there would parse, validate, review clean and paint nothing. Proven
   with a mutant that moves them: the shipped selector paints rgb(245,197,24), the mutant paints
   rgb(242,235,217) and no border at all. */
/* MUS-814, 2026-09-12. THIS RULE HAS NO CALLER AS OF TODAY, AND BOTH COMMENTS ABOVE IT STAY.
   renderLive stopped passing an onClick to the Owed figure, so nothing in the product renders a
   button inside a .tk-fig any more. The reason is domination rather than distaste: the Guest list
   button promoted into the main column goes to the same screen and renders in every state this
   one did, because owedN > 0 implies a pending booking implies live.length >= 1. Two amber
   controls 60px apart going to one room read as a mistake rather than a hierarchy, which is the
   same fault MUS-813 removed from the side rail in the same pass.
   NOTHING ABOVE IS DELETED, and that is the point of this note. Those two paragraphs carry a CEO
   ruling on amber, a measurement of 56 amber-as-text rules in this file, and a specificity trap
   that is true again the moment anybody styles anything inside a figure. A rule stripped of its
   reasoning is a rule the next person re-derives wrongly. */
.tk-fig button.v {
  display: inline-flex; align-items: center; min-height: 44px;
  width: auto; max-width: calc(100% + 20px);
  color: var(--amber); border: 2px solid var(--amber);
  padding: 2px 8px; margin-left: -10px;
}
.tk-fig .s { display: block; font-size: 11.5px; color: var(--muted); margin-top: 4px; line-height: 1.45; }
@media (min-width: 900px) {
  .tk-fig .v { font-size: 30px; }
  .tk-fig:nth-child(1) .v { font-size: 40px; }
}

.tk-facts { border: 2px solid var(--edge); background: var(--card); }
/* 375 FIRST: the label and Edit share the top line and the value owns the line below it. A
   label column at 375 leaves a 120px value column, which wraps every sentence into four lines
   and pushes Edit onto a line of its own.
   EVERY CELL IS PLACED EXPLICITLY. An item carrying a definite grid-row is placed before the
   auto-placed ones, so leaving Edit to auto placement put it in column 1 and pushed the label
   to the right hand edge. */
.tk-fact { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 2px 12px; padding: 10px 14px; border-bottom: 1px solid var(--line); align-items: center; }
.tk-fact:last-child { border-bottom: 0; }
.tk-fact .k { grid-column: 1; grid-row: 1; font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .14em; color: var(--muted); }
/* THE WRAP RULE IS LOAD BEARING AND THE MEASUREMENT SAYS SO: delete it and a 68 character contact
   address puts this row off the side of a 375px phone, caught by tests/e2e/mus749-375.spec.js.
   CORRECTED AFTER MEASURING, because the first version of this comment claimed more than was
   true. The studio rule is that overflow-wrap: break-word is IDENTICAL TO NO RULE AT ALL, which
   was measured on a FLEX row: an unbroken token still sets min-content there and a flex item is
   never sized below it. IN THIS GRID CELL IT IS NOT identical. The track is minmax(0, 1fr) and
   the cell carries min-width: 0, so the track may already shrink below min-content, and a mutant
   swapping word-break for overflow-wrap: break-word here SURVIVED at zero overflow.
   word-break IS KEPT ANYWAY, for a reason rather than by habit: it is the spelling that works in
   BOTH layouts, so this row survives being reparented into a flex container, which is exactly the
   change nobody would think to re-measure. overflow-wrap: anywhere is the other one that works
   everywhere. Do not swap it for overflow-wrap: break-word, which only works while the track
   above it stays minmax(0, 1fr). */
.tk-fact .v { grid-column: 1; grid-row: 2; font-size: 12.5px; min-width: 0; word-break: break-word; line-height: 1.5; }
.tk-fact .e { grid-column: 2; grid-row: 1 / span 2; align-self: center; font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .08em; color: var(--amber); min-height: 44px; min-width: 44px; display: flex; align-items: center; justify-content: flex-end; padding: 0 2px; background: none; border: 0; cursor: pointer; font-family: inherit; }
.tk-fact .e:focus-visible { outline: 2px solid var(--amber); outline-offset: -2px; }
@media (min-width: 900px) {
  .tk-fact { grid-template-columns: 108px minmax(0, 1fr) auto; min-height: 48px; gap: 12px; }
  .tk-fact .k { grid-column: 1; grid-row: 1; }
  .tk-fact .v { grid-column: 2; grid-row: 1; }
  .tk-fact .e { grid-column: 3; grid-row: 1; }
}

/* An empty state that only says "no bookings yet" is a wasted screen. This one carries the
   thing that changes the number. */
.tk-live-empty { border: 2px dashed var(--edge); padding: 20px 16px; text-align: center; background: var(--card); margin-bottom: 16px; }
.tk-live-empty[hidden] { display: none; }
.tk-live-empty b { display: block; font-size: 13px; font-weight: 700; margin-bottom: 6px; }
.tk-live-empty span { display: block; font-size: 12.5px; color: var(--muted); line-height: 1.6; max-width: 44ch; margin: 0 auto; }

/* 375px. The rail scrolls sideways rather than hiding, and the two-column form collapses.
   min-width on the cell is what makes the scroll happen instead of five crushed labels. */
@media (max-width: 720px) {
  .tk-step { min-width: 122px; }
  .tk-choice { max-width: none; }
  .tk-addrow { flex-wrap: wrap; }
  .tk-addrow input { flex: 1 1 100%; }
  .tk-addrow .btn { flex: 1 1 auto; }
  .tk-switch .btn { flex: 1 1 100%; }
  .tk-bk-actions .btn { flex: 1 1 auto; }
  .tk-bk-bar { flex-direction: column; align-items: stretch; }
  .tk-bk-bar .btn { width: 100%; }
  /* MUS-628 at 375px. Full width, and Keep the bookings FIRST in the source, so it is the one
     under the thumb and the destructive one is the further reach. */
  .tk-cancel-acts { flex-direction: column; align-items: stretch; }
  .tk-cancel-acts .btn { width: 100%; }
  #tk-cancel-open { width: 100%; }
}
