/* ═══════════════════════════════════════════════════════════
   PROGRAM DETAIL PAGE — /tour/:slug
   ═══════════════════════════════════════════════════════════
   Extracted from dashboard.css. Everything here is used only by
   `#view-tour` and its tabs (Summary, Revenue & Direct, Hotel Lookup,
   Expenses, Cash Flow, Settlement, Settings, Sign Off) — see
   specs/wip/program-detail-audit.md.

   LOADS AFTER dashboard.css, so these rules win ties against it.

   STILL IN dashboard.css, because other surfaces use them too:
     tokens · topbar + tour nav · .container · .view · .panel* ·
     .metric-card/-label/-value/-sub · .section-* · .btn* · .form-row/
     -label/-input · .hint · .modal* · .search-* · .ss-* (searchable
     select) · .save-ind · partner-mode lockdown · zoom controls · the
     bare table/th/td primitives
   Editing those changes the program list and division pages as well.

   ─── Layout approach ───
   Every grid here is intrinsic — `repeat(auto-fit, minmax(N, 1fr))` — and
   the file carries no per-breakpoint column counts. The version before this
   one declared column counts in up to four places per grid and they
   disagreed: source order made the 7-tile metric row render 7 → 3 → 4 → 2
   columns as the viewport narrowed, and a partner-mode `!important` pinned
   it to 6 at every width, phones included. Intrinsic grids can't drift that
   way, because the tile count and the breakpoints stop being written down.

   Pick the minmax floor as "the narrowest this content is still readable
   at", and the browser does the rest.
   ═══════════════════════════════════════════════════════════ */

/* ─── SUB-HEADER ───
   Up-navigation + the tab set, on the page rather than inside the sticky
   topbar. It used to be a second row of the topbar, which made the program
   name compete with seven tabs for one strip of chrome and left the tabs
   reading as part of the app frame rather than as this page's own navigation.

   "Attached to the body" is doing real work in the styling: the strip takes
   the page background (not a card surface), sits on the same rails as the
   content, and its bottom border is the line the content begins under. The
   active tab is underlined into that border instead of being a filled pill,
   so it reads as connected to the panel below it. */
.tour-subnav {
  display: flex;
  align-items: stretch;
  gap: 2px;
  margin-bottom: 24px;
  border-bottom: 1px solid var(--border);
  background: var(--bg);
  /* overflow is visible so the mobile tab list can escape the strip. Above
     769px the tabs are laid out inline and can overflow a narrow window, so
     the scroller comes back there — see the desktop block below. */
  overflow: visible;
}

/* Pins under the topbar at every width, so tab switching stays reachable on
   the pages that scroll for thousands of pixels. --topbar-h is the shared
   offset; the topbar is given a fixed height on mobile (dashboard.css) rather
   than wrapping, so this one value is always right. */
.tour-subnav {
  position: sticky;
  top: var(--topbar-h);
  z-index: 50;
}

/* Separates up-navigation from the tabs: going up is a different kind of move
   than picking a tab, and without a rule between them the row reads as eight
   peers. */
.tour-subnav-divider {
  flex: 0 0 auto;
  align-self: center;
  width: 1px;
  height: 18px;
  /* Even on both sides. It was 4px left / 10px right, so with the strip's own
     2px gap the pill sat 6px from the rule and the first tab 12px. */
  margin: 0 8px;
  background: var(--border);
}

/* showPage() scrolls the active tab into view, which would carry the only way
   back off-screen. The pills pin to the left edge and the tabs slide under. */
.tour-subnav .back-pill,
.tour-subnav .partner-back-pill {
  position: sticky;
  left: 0;
  z-index: 1;
  align-self: center;
  background: var(--bg);
}

/* ─── TABS ───
   Desktop: the <details> and its list collapse to display:contents, so the
   eight buttons become direct flex children of the strip and lay out as a
   plain row — identical to before this was a disclosure. ::details-content is
   forced visible so the closed <details> doesn't hide them. */
@media (min-width: 769px) {
  .tour-subnav {
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
    scroll-snap-type: x proximity;
  }
  .tour-subnav::-webkit-scrollbar {
    display: none;
  }
  .tour-tabs,
  .tour-tabs-list {
    display: contents;
  }
  .tour-tabs::details-content {
    content-visibility: visible;
    display: contents;
  }
  .tour-tabs-current {
    display: none;
  }
}

/* Mobile: one row showing the current tab, opening to the full list. */
@media (max-width: 768px) {
  .tour-tabs {
    position: relative;
    flex: 1 1 auto;
    min-width: 0;
  }
  .tour-tabs-current {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    height: var(--control-h);
    padding: 0 4px 0 12px;
    font-family: var(--heading);
    font-size: var(--fs-eyebrow);
    letter-spacing: var(--ls-chrome);
    line-height: 1;
    text-transform: uppercase;
    color: var(--gold-bright);
    cursor: pointer;
    user-select: none;
    list-style: none;
  }
  .tour-tabs-current::-webkit-details-marker {
    display: none;
  }
  .tour-tabs-current svg {
    width: 14px;
    height: 14px;
    flex-shrink: 0;
    color: var(--text-dim);
    transition: transform 0.15s;
  }
  .tour-tabs[open] .tour-tabs-current svg {
    transform: rotate(180deg);
  }
  .tour-tabs-list {
    position: absolute;
    top: calc(100% + 9px);
    left: 0;
    right: 0;
    z-index: 60;
    display: grid;
    padding: 6px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.16);
  }
  /* In the list a tab is a menu row, so the underline that marks the active
     one in the strip becomes a left rule instead. */
  .tour-tabs-list .nav-btn {
    justify-content: flex-start;
    width: 100%;
    padding: 11px 12px;
    border-bottom: none;
    border-left: 2px solid transparent;
    margin-bottom: 0;
    text-align: left;
  }
  .tour-tabs-list .nav-btn + .nav-btn {
    border-top: 1px solid var(--border);
  }
  .tour-tabs-list .nav-btn.active {
    border-left-color: var(--gold-fill);
    background: var(--surface2);
  }
}

.nav-btn {
  flex-shrink: 0;
  scroll-snap-align: start;
  font-family: var(--heading);
  font-size: var(--fs-eyebrow);
  letter-spacing: var(--ls-chrome);
  padding: 11px 14px;
  border: none;
  /* Overlaps the strip's own bottom border, so the active underline replaces
     that line rather than stacking a second one under it. */
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  border-radius: 0;
  cursor: pointer;
  background: none;
  color: var(--text-dim);
  text-transform: uppercase;
  white-space: nowrap;
  transition:
    color 0.15s,
    border-color 0.15s;
}
.nav-btn:hover {
  color: var(--text);
}
.nav-btn:focus-visible {
  outline: none;
  color: var(--text);
  border-bottom-color: var(--text-dim);
}
.nav-btn.active {
  color: var(--gold-bright);
  border-bottom-color: var(--gold-fill);
  font-weight: 600;
}

/* ─── SECTION TOOLBAR ───
   The action cluster in a .section-header. Replaces the `margin-left: 4px`
   inline style that used to sit on each of the twelve buttons in the Revenue &
   Direct header: they were direct children of the header, so they wrapped one
   at a time and the row's alignment came apart as soon as it ran out of width.
   As one group they push right together and wrap as a block. */
.section-actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  margin-left: auto;
}
/* Keeps the group from being squeezed narrower than the search box it leads
   with when the title beside it is long. */
.section-actions .search-wrap {
  flex: 0 1 auto;
}

/* Stand-ins for the two controls in the shows table's heading strip — sort and
   select-all. Hidden while that strip is on screen, so neither is offered
   twice. */
.shows-card-only {
  display: none;
}
@media (max-width: 1200px) {
  .shows-card-only {
    display: inline-flex;
  }
}
.shows-select-all {
  align-items: center;
  gap: 6px;
  font-family: var(--heading);
  font-size: var(--fs-eyebrow);
  letter-spacing: var(--ls-chrome);
  text-transform: uppercase;
  color: var(--text-dim);
  cursor: pointer;
  white-space: nowrap;
}
.shows-select-all input {
  cursor: pointer;
}

/* ─── TOOLBAR MENU ───
   The Filters button's pattern from the programs list, applied to a section
   toolbar: a <details>, so the disclosure needs no JavaScript, with the
   summary styled as a toolbar button.

   It opens as a popover anchored to the button rather than as a full-width
   panel the way Filters does. Filters can push the list down because it *is*
   the list's header; these actions sit above a table, and inserting a block
   between the toolbar and the data would shove the rows out from under the
   cursor every time the menu opened. */
.toolbar-menu {
  position: relative;
  flex-shrink: 0;
}
.toolbar-menu-btn {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  height: var(--control-h);
  padding: 0 14px;
  background: var(--surface2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: var(--text-dim);
  font-family: var(--heading);
  font-size: var(--fs-eyebrow);
  /* line-height:normal leaves descender space the all-caps glyphs never use,
     which reads as the icon sitting high beside the label. */
  line-height: 1;
  letter-spacing: var(--ls-chrome);
  text-transform: uppercase;
  cursor: pointer;
  user-select: none;
  list-style: none; /* suppress the default disclosure triangle */
}
.toolbar-menu-btn::-webkit-details-marker {
  display: none;
}
.toolbar-menu-btn:hover {
  color: var(--text);
  border-color: var(--text-dim);
}
.toolbar-menu-btn svg {
  width: 14px;
  height: 14px;
  flex-shrink: 0;
}
.toolbar-menu[open] .toolbar-menu-btn {
  color: var(--gold);
  border-color: var(--gold-fill);
}

.toolbar-menu-body {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  z-index: 60;
  display: grid;
  min-width: 230px;
  padding: 6px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.16);
}
/* The buttons drop their own boxes and read as menu rows — same treatment the
   topbar actions menu gets. Left-aligned, since the labels are what you scan. */
.toolbar-menu-body > * {
  justify-content: flex-start;
  width: 100%;
  border: none !important;
  border-radius: 0 !important;
  background: none !important;
  padding: 9px 10px !important;
  margin: 0 !important;
  text-align: left;
  white-space: nowrap;
}
.toolbar-menu-body > * + * {
  border-top: 1px solid var(--border) !important;
}
.toolbar-menu-body > *:hover {
  background: var(--surface2) !important;
  color: var(--text) !important;
}

/* ─── CONTROL STRIP ───
   A bordered row of inputs that acts on the page below it — the forecast
   slider, the "sales + N" setter, the summary-source toggle. There were four
   of these built as near-identical eight-declaration inline style blocks
   (audit §6); this is the shared version, and it picks up --radius instead of
   the hardcoded 6px corner they all carried.

   On Revenue & Direct these controls used to live *inside* .search-wrap, which
   is a position:relative box sized for the search input. They stacked
   underneath it and the ⌕ icon, absolutely positioned at 50% of the wrap,
   floated down into the middle of them. */
.control-strip {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px 14px;
  margin-bottom: 16px;
  padding: 10px 12px;
  background: var(--surface2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
/* For strips holding more than one row of controls, divided by a rule. */
.control-strip--stack {
  flex-direction: column;
  align-items: stretch;
  gap: 0;
}
.control-strip--stack > * + * {
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px solid var(--border);
}
.control-strip-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px;
}
/* Label leading a control group. */
.control-strip-label {
  font-family: var(--heading);
  font-size: var(--fs-eyebrow);
  letter-spacing: var(--ls-chrome);
  text-transform: uppercase;
  color: var(--text-dim);
  white-space: nowrap;
}

/* Live readout beside a control — the sold percentage, a running total. Data,
   so it keeps the body face and tabular figures. */
.control-strip-value {
  font-family: var(--mono);
  font-size: var(--fs-meta);
  font-variant-numeric: tabular-nums;
  color: var(--gold);
  min-width: 64px;
  white-space: nowrap;
}
/* Small numeric field inside a strip. */
.control-strip-num {
  width: 64px;
  padding: 4px 8px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: var(--text);
  font-family: var(--mono);
  font-size: var(--fs-meta);
  font-variant-numeric: tabular-nums;
  text-align: left;
  outline: none;
}
.control-strip-num:focus {
  border-color: var(--gold);
}

/* ─── METRIC ROW ───
   165px basis: two tiles still fit a 375px phone (343px of container after
   .container's 16px padding), and a tile that narrow still holds a figure at
   --fs-metric. Above that it packs as many as fit — 7 across on a wide
   desktop, 4 on a laptop, 2 on a phone, with no breakpoints involved.

   flex-wrap rather than `grid-template-columns: repeat(auto-fit, …)`, which
   is the obvious choice here and the wrong one: the 1px "borders" between
   tiles are this container's background showing through the gaps, so any
   grid cell without a tile in it paints as a solid --border block. Seven
   tiles over four columns leaves exactly that. Wrapped flex items grow to
   fill their line instead, so a short last row has no holes in it.

   The .metrics-grid-7col / -4col variants this replaces are gone, along with
   those class names in the markup and settlement.js's inline 7-column
   override — an inline style would have beaten anything written here. */
.metrics-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 1px;
  background: var(--border);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
  margin-bottom: 28px;
}
.metrics-grid > .metric-card {
  flex: 1 1 165px;
  /* Without this a long figure sets the tile's min-content width and pushes
     the row wider than its container. */
  min-width: 0;
}

/* ─── PANEL COLUMNS ───
   Both were fixed column counts that collapsed to a single column at 900px —
   one hard cliff, and until it hit, a 1fr track on the three-col row could
   get as narrow as 250px. Intrinsic floors let them drop one column at a
   time as the window narrows. */
.two-col {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 380px), 1fr));
  gap: 20px;
  margin-bottom: 28px;
}
/* Even tracks, deliberately. A `2fr 1fr 1fr` proportion here gave the
   waterfall the space it wants, but the two panels beside it are not filler —
   Ticket Sales Pace carries a slider, a number input and two buttons on one
   row, and Tour Info is a label/value table. Both need real width, and at
   ~1280px the 1fr tracks landed near 290px and started clipping. The
   waterfall reads fine at an even share; those two don't. */
.three-col {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 300px), 1fr));
  gap: 20px;
  margin-bottom: 28px;
}

canvas {
  width: 100% !important;
}

/* ─── SHOWS TABLE ───
   The bare table/th/td primitives stay in dashboard.css — modal preview
   tables inherit them. Only the detail page's own table pieces live here.

   The table itself stays wide and scrolls inside .table-wrap rather than
   reflowing: these are financial columns that are read across, and stacking
   them into cards would break the comparison the table exists for. */
.table-wrap {
  overflow-x: auto;
  min-width: 0;
  /* Stops a horizontal flick inside the table from also scrolling the page
     back on touch devices. */
  overscroll-behavior-x: contain;
}

td.city {
  color: var(--text-dim);
}
td.date-cell {
  color: var(--gold);
}

.pct-bar-wrap {
  display: flex;
  align-items: center;
  gap: 8px;
}
.pct-bar-track {
  width: 60px;
  height: 4px;
  background: var(--border);
  border-radius: var(--radius);
  overflow: hidden;
  /* The bar is decoration beside a number; it gives up width first. */
  flex: 0 1 60px;
}
.pct-bar-fill {
  height: 100%;
  border-radius: var(--radius);
  background: var(--green);
}
.pct-bar-fill.warn {
  background: var(--gold);
}
.pct-bar-fill.danger {
  background: var(--red);
}

/* Package tag in the shows table. It became an editable <input> in "Revenue
   tab: make Date / City / Pkg columns editable"; the chip travelled with it,
   from .pkg-pill onto the .pkg-edit hook that change added but left unstyled.

   The base carries a chip, not just padding. shows.js maps the package name to
   a class by exact string — "VIP1" and "VIP2" only — so anything else ("VIP 1
   Upgrades", "Platinum", …) lands on .pkg-vip3, which has no rule. With padding
   but no background that rendered as bare text indented 8px from its label,
   reading as a mis-aligned value rather than a tag. Every package is a green
   chip; the two known tiers tint theirs differently. */
/* Compounded with .editable, not standalone: .editable sets
   background: transparent and sits later in this file, so a bare .pkg-edit
   would lose the chip on source order alone. Two classes beats one whatever
   the order. */
.editable.pkg-edit {
  padding: 2px 8px;
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--green) 14%, transparent);
  color: var(--green);
  font-family: var(--heading);
  font-size: var(--fs-eyebrow);
  letter-spacing: var(--ls-chrome);
}
/* Tints are mixed from the same token the text uses, so a chip themes with the
   palette instead of drifting from it. The three used to be hardcoded rgba from
   the dark-first era — .pkg-vip1's fill was a blue that no longer matched its
   own var(--blue) text once that token became teal (audit §2). */
.editable.pkg-vip1 {
  background: color-mix(in srgb, var(--blue) 14%, transparent);
  color: var(--blue);
}
.editable.pkg-vip2 {
  background: color-mix(in srgb, var(--gold) 14%, transparent);
  color: var(--gold);
}

/* ─── SETTINGS TAB FORMS ───
   .form-row / .form-label / .form-input and the .btn family stay in
   dashboard.css — the Add Tour modal uses them too. */
.form-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr));
  gap: 16px;
}

/* Hybrid date field: free-text input you can type into + a calendar button
   that opens the native date picker. The companion type=date input is kept
   off-screen (not display:none, so showPicker() still works) and only drives
   the popout calendar. */
.date-field {
  position: relative;
  display: flex;
  align-items: center;
  gap: 6px;
}
.date-field .form-input {
  flex: 1;
  min-width: 0;
}
.date-field-cal {
  position: absolute;
  right: 44px;
  bottom: 0;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: 0;
  border: 0;
  opacity: 0;
  pointer-events: none;
}
.date-field-btn {
  flex: 0 0 auto;
  background: var(--surface2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 8px 10px;
  font-size: var(--fs-lead);
  line-height: 1;
  cursor: pointer;
  color: var(--text);
  transition: border-color 0.15s;
}
.date-field-btn:hover {
  border-color: var(--text-dim);
}

/* ─── INLINE EDIT TABLE ───
   Data, not chrome: keeps the body face and tabular figures. */
.editable {
  background: transparent;
  border: none;
  border-bottom: 1px solid transparent;
  color: var(--text);
  font-family: var(--mono);
  font-size: var(--fs-meta);
  width: 100%;
  text-align: left;
  outline: none;
  padding: 2px 0;
  transition: border-color 0.15s;
}
.editable:focus {
  border-bottom-color: var(--gold);
  background: rgba(201, 168, 76, 0.05);
}

/* [EXP_QTY_WIDTH_2026_09_02] Qty on the Expense Detail table was clipping to a
   single digit, so any double-digit quantity was unreadable.

   Two causes, both needed fixing. `.editable` is width:100%, so an input can
   never widen the column it sits in — the column has to be given a floor. And
   number inputs render spinner arrows that swallow ~16px of whatever width the
   column does get, which on a table this dense was the whole difference between
   one visible digit and four.

   Scoped to #exp-table so the other inline-edit tables keep their own sizing
   (the per-package rates table, for one, sets explicit pixel widths inline). */
#exp-table .col-qty {
  min-width: 72px;
}
#exp-table .col-cost-unit {
  min-width: 88px;
}
/* Drop the spinners: they are near-useless for typing a quantity, easy to
   mis-click in a tight row, and cost more space than they earn. Keyboard
   arrows still increment, so nothing is actually lost. */
#exp-table .editable[type="number"] {
  appearance: textfield;
  -moz-appearance: textfield;
}
#exp-table .editable[type="number"]::-webkit-outer-spin-button,
#exp-table .editable[type="number"]::-webkit-inner-spin-button {
  appearance: none;
  -webkit-appearance: none;
  margin: 0;
}

/* ─── PROFIT WATERFALL ───
   Summary's headline panel. Rows are label + figure; the figure column holds
   its width and the label takes what's left. */
.waterfall {
  display: flex;
  flex-direction: column;
  gap: 0;
}
.waterfall-row {
  display: flex;
  align-items: baseline;
  padding: 10px 0;
  /* Was rgba(255,255,255,0.04) — a dark-theme value that is invisible on the
     white panel this sits on. Only :last-child had a real border, which is
     why the panel read as one undivided block with a line at the bottom. */
  border-bottom: 1px solid var(--border);
  gap: 16px;
  flex-wrap: wrap;
}
.waterfall-row:last-child {
  border-bottom: none;
  border-top: 1px solid var(--border);
  margin-top: 4px;
}
.waterfall-label {
  flex: 1 1 55%;
  min-width: 0;
  font-size: var(--fs-body);
  line-height: 1.4;
  color: var(--text-dim);
}
.waterfall-label.total {
  color: var(--text);
  font-weight: 500;
}
.waterfall-val {
  font-family: var(--mono);
  font-size: var(--fs-lead);
  font-variant-numeric: tabular-nums;
  /* Grows with the row instead of being pinned at 100px, so a wide panel
     right-aligns the column and a narrow one lets the label wrap under it
     rather than squeezing the figure. */
  flex: 0 0 auto;
  margin-left: auto;
  text-align: right;
}
.waterfall-val.pos {
  color: var(--green);
}
.waterfall-val.neg {
  color: var(--red);
}
.waterfall-val.total {
  color: var(--gold-bright);
  font-family: var(--display);
  font-size: var(--fs-figure);
}

/* ─── PAGE VIEWS ─── */
.page {
  display: none;
}
.page.active {
  display: block;
}

/* ─── CONFLICT BANNER ───
   Margins track .container's padding at each width so the banner lines up
   with the page content rather than floating in its own gutter. */
.conflict-banner {
  background: rgba(229, 72, 77, 0.15);
  border: 1px solid var(--red);
  color: var(--text);
  padding: 12px 20px;
  border-radius: var(--radius);
  font-family: var(--mono);
  font-size: var(--fs-meta);
  display: none;
  margin: 16px 32px 0;
  line-height: 1.5;
}
.conflict-banner strong {
  color: var(--red);
  font-family: var(--heading);
  text-transform: uppercase;
  letter-spacing: var(--ls-chrome);
  font-size: var(--fs-eyebrow);
}
.conflict-banner-actions {
  margin-top: 10px;
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}
.conflict-banner-actions button {
  font-family: var(--heading);
  font-size: var(--fs-eyebrow);
  letter-spacing: var(--ls-chrome);
  text-transform: uppercase;
  padding: 6px 14px;
  border-radius: var(--radius);
  cursor: pointer;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
}
.conflict-banner-actions button.primary {
  background: var(--gold-fill);
  color: var(--on-accent);
  border-color: var(--gold-fill);
}
.conflict-banner-actions button:hover {
  opacity: 0.85;
}

/* VIP tours hide the Tickets/Package and Room Nights columns since
   they're always 1 ticket per package and 0 room nights for VIP shows. */
#shows-table.vip-mode .col-non-vip {
  display: none;
}

/* ─── FORECAST SLIDER ───
   The row itself is a .control-strip; only the range input needs anything
   of its own. */
.forecast-slider-input {
  /* Shrinks on a narrow header instead of forcing the row to wrap early. */
  width: 120px;
  max-width: 100%;
  min-width: 80px;
  accent-color: var(--gold);
}

/* ─── SCENARIO ANALYSIS GRID ───
   Row label + five data columns. The label track keeps a fixed floor and the
   data columns share what's left, so the grid narrows by squeezing data
   rather than by dropping columns at breakpoints — the previous version cut
   it to 3 then 2 columns, which silently hid the right-hand figures. */
.scenario-targets-grid {
  display: grid;
  grid-template-columns: minmax(120px, 1.2fr) repeat(5, minmax(72px, 0.8fr));
  gap: 14px;
  align-items: end;
  margin-bottom: 24px;
  padding-bottom: 18px;
  border-bottom: 1px solid var(--border);
  /* Below ~500px the five data columns stop fitting; scroll rather than
     truncate, same reasoning as the shows table. */
  overflow-x: auto;
  overscroll-behavior-x: contain;
}

/* ─── SETTINGS TWO-COLUMN GRID ───
   Ticket types on the left, fee percentages on the right. */
.settings-two-col {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 280px), 1fr));
  gap: 24px;
}

/* ═══════════════════════════════════════════════════════════
   SHOWS TABLE — FROZEN HEADER (>1200px)
   ═══════════════════════════════════════════════════════════
   Twenty-three columns don't fit any screen, so .table-wrap scrolls in both
   axes — with the heading strip and the totals row pinned inside it. Without
   that, scrolling right to "Show Net Profit" left you reading unlabelled
   figures and scrolling down lost the tour total.

   A sticky thead needs a bounded scroll container to stick to. The wrapper was
   already a scroll container — overflow-x: auto computes overflow-y to auto as
   well — just an unbounded one, which is why the `position: sticky` that has
   always been on `th` never did anything (audit §9). Capping the height is
   what switches it on. */
@media (min-width: 1201px) {
  .table-wrap:has(#shows-table) {
    /* Room for the topbar, sub-header, section toolbar and control strip above
       it; min-height keeps it usable on a short window. */
    max-height: calc(100vh - 300px);
    min-height: 320px;
    overflow: auto;
  }
  #shows-table thead th {
    position: sticky;
    top: 0;
    z-index: 5;
    /* th already paints --surface so rows can scroll under it, but the strip
       needs its own bottom rule: thead's border scrolls away with the element
       it is drawn on. inset shadow, so it can't affect layout. */
    box-shadow: inset 0 -1px 0 var(--border);
  }
  /* The tour total is the figure you scroll down to check, so it stays put. */
  #shows-table tfoot td {
    position: sticky;
    bottom: 0;
    z-index: 3;
    background: var(--surface2);
    box-shadow: inset 0 1px 0 var(--border);
  }
  /* The totals row carries its own frozen cell over the frozen columns — its
     label is a colspan=2 covering exactly that pair. Without it the row's
     other cells scroll left into that region and paint over the frozen Date
     column, which is what put the allotment total under the dates. */
  #shows-table tfoot td:first-child {
    left: 0;
    z-index: 4;
    box-shadow:
      inset -1px 0 0 var(--border),
      inset 0 1px 0 var(--border);
  }

  /* ─── Frozen columns ───
     Checkbox + Date pin to the left edge, so scrolling right through the other
     21 columns keeps the show you're reading identified — a header row alone
     tells you which column you're in but not which row.

     Column 1 gets a fixed width so column 2's `left` offset is a known number
     rather than whatever the browser sizes a checkbox cell to.

     :nth-child is safe here even though partner and VIP mode hide columns:
     those are .col-allotment (7) and .col-non-vip (9–11), and a hidden cell
     stays in the DOM, so the first two indices never move. */
  #shows-table thead th:first-child,
  #shows-table tbody td:first-child {
    width: var(--freeze-col1);
    min-width: var(--freeze-col1);
    left: 0;
  }
  #shows-table thead th:nth-child(2),
  #shows-table tbody td:nth-child(2) {
    left: var(--freeze-col1);
  }
  /* Right-hand rule on the frozen pair, so it reads as an edge once content
     slides under it. inset, so it can't widen the column.

     Set per section, and combined with each section's own rule rather than
     replacing it: box-shadow does not accumulate across rules — the winning
     declaration replaces the whole list. A single `inset -1px 0 0` on column 2
     therefore erased the heading strip's bottom rule and the totals row's top
     rule on exactly those cells. */
  #shows-table tbody td:nth-child(2) {
    box-shadow: inset -1px 0 0 var(--border);
  }
  #shows-table thead th:nth-child(2) {
    box-shadow:
      inset -1px 0 0 var(--border),
      inset 0 -1px 0 var(--border);
  }

  /* NOT tfoot: the totals row's first cell is a colspan=4 label, so it has 20
     cells to the body's 23 and :nth-child(2) there is Current Sales, not the
     Date column. Pinning it dragged that figure to the frozen offset and sat it
     on top of the label. The totals row stays frozen to the bottom; its cells
     scroll sideways with the columns they belong to. */
  #shows-table thead th:first-child,
  #shows-table thead th:nth-child(2),
  #shows-table tbody td:first-child,
  #shows-table tbody td:nth-child(2) {
    position: sticky;
    z-index: 1;
  }
  /* Backgrounds, per section — every frozen cell needs an opaque one or the
     cells sliding beneath it show through.

     Body cells take the ROW's colour rather than a fixed one: rows carry
     selected / counts-flagged / tied-out tints, and a frozen cell painting
     --surface over them would leave the Date cell looking unselected while the
     rest of its row is tinted. The tints are opaque for exactly this reason.

     Head and foot must NOT inherit — thead/tfoot rows have no background of
     their own, so inheriting makes the frozen corner cells transparent and the
     first data row reads straight through the heading strip. */
  #shows-table tbody tr {
    background-color: var(--surface);
  }
  #shows-table tbody td:first-child,
  #shows-table tbody td:nth-child(2) {
    background-color: inherit;
  }
  #shows-table thead th:first-child,
  #shows-table thead th:nth-child(2) {
    background-color: var(--surface);
    /* The corner has to beat both stickies it belongs to, or row cells slide
       over it. Stack, low to high: frozen body columns 1 · totals row 3 ·
       its frozen cell 4 · heading strip 5 · heading-strip corner 6. */
    z-index: 6;
  }
}

/* ═══════════════════════════════════════════════════════════
   SHOWS TABLE — CARD LAYOUT (≤1200px)
   ═══════════════════════════════════════════════════════════
   Below the width where the columns are worth scrolling, each row becomes a
   card carrying its own labels — the same move the program list makes at the
   same breakpoint.

   The labels come from `data-label` attributes emitted by renderShowsTable()
   (shows.js), not from a list of :nth-child rules. .tour-tile's card layout
   pins labels by cell index and repeats their wording from the markup; its own
   comment flags that those indices rot silently when a column moves. Reading
   attr(data-label) means the label travels with the cell, so reordering or
   dropping a column can't put the wrong name on a figure.

   NOTE the accessibility tradeoff: display:grid on the rows drops the
   elements' implicit table roles, so at these widths it is not a table to a
   screen reader. Each cell still carries its label in data-label, which the
   ::before renders and most screen readers announce — and above the breakpoint
   it is a real table again. */
@media (max-width: 1200px) {
  #shows-table thead {
    display: none;
  }
  #shows-table,
  #shows-table tbody,
  #shows-table tfoot {
    display: block;
  }
  #shows-table tbody tr,
  #shows-table tfoot tr {
    position: relative;
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 150px), 1fr));
    gap: 12px 14px;
    padding: 16px;
    border-bottom: 1px solid var(--border);
    /* Cells hug their content rather than stretching to the row's tallest, so a
       cell's box matches what it draws. */
    align-items: start;
  }
  /* Zebra, as on the program list: a card is several lines tall and there is
     no hover to track rows with on touch. Rows carrying a state tint —
     selected, counts-flagged, tied out — set it inline and so still win. */
  #shows-table tbody tr:nth-child(even) {
    background: var(--row-alt);
  }

  /* Block, not flex-column. A column flex container makes every child its own
     flex item on its own line, which is right for label-over-value and wrong for
     any cell whose value is more than one element: the Fees cell put its "—" and
     its ✎ edit button on separate lines, and the Date cell dropped its ⚠/🔒 flag
     above the date. Those three-line cells then set their whole grid row's
     height, which is where the empty space in the shorter cells beside them came
     from. As a block, the ::before label takes its own line and the value
     content stays in normal inline flow. */
  #shows-table td {
    display: block;
    padding: 0;
    border: none;
    text-align: left;
    white-space: normal;
  }
  /* The label the heading strip used to provide. */
  #shows-table td::before {
    content: attr(data-label);
    display: block;
    margin-bottom: 3px;
    font-family: var(--heading);
    font-size: var(--fs-eyebrow);
    letter-spacing: var(--ls-chrome);
    line-height: 1.2;
    text-transform: uppercase;
    color: var(--text-dim);
  }
  /* Cells whose content speaks for itself: the checkbox and the delete
     button. Emitted with an empty data-label rather than none, so a missing
     attribute stays a visible mistake. */
  #shows-table td[data-label=""]::before {
    content: none;
  }

  /* The date identifies the card, so it leads, full width and larger. */
  #shows-table td.date-cell {
    grid-column: 1 / -1;
    font-family: var(--display);
    font-size: var(--fs-figure);
    letter-spacing: 0.02em;
    padding-right: 32px;
  }
  /* Out of flow, top-right — in flow it would take a whole 130px track to
     hold a 14px checkbox. */
  #shows-table td.show-select-cell {
    position: absolute;
    top: 14px;
    right: 14px;
  }
  #shows-table td.show-delete-cell {
    grid-column: 1 / -1;
  }

  /* Inputs align left with their labels rather than right against a column
     edge that no longer exists. */
  #shows-table td .editable,
  #shows-table td input[type="number"] {
    width: 100% !important;
    max-width: 140px;
    text-align: left !important;
  }

  /* Totals read as a summary card, not another show. */
  #shows-table tfoot tr {
    border-top: 2px solid var(--border);
    background: var(--surface2);
  }
  #shows-table tfoot td[colspan] {
    grid-column: 1 / -1;
  }
  /* The two cells that only exist to line the totals row up with the body's
     column count have no content to label. */
  #shows-table tfoot td:empty {
    display: none;
  }

  /* Never anything to scroll sideways. */
  .table-wrap:has(#shows-table) {
    overflow-x: visible;
  }
}

/* ═══════════════ RESPONSIVE ═══════════════
   Only the things that genuinely need a breakpoint: page-edge margins that
   track .container's padding, and touch-target padding. Every grid above is
   intrinsic and needs nothing here. */
@media (max-width: 768px) {
  .conflict-banner {
    margin: 12px 16px 0;
  }
  /* Tiles get their vertical padding back but give up horizontal padding, so
     a figure has more of the tile's width to sit in on a phone. */
  .metric-card {
    padding: 18px 14px 16px;
  }
}

@media (max-width: 480px) {
  .conflict-banner {
    margin: 8px 12px 0;
  }
}
