/* Casino Platform Admin
   Base: 301-ui CSS system (301-ui-base.css loaded first)
   This file: casino-platform custom additions only */

/* ========== VARIABLE COMPATIBILITY ========== */
:root {
  --border: var(--border-subtle);
  /* 301-ui ships with --header-height: 96px and --footer-height: 120px
     (site-header + utility-bar + footer for the public site shell). The
     admin shell is thinner: 48px sticky header, no footer. Override the
     tokens here so .app-shell + .sidebar pick up the right offsets for
     sticky positioning + min-height calc. */
  --header-height: 48px;
  --footer-height: 0px;
}

/* ========== ADMIN HEADER (not in 301-ui) ========== */
.admin-header {
  position: sticky;
  top: 0;
  z-index: var(--z-header, 200);
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: 48px;
  padding: 0 var(--space-4);
  background: var(--nav-bg);
  border-bottom: 1px solid var(--border-subtle);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
}
.admin-header__left {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}
.admin-header__brand {
  font-size: var(--fs-md);
  font-weight: var(--fw-bold);
  color: var(--text-main);
  letter-spacing: -0.01em;
  display: inline-flex;
  align-items: center;
}
.admin-header__brand-mark {
  height: 28px;
  width: auto;
  display: block;
}
.admin-header__burger {
  display: none;
  background: none;
  border: none;
  color: var(--text-main);
  font-size: 1.25rem;
  cursor: pointer;
  padding: var(--space-1);
}
@media (max-width: 1023px) {
  .admin-header__burger { display: flex; }
}
.admin-header__right {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

/* Loading shimmer pinned to the bottom edge of the sticky admin
   header. Base .loading-bar styles (301-ui-base.css) supply the
   absolute positioning + shimmer animation; this override drops the
   bar a hair below the header's bottom border so the shimmer reads
   as "progress under the top bar" rather than overlapping the
   border-bottom rule. Toggled via window.adminLoadingBar. */
.loading-bar--admin-header {
  bottom: calc(-1 * var(--indicator-thickness, 2px));
}

/* ========== SITE PREVIEW BUTTON ===================================
   Lives in the admin header on every `/admin/sites/:siteId/*` page.
   Two states controlled purely by the `.is-dirty` modifier toggled
   from JS (window.markSiteDirty / cleared after a successful purge):

     • clean — opens the live origin in a new tab. Just navigation.
     • dirty — runs `POST /api/cache/publish/:siteId` first, then
               opens the live origin once the cache version bumps.

   Both labels are rendered server-side; we hide one with CSS so the
   click handler doesn't have to swap markup. */
.site-preview .site-preview__clean,
.site-preview .site-preview__dirty {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
}
.site-preview .site-preview__dirty { display: none; }
.site-preview.is-dirty .site-preview__clean { display: none; }
.site-preview.is-dirty .site-preview__dirty { display: inline-flex; }
/* Dirty state borrows the primary look without us having to swap the
   `btn--ghost` modifier from JS. Scoped under `.admin-header__right` so
   it outranks the bare `.btn--ghost` rule on specificity. */
.admin-header__right .site-preview.is-dirty {
  background: var(--primary);
  color: var(--btn-text-on-dark);
  border-color: var(--primary);
}
.admin-header__right .site-preview.is-dirty:hover {
  background: var(--primary-hover);
  border-color: color-mix(in srgb, var(--primary) 35%, transparent);
  color: var(--btn-text-on-dark);
}
/* Truncate long site names so the header doesn't blow up on
   "bitcoincasinos" + locale-rich names. */
.site-preview__name {
  max-width: 22ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ========== LAYOUT — admin-shell tweaks on top of 301-ui .app-shell ==
   The admin uses 301-ui's `.app-shell` + `.sidebar` + `.navitem` directly
   (see ADMIN-STYLEGUIDE.md "Never duplicate 301-ui styles"). Only the
   thin admin-header wraps it, so we tune the sticky offsets and inject
   the legacy mobile-drawer pattern that 301-ui doesn't ship. */
.app-shell {
  /* admin-header is sticky 48px tall on top; the sidebar should sit
     below it and stretch to the bottom of the viewport. */
  padding: var(--space-4);
}
@media (min-width: 1024px) {
  /* Grid items would otherwise shrink to content width because main
     centers via margin auto; force fill so the layout doesn't look
     squashed when content is short. The padding-block/inline mirrors
     the canonical .layout-shell .layout-main rule from 301-ui-base
     (line ~165) so admin pages get the same internal breathing room
     as dashboard surfaces — without it the page-header h1 sits flush
     against the sticky admin-header and the cards butt up against
     the sidebar grid edge. */
  .layout-main {
    width: 100%;
    min-width: 0;
    padding-block: var(--space-6);
    padding-inline: var(--page-gutter-desktop);
  }
}
@media (max-width: 1023px) {
  .layout-main {
    padding-block: var(--space-4);
    padding-inline: var(--page-gutter-mobile);
  }
}

/* Dashboard card-as-link (per-site landing page card-grid). */
.card.card--link {
  display: block;
  text-decoration: none;
  color: inherit;
  transition: border-color 120ms ease, transform 120ms ease;
}
.card.card--link:hover {
  border-color: var(--primary);
  transform: translateY(-1px);
}
.card.card--link:focus-visible {
  outline: 2px solid var(--primary);
  outline-offset: 2px;
}

/* ========== TABLES — admin-shell tweaks on top of 301-ui .table ==========
   The 301-ui base ships .table + .table-scroll + the canonical card
   composition (.card.card--panel > .card__body > .table-scroll > .table
   with .card--panel .card__body { padding-top: 0 } so filters in the
   header sit flush with the table). We don't override that — admin
   tables ARE 301-ui tables — but two surface tweaks fall outside the
   public-site assumptions:

   1. row hover. 301-ui ships rgba(255,255,255,0.02) for the public-
      site dark theme. On the admin's light .card--panel that's
      invisible; swap to var(--bg-soft) which reads on both themes.
   2. thead column-name weight. The base uses --fw-medium (500) on a
      muted colour, which scans as washed-out at a glance. Bump to
      semi-bold so the column row visually anchors.

   We deliberately do NOT add sticky thead. CSS spec promotes
   overflow-x: auto on .table-scroll to overflow-y: auto too, so a
   sticky thead inside the wrapper sticks against .table-scroll's
   non-scrolling viewport and never bites page scroll. The 301-ui
   pattern for "long-content navigation" is .dashboard-content--
   fill-height + sticky .card, not sticky thead. Adopting that pattern
   is a separate layout pass; we accept page-scroll-past-thead in the
   meantime, same as the public-site's 301-ui list views. */

.table thead th {
  font-weight: var(--fw-semibold, 600);
  letter-spacing: 0.01em;
}
.table tbody tr {
  transition: background var(--transition-fast, 150ms ease);
}
.table tbody tr:hover {
  background: var(--bg-soft);
}

/* Mobile sidebar overlay — drawer-style hide/show beneath admin-header.
   301-ui's .sidebar is sticky on desktop; on mobile we slide it in as a
   drawer when the burger toggles `.is-open`. */
.sidebar-overlay {
  display: none;
  position: fixed;
  inset: 48px 0 0 0;
  background: rgba(0, 0, 0, 0.4);
  z-index: calc(var(--z-sidebar, 100) - 1);
}
.sidebar.is-open ~ .sidebar-overlay { display: block; }
@media (max-width: 1023px) {
  .sidebar {
    position: fixed;
    left: 0;
    top: 48px;
    bottom: 0;
    width: 260px;
    z-index: var(--z-sidebar, 100);
    transform: translateX(-100%);
    transition: transform 200ms ease-out;
  }
  .sidebar.is-open { transform: translateX(0); }
  /* On mobile the .app-shell grid collapses to a single column so the
     sidebar can sit on top instead of beside the content. */
  .app-shell {
    grid-template-columns: 1fr;
    grid-template-areas: "content";
  }
}

/* ========== LANG TABS (casino-specific) ========== */
.lang-tabs {
  display: flex;
  gap: 2px;
  background: var(--bg-soft);
  border-radius: var(--r-pill, 999px);
  padding: 2px;
  width: fit-content;
}
.lang-tab {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: var(--space-1) var(--space-3);
  border-radius: var(--r-pill, 999px);
  border: none;
  background: transparent;
  color: var(--text-muted);
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  cursor: pointer;
  transition: all var(--transition-fast);
}
.lang-tab .icon { width: 1.2em; height: 1.2em; flex-shrink: 0; }
.lang-tab:hover { color: var(--text-main); }
.lang-tab.is-active { background: var(--primary); color: #fff; }
.lang-panel { display: none; }
.lang-panel.is-active { display: block; }

/* Per-lang spintax preset tabs (Sites page) */
[role="tablist"] .btn--ghost.is-active {
  background: var(--primary-soft, var(--surface-2));
  color: var(--primary);
  border-color: var(--primary);
}

/* ========== FIELD EXTENSIONS (casino-specific) ========== */
/* Label row: label left, badges/counter right */
.field__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: var(--space-1);
}
.field__row .field__label { margin-bottom: 0; }

/* Per-field save button — don't stretch in column/block context */
.field__save {
  margin-top: var(--space-2);
  align-self: flex-start;
  width: auto;
}

/* SEO textarea: compact, resizable */
textarea.seo-field { min-height: auto; resize: vertical; }

/* Compact numeric input used in site_casinos table (sort_order, Boost).
   Spinner arrows stripped — autosave fires on every change, we don't
   need them — and width set explicitly so the value is actually visible. */
.num-input {
  width: 5ch;
  text-align: right;
  padding-inline: var(--space-2);
  -moz-appearance: textfield;
  appearance: textfield;
}
.num-input::-webkit-outer-spin-button,
.num-input::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

/* ========== CODE EDITOR (HTML syntax highlight overlay) ========== */
.code-editor { position: relative; }

/* Textarea: transparent text, visible caret, captures all input */
.code-editor .content-editor {
  color: transparent;
  caret-color: var(--text-main);
  background: transparent;
  position: relative;
  z-index: 1;
  width: 100%;
  min-height: 300px;
  font-family: monospace;
  font-size: var(--fs-sm);
  resize: vertical;
}

/* Backdrop: renders highlighted HTML behind the textarea */
.code-editor__backdrop {
  position: absolute;
  inset: 0;
  margin: 0;
  overflow: hidden;
  pointer-events: none;
  /* Match textarea font/padding exactly */
  font-family: monospace;
  font-size: var(--fs-sm);
  line-height: var(--lh-control);
  padding: var(--control-pad-y) var(--control-pad-x);
  /* Visible bg + border (textarea is transparent) */
  background: var(--input-bg);
  border: var(--border-1) solid var(--input-border);
  border-radius: var(--r-field);
  /* Same word-wrap as textarea */
  white-space: pre-wrap;
  word-wrap: break-word;
  overflow-wrap: break-word;
  color: var(--text-main);
}
.code-editor__backdrop code {
  font-family: inherit;
  font-size: inherit;
}

/* Focus ring on backdrop when textarea is focused */
.code-editor .content-editor:focus-visible + .code-editor__backdrop {
  border-color: var(--brand);
  box-shadow: 0 0 0 2px color-mix(in oklab, var(--brand), transparent 80%);
}
/* Hide textarea's own focus ring (backdrop handles it) */
.code-editor .content-editor:focus-visible {
  outline: none;
  border-color: transparent;
  box-shadow: none;
}

/* Syntax token colors */
.hl-tag { color: var(--primary); }
.hl-attr { color: var(--warning); }
.hl-val { color: var(--success); }
.hl-ent { color: var(--info); }

/* ========== CASINO META GRID (slug, domain, license — Catalog metadata card) ==
   Auto-fitting grid so the field count can grow without forcing the
   row layout to wrap unevenly — every column gets a sensible min width
   and the grid reflows from 4 cols on desktop to 2 / 1 on narrow. */
.casino-meta-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: var(--space-3);
}
.casino-meta-grid .field {
  margin: 0;
}
.casino-meta-grid .field__label {
  font-size: var(--fs-xs);
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}
.casino-meta-grid .input[disabled] {
  font-family: var(--font-mono, monospace);
  color: var(--text-muted);
  background: var(--bg-soft);
  cursor: default;
}

/* ========== SVG PREVIEW ========== */
.svg-preview-wrap {
  display: flex;
  gap: var(--space-4);
}
.svg-preview-col {
  flex: 1;
  min-width: 0;
}
.svg-preview {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 64px;
  max-height: 120px;
  padding: var(--space-3);
  border: 1px dashed var(--border-subtle);
  border-radius: var(--r);
  overflow: hidden;
  /* `--preview-bg` / `--preview-fg` are set inline per-box from the
     site's stored theme tokens (with platform defaults as fallback)
     so an SVG using fill="currentColor" picks up the matching
     light/dark text color and is visible on both backgrounds. */
  background: var(--preview-bg, var(--bg));
  color: var(--preview-fg, currentColor);
}
.svg-preview--dark { --preview-bg: #22223a; --preview-fg: #e8e8ed; }
.svg-preview--light { --preview-bg: #f3f5f6; --preview-fg: #000; }
.svg-preview svg, .svg-preview img {
  max-width: 100%;
  max-height: 80px;
  height: auto;
}
.svg-preview--empty {
  color: var(--text-muted);
  font-size: var(--fs-xs);
}

/* ========== VARIABLE LIST (drawer — uses 301-ui detail-list) ========== */
.drawer .detail-list code {
  color: var(--primary);
}
.drawer .detail-row dt {
  min-width: 140px;
}

/* ========== LANGUAGE STATUS PANEL ========== */
.lang-status-panel {
  display: flex;
  gap: var(--space-2);
  flex-wrap: wrap;
  margin-bottom: var(--space-2);
}
.lang-status {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-1) var(--space-3);
  border-radius: var(--r-pill);
  font-size: var(--fs-sm);
  border: 1px solid var(--border-subtle);
}
.lang-status strong { font-weight: var(--fw-semibold); }
.lang-status--hidden { color: var(--text-muted); }
.lang-status--incomplete {
  border-color: color-mix(in srgb, var(--warning) 40%, transparent);
  color: var(--warning);
}
.lang-status--ready {
  border-color: color-mix(in srgb, var(--success) 40%, transparent);
  color: var(--success);
}

/* ========== PAGE HEADER DETAIL (back link + h1 on entity pages) ========== */
.page-header--detail {
  margin-top: var(--space-4);
}
.page-header--detail .page-header__identity {
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
  flex: 1;
  min-width: 0;
}
.page-header--detail .page-header__identity h1 {
  margin: 0;
}
.page-header--detail .page-header__identity .badge {
  position: relative;
  top: -1px;
}

/* ========== PAGE HEADER ACTIONS ========== */
.page-header__actions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

/* ========== BREADCRUMB =========================================
   Site-scoped admin pages render a breadcrumb chain above the h1 so
   the title can stay entity-only ("Stake Casino") while context lives
   in the chain ("Sites / Bitcoin Casinos / Casinos / Stake Casino").
   See ADMIN-STYLEGUIDE.md "Breadcrumb vs h1". */
.breadcrumb {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-1);
  font-size: var(--fs-sm, 0.875rem);
  color: var(--text-muted);
  min-width: 0;
}
.breadcrumb__segment {
  display: inline-flex;
  align-items: center;
  min-width: 0;
}
/* `/` separator before every segment except the first. */
.breadcrumb__segment + .breadcrumb__segment::before {
  content: "/";
  margin-right: var(--space-1);
  color: var(--text-muted);
  opacity: 0.6;
}
.breadcrumb__link {
  color: var(--text-muted);
  text-decoration: none;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 28ch;
}
.breadcrumb__link:hover {
  color: var(--text-main);
  text-decoration: underline;
}
.breadcrumb__leaf {
  color: var(--text-main);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 28ch;
}
/* Collapse-middle on narrow viewports: hide all but the first link
   and the leaf, replace the gap with a single ellipsis pseudo-segment.
   Keeps "Sites / … / FAQs" readable instead of wrapping into 3 rows. */
@media (max-width: 720px) {
  .breadcrumb__segment:not(:first-child):not(:nth-last-child(1)) .breadcrumb__link,
  .breadcrumb__segment:not(:first-child):not(:nth-last-child(1)) .breadcrumb__leaf {
    display: none;
  }
  .breadcrumb__segment:not(:first-child):not(:nth-last-child(1)) {
    width: 1ch;
  }
  .breadcrumb__segment:not(:first-child):not(:nth-last-child(1))::after {
    content: "…";
    color: var(--text-muted);
    opacity: 0.6;
  }
}

/* ========== ICON + TEXT ALIGNMENT ========== */
.icon--flag { width: 1.2em; height: 1.2em; flex-shrink: 0; }
.cluster--tight { gap: var(--space-1); }

/* ========== CARD HEADER WITH ACTIONS ========== */
.card__header .cluster--split {
  justify-content: space-between;
  width: 100%;
}

/* ========== BONUS LIST ========== */
.bonus-list { display: flex; flex-direction: column; gap: var(--space-3); }
.bonus-item {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2);
  border: 1px solid var(--border-subtle);
  border-radius: var(--r-field);
}
.bonus-item__thumb {
  flex-shrink: 0;
  width: 120px;
  height: 68px;
  border-radius: var(--r-field);
  overflow: hidden;
  background: var(--bg-soft);
  display: flex;
  align-items: center;
  justify-content: center;
}
.bonus-item__thumb img { width: 100%; height: 100%; object-fit: cover; }
.bonus-item__info {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.bonus-item__actions { display: flex; gap: var(--space-1); flex-shrink: 0; }

/* ========== UPLOAD ZONE ========== */
.upload-zone {
  border: 2px dashed var(--border-subtle);
  border-radius: var(--r-field);
  padding: var(--space-4);
  text-align: center;
  cursor: pointer;
  transition: border-color var(--transition-fast);
  position: relative;
  min-height: 100px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.upload-zone:hover,
.upload-zone.is-dragover { border-color: var(--primary); }
.upload-zone__prompt { color: var(--text-muted); display: flex; align-items: center; gap: var(--space-2); }
.upload-zone__preview { width: 100%; }
.upload-zone__preview img { max-width: 100%; max-height: 200px; border-radius: var(--r-field); }
.upload-zone__preview:not([hidden]) ~ .upload-zone__prompt { display: none; }

/* ========== CRYPTO GRID ========== */
.crypto-grid {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}
.crypto-grid__group { display: flex; flex-direction: column; gap: 2px; }
.crypto-grid__item {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-1) var(--space-2);
  border-radius: var(--r-field);
  cursor: pointer;
  font-size: var(--fs-sm);
  white-space: nowrap;
}
.crypto-grid__item:hover { background: var(--bg-soft); }
.crypto-grid__item input[type="checkbox"] { accent-color: var(--primary); }
.crypto-grid__item--sub { padding-left: var(--space-4); }
/* 3:2 flag chip — used by language + currency pickers */
.flag-icon {
  width: 20px;
  height: 14px;
  flex-shrink: 0;
  border-radius: 2px;
  box-shadow: 0 0 0 1px rgba(255,255,255,0.08);
  object-fit: cover;
  display: block;
}
.crypto-grid__variants.is-disabled {
  opacity: 0.4;
  pointer-events: none;
}

/* ========== FAQ LIST ========== */
.faq-list { display: flex; flex-direction: column; }
.faq-item {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-4);
  border-bottom: 1px solid var(--border-subtle);
}
.faq-item:last-child { border-bottom: none; }
.faq-item__num { font-weight: var(--fw-bold); color: var(--text-muted); min-width: 1.5em; }
.faq-item__info { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }

/* ========== PROMPT LIST ========== */
.prompt-list { display: flex; flex-direction: column; }
.prompt-item {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-4);
  border-bottom: 1px solid var(--border-subtle);
}
.prompt-item:last-child { border-bottom: none; }
.prompt-item__info { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.prompt-preview { font-family: monospace; opacity: 0.7; }

/* ========== COMPACT TABLE INPUT ========== */
.input--sm {
  font-size: var(--fs-xs);
  padding: var(--space-1) var(--space-2);
  min-height: auto;
}

/* ========== STRINGS TABLE LANG COLUMNS ========== */
.strings-lang-col.is-hidden { display: none; }

/* ========== ARTICLE GROUPS (collapsible categories) ========== */
/* Composes <details class="card card--panel field-details articles-group">.
   Card panel already supplies border + radius + padding, so we
   neutralize field-details' duplicates and let the card visuals win.
   The chevron + summary semantics from field-details are kept. */
.articles-group.field-details {
  border: 1px solid var(--border-subtle);
  border-radius: var(--r-field);
  padding: 0;
}
.articles-group > .field-details__summary {
  /* Override card__header's default cursor with one that signals toggle. */
  cursor: pointer;
}
.articles-group > .field-details__body {
  padding-top: 0;
}

/* ========== AUTOSIZE TEXTAREA (long string values) ========== */
/* field-sizing: content lets supporting browsers grow with content
   automatically. admin.js mirrors the behavior on load + input for the
   rest. The min-height matches input--sm so single-line values still
   look like inline inputs. */
textarea.input--autosize {
  resize: vertical;
  min-height: 1.8em;
  line-height: 1.4;
  font-family: inherit;
  field-sizing: content;
}

/* ========== CHAR COUNTER ========== */
.field__counter {
  font-size: var(--fs-xs);
  font-weight: var(--fw-medium);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.field__counter--ok { color: var(--success); }
.field__counter--warn { color: var(--warning); }
.field__counter--over { color: var(--danger); }

/* ========== SCORE GRID (casino-specific) ========== */
.score-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap: var(--space-3);
}
.score-grid .field { margin-bottom: 0; }
.score-grid .input[type="number"] { -moz-appearance: textfield; }
.score-grid .input[type="number"]::-webkit-inner-spin-button { opacity: 1; }

/* ========== SECTION LABEL ========== */
.section-label {
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  color: var(--text-subtle, var(--text-muted));
  text-transform: uppercase;
  letter-spacing: 0.05em;
  margin-bottom: var(--space-2);
}

/* ========== FORM STATUS ========== */
.form-status {
  padding: var(--space-2) var(--space-3);
  border-radius: var(--r-field, 6px);
  font-size: var(--fs-sm);
  margin-bottom: var(--space-3);
}
.form-status[hidden] { display: none; }
.form-status--error { background: color-mix(in srgb, var(--danger) 15%, var(--bg-soft)); color: var(--danger); }
.form-status--success { background: color-mix(in srgb, var(--success) 15%, var(--bg-soft)); color: var(--success); }
.form-status--info { background: color-mix(in srgb, var(--primary) 15%, var(--bg-soft)); color: var(--primary); }

/* ========== MOBILE TOPBAR (fallback) ========== */
.mobile-topbar {
  display: none;
  padding: var(--space-3) var(--space-4);
  background: var(--sidebar-bg);
  border-bottom: 1px solid var(--border-subtle);
  align-items: center;
  justify-content: space-between;
}
@media (max-width: 1023px) {
  .mobile-topbar { display: flex; }
}
.mobile-topbar__brand { font-weight: var(--fw-bold); }

/* ========== DRAWER SIZE MODIFIERS ========== */
/* 301-ui base ships .drawer__panel { max-width: 560px } only.
   .drawer--lg widens for tabbed drawers that hold tables (e.g.
   /admin/sites Locales tab needs lang/host/path_prefix/default cols). */
.drawer.drawer--lg .drawer__panel {
  max-width: 720px;
}

/* ========== TEXT COLOR UTILITIES (301-ui canon) ==========
   Used for momentary feedback (e.g. clipboard-copy icon flash via
   flashIcon in deploy-runner.js); .icon inherits currentColor so
   adding .text-ok to the button or the svg paints the glyph green.
   ----------------------------------------------------------- */
.text-ok      { color: var(--text-success, var(--ok, #22c55e)); }
.text-danger  { color: var(--text-warning, var(--danger, #ef4444)); }

/* ========== TABS INSIDE DRAWERS ========== */
/* 301-ui canon: tabs in a tabbed drawer live in their OWN flex row
   between drawer__header and drawer__body, not inside the scrolling
   body. That cleanly sidesteps the sticky-positioning + stacking-
   context bugs we ran into when the tabs were a direct child of the
   scrollable body. drawer__panel is `display:flex; flex-direction:
   column`; tabbar gets `flex-shrink:0`, body keeps `flex:1`. */
.drawer__tabbar {
  flex-shrink: 0;
  padding: var(--space-1) var(--space-4) 0;
  border-bottom: 1px solid var(--border-subtle, var(--border));
  background: var(--bg);
}
.drawer__tabbar > .tabs[data-tabs] {
  margin: 0;
}

/* ========== FIELDSET / LEGEND (drawer forms) ========== */
.drawer__body .inspector-fieldset {
  margin-bottom: var(--space-3);
}
.drawer__body .inspector-fieldset > legend {
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  color: var(--text-main);
}
.drawer__body .inspector-fieldset .stack--sm {
  margin-top: var(--space-2);
}
.drawer__body .inspector-fieldset > .input,
.drawer__body .inspector-fieldset > .textarea,
.drawer__body .inspector-fieldset > select,
.drawer__body .inspector-fieldset > .upload-zone {
  width: 100%;
  display: block;
}
.drawer__body .inspector-fieldset > .textarea {
  resize: vertical;
  min-height: 80px;
}
/* Fieldsets inside .cluster share space equally */
.drawer__body .cluster > .inspector-fieldset {
  flex: 1;
  min-width: 0;
}

/* ========== WIZARD / TOKEN PREVIEW ========== */
.token-preview {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin-top: var(--space-2);
}
.token-preview__row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-1);
}
.token-preview__swatch {
  width: 24px; height: 24px; min-width: 24px;
  border-radius: 4px;
  border: 1px solid var(--border-subtle);
}
.token-preview__label {
  font-size: var(--fs-xs);
  color: var(--text-muted);
}

/* Brand upload zone */
[data-brand-upload] { cursor: pointer; }
[data-brand-upload] .upload-zone__preview img {
  max-height: 120px; width: auto; margin: 0 auto; display: block;
}

/* ========== UPLOAD URL ROW ========== */
.upload-url-row {
  margin-top: var(--space-2);
}
.upload-url-row input {
  flex: 1;
  min-width: 0;
}

/* ========== TEMPLATES TABLE ========== */
/* Filter bar above the table — sits inside card__header */
.layout-main--wide {
  max-width: 1440px;
}

.tpl-toolbar {
  display: flex;
  gap: var(--space-3);
  align-items: center;
  flex-wrap: wrap;
}
.tpl-search { flex: 1 1 24rem; min-width: 0; max-width: 40rem; }
.tpl-filters { display: flex; gap: var(--space-2); flex-wrap: wrap; }

.tpl-help {
  margin: 0 0 var(--space-4);
  background: var(--surface-1, var(--bg-elevated));
}

.tpl-help__summary {
  align-items: flex-start;
}

.tpl-help__summary .icon {
  margin-top: 2px;
  color: var(--primary);
  flex: 0 0 auto;
}

.tpl-help__summary-copy {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.tpl-help__body {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.tpl-help-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-3);
}

.tpl-help-card {
  padding: var(--space-3);
  border: 1px solid var(--border-subtle);
  border-radius: var(--r-field, 6px);
  background: var(--surface-2, var(--bg-soft));
}

.tpl-help-card h3 {
  margin: 0 0 var(--space-2);
  font-size: var(--fs-base);
}

.tpl-help-list {
  margin: 0;
  padding-left: 1.1rem;
  color: var(--text-muted);
  display: grid;
  gap: var(--space-2);
}

.tpl-help-list li code {
  white-space: nowrap;
}

/* Inline help icon next to cheat-sheet headers / drawer titles. Links
   to /admin/help/variables. Sized to sit flush with the heading text;
   muted by default, picks up the primary accent on hover/focus. */
.help-icon-link {
  display: inline-flex;
  align-items: center;
  margin-left: var(--space-2);
  vertical-align: middle;
  color: var(--text-muted);
  opacity: 0.7;
  text-decoration: none;
  transition: opacity 0.15s ease, color 0.15s ease;
}
.help-icon-link:hover,
.help-icon-link:focus-visible {
  color: var(--primary);
  opacity: 1;
}
.help-icon-link .icon {
  width: 1em;
  height: 1em;
}

/* Rendered Markdown documents (admin help pages mirroring docs/). The
   subset matches the renderer in worker/src/markdown.ts: headings,
   paragraphs, lists, tables, code blocks, blockquotes, hr. Tuned to
   read like a doc page rather than a settings card. */
.md-doc { line-height: 1.55; }
.md-doc h1, .md-doc h2, .md-doc h3, .md-doc h4 {
  margin: var(--space-5) 0 var(--space-2);
  line-height: 1.25;
}
.md-doc h1 { font-size: 1.6rem; margin-top: 0; }
.md-doc h2 { font-size: 1.25rem; padding-bottom: var(--space-1); border-bottom: 1px solid var(--border-subtle); }
.md-doc h3 { font-size: 1.05rem; }
.md-doc p { margin: var(--space-3) 0; }
.md-doc ul, .md-doc ol { margin: var(--space-3) 0; padding-left: 1.4em; }
.md-doc li { margin: var(--space-1) 0; }
.md-doc code {
  background: var(--bg-soft);
  padding: 0.1em 0.35em;
  border-radius: 3px;
  font-size: 0.92em;
}
.md-doc pre {
  background: var(--bg-soft);
  padding: var(--space-3);
  border-radius: var(--r);
  overflow-x: auto;
  font-size: 0.9em;
  line-height: 1.4;
}
.md-doc pre code { background: transparent; padding: 0; }
.md-doc blockquote {
  margin: var(--space-3) 0;
  padding: var(--space-2) var(--space-3);
  border-left: 3px solid var(--border);
  color: var(--text-muted);
}
.md-doc hr {
  margin: var(--space-5) 0;
  border: 0;
  border-top: 1px solid var(--border-subtle);
}
.md-doc a { color: var(--primary); }
.md-table {
  border-collapse: collapse;
  width: 100%;
  margin: var(--space-3) 0;
  font-size: 0.92em;
}
.md-table th, .md-table td {
  text-align: left;
  padding: var(--space-2) var(--space-3);
  border-bottom: 1px solid var(--border-subtle);
  vertical-align: top;
}
.md-table th { background: var(--bg-soft); font-weight: 600; }

/* Filter chip dropdowns reuse 301-ui .btn-chip + .dropdown__menu */
.tpl-filter-chip .dropdown__menu { min-width: 200px; }
.tpl-filter-chip .dropdown__item.is-active {
  background: color-mix(in srgb, var(--primary) 14%, transparent);
  color: var(--text-main);
}
.tpl-filter-chip .dropdown__item.is-disabled {
  opacity: .45;
  cursor: not-allowed;
  pointer-events: none;
}

/* Table variant. Column-width strategy: every column except Preview
   shrinks to its content (width:1% + nowrap is the canonical "auto-
   shrink" pattern). Preview is the flex column that absorbs all
   remaining space, since it carries the longest content (truncated
   spintax body) and is what the operator scans visually. */
.table--templates { min-width: 1040px; }
.table--templates tbody tr { cursor: pointer; }
.table__th-scope    { width: 1%; white-space: nowrap; }
.table__th-field    { width: 1%; white-space: nowrap; }
.table__th-status   { width: 1%; white-space: nowrap; }
.table__th-updated  { width: 1%; white-space: nowrap; }
.table__th-actions  { width: 1%; white-space: nowrap; }
.table__th-checkbox { width: 1%; padding-right: 0; }
.table__cell-checkbox { padding-right: 0; }

/* ========== BULK ACTION BAR (casino catalog) ========== */
.bulk-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--space-2) var(--space-3);
  border-top: 1px solid var(--border-subtle);
  background: var(--bg-soft);
  gap: var(--space-3);
}
.bulk-bar[hidden] { display: none; }
.bulk-bar__count {
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  font-variant-numeric: tabular-nums;
}
.casinos-row-hidden { display: none; }

/* Scope cell: icon + label + optional badge. Use inline-flex on a
   wrapper inside the td (not on the td itself) — display:flex on a
   <td> drops it out of the table-cell flow and breaks row-height
   alignment with the other cells. The td itself just nowraps + sets
   the colour; alignment lives on the wrapper. */
.tpl-cell-scope {
  white-space: nowrap;
  color: var(--text-main);
  font-weight: var(--fw-medium);
}
.tpl-cell-scope > .tpl-cell-scope__inner {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}
.tpl-cell-scope .icon { color: var(--text-muted); flex: 0 0 auto; }

.tpl-cell-field code {
  background: var(--bg-soft);
  padding: 2px 6px;
  border-radius: var(--radius-xs);
  font-size: 0.85em;
  color: var(--text-main);
  white-space: nowrap;
}

.tpl-cell-preview {
  max-width: 0;        /* let flex layout shrink the cell */
  overflow: hidden;
}
.tpl-preview {
  display: block;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  color: var(--text-subtle);
  font-family: var(--font-mono, monospace);
  font-size: var(--fs-xs);
}

.tpl-cell-status   { white-space: nowrap; }
.tpl-cell-updated  { white-space: nowrap; font-size: var(--fs-xs); }
.tpl-cell-actions  { text-align: right; white-space: nowrap; }

/* Hover-reveal action icon (like 301-ui) */
.tpl-kebab {
  display: inline-flex;
  opacity: 0;
  color: var(--text-muted);
  transition: opacity var(--transition-fast);
}
.table--templates tbody tr:hover .tpl-kebab { opacity: 1; }

/* FAQ pairs: question row starts the pair, answer row is visually subordinate */
.tpl-row--faq-question { border-top: 1px solid var(--border-subtle, rgba(255,255,255,.06)); }
.tpl-row--faq-answer .tpl-cell-scope { opacity: 0.35; }
.tpl-row--faq-answer .tpl-cell-field code { opacity: 0.7; }

/* Missing rows: de-emphasize preview + field */
.table--templates tr[data-status="missing"] .tpl-cell-field code,
.table--templates tr[data-status="missing"] .tpl-cell-preview {
  opacity: 0.6;
}

.tpl-empty {
  padding: var(--space-6) var(--space-4);
  text-align: center;
}

@media (max-width: 1100px) {
  .layout-main--wide {
    max-width: 1200px;
  }

  .tpl-help-grid {
    grid-template-columns: 1fr;
  }
}

/* ========== SPINTAX EDITOR ========== */
.spintax-editor-wrap {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: var(--space-3);
  align-items: start;
}
.spintax-textarea {
  min-height: 160px;
  font-family: var(--font-mono, monospace);
  font-size: var(--fs-sm);
  line-height: 1.5;
}
.spintax-system-vars {
  padding: var(--space-2);
  background: var(--bg-soft);
  border-radius: var(--r-field, 6px);
  min-width: 160px;
}

/* ========== VAR REGISTRY (cheat sheet) ==========================
   The variable registry surfaces ~70 vars across 10+ categories. The
   v1 layout flowed every section-label inline with its chips inside
   one big flex cluster — categories blurred together as the list grew
   and the operator had no way to find a specific %Var% short of CTRL+F.
   v2 (this block) lays out one structured section per group with a
   shared filter at the top, so the eye scans by category and the
   keyboard finds a chip by name. Used on /admin/sites (preset editor),
   /admin/casinos/:id (drawer), /admin/templates (help disclosure). */
.var-registry {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
/* Controls strip: category chips on top, text filter underneath. The
   wrapper is its own column-flex so the inner table-search can keep
   the canonical pill width without inheriting the parent's vertical
   stretch quirk. */
.var-registry__controls {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.var-registry__cats {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1) var(--space-2);
}
.var-registry__cats .btn-chip {
  /* 301-ui .btn-chip is the canonical pill control — already wired
     up with hover/active in the base. We only adjust the active
     state to read as "currently selected category" rather than the
     default "interactive". */
  font-size: var(--fs-xs);
  letter-spacing: 0.02em;
}
.var-registry__cats .btn-chip.is-active {
  background: var(--primary);
  color: #fff;
  border-color: var(--primary);
}
.var-registry__filter {
  /* .table-search base in 301-ui carries `flex: 1 1 16rem` — great
     inside a row-flex toolbar (the search field grows to fill the
     row), but here the parent (.var-registry__controls) is column-
     flex and the inherited grow tries to expand the label vertically
     into a 256px-tall pill. Pin to auto so it sizes to the canonical
     control min-height (~33px) and respects max-width. */
  flex: 0 0 auto;
  max-width: 22rem;
}
.var-registry__group {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}
/* Section label: small caps, muted, sits above its chip row. Border-
   bottom gives a subtle separator without a heavyweight rule between
   groups (the gap on .var-registry already separates rows visually). */
.var-registry__group-label {
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold, 600);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-muted);
  padding-bottom: var(--space-1);
  border-bottom: 1px solid var(--border-subtle);
}
.var-registry__chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
  padding-top: var(--space-1);
}
.var-registry__chips code {
  font-size: var(--fs-xs);
  color: var(--primary);
  cursor: pointer;
  padding: 2px var(--space-1);
  border-radius: 3px;
  transition: background var(--transition-fast, 150ms ease);
}
.var-registry__chips code:hover {
  background: color-mix(in srgb, var(--primary) 10%, transparent);
}
/* Filter hides individual chips via [hidden]; whole-group hide kicks
   in when the filter empties a group — the JS toggles
   .var-registry__group[hidden] alongside chip-level hidden so the
   header doesn't dangle above an empty row. */
.var-registry__group[hidden] {
  display: none;
}
@media (max-width: 767px) {
  .spintax-editor-wrap { grid-template-columns: 1fr; }
}

/* Inline preset-row lint (Phase 5 cosmetic). Surfaces #set bindings that
   redefine system / synthetic / casino-ctx names — those are won by the
   runtime regardless, so the preset row is dead weight. */
.preset-validator {
  grid-column: 1 / -1;
  margin-top: var(--space-2);
  padding: var(--space-2) var(--space-3);
  background: color-mix(in srgb, var(--color-warning, #b87a13) 8%, transparent);
  border: 1px solid color-mix(in srgb, var(--color-warning, #b87a13) 30%, transparent);
  border-radius: var(--r-field, 6px);
  font-size: var(--fs-sm);
}
.preset-validator__title {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  font-weight: 500;
  margin-bottom: var(--space-1);
}
.preset-validator__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.preset-validator__list li {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-family: var(--font-mono, monospace);
  font-size: var(--fs-xs);
}
.preset-validator__line {
  display: inline-block;
  min-width: 32px;
  padding: 1px 6px;
  border-radius: 3px;
  background: var(--bg-soft);
  color: var(--text-muted);
  font-size: 11px;
}
.preset-validator__verdict {
  color: var(--text-muted);
  font-family: inherit;
}

/* Saved! state — green flash on save buttons */
.btn.is-saved,
.btn.is-saved:disabled {
  background: var(--color-success, #2e9f5c) !important;
  border-color: var(--color-success, #2e9f5c) !important;
  color: #fff !important;
  opacity: 1 !important;
}

/* ========== SPINTAX PREVIEW ========== */
.spintax-preview-row {
  gap: var(--space-2);
  align-items: flex-end;
  flex-wrap: wrap;
}
.field-inline {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.field-inline__label {
  font-size: var(--fs-xs);
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.spintax-preview-output {
  margin-top: var(--space-3);
  border: 1px solid var(--border-subtle);
  border-radius: var(--r-field, 6px);
  overflow: hidden;
  background: var(--bg-soft);
}
.spintax-preview-label {
  padding: var(--space-1) var(--space-3);
  background: color-mix(in srgb, var(--primary) 6%, transparent);
  border-bottom: 1px solid var(--border-subtle);
}
.spintax-preview-text {
  padding: var(--space-3);
  font-size: var(--fs-sm);
  line-height: 1.6;
  max-height: 420px;
  overflow-y: auto;
  color: var(--text-main);
}
.spintax-preview-text p { margin: 0 0 var(--space-2); }
.spintax-preview-text p:last-child { margin-bottom: 0; }
.spintax-preview-text h2,
.spintax-preview-text h3 {
  font-size: var(--fs-base);
  font-weight: 600;
  margin: var(--space-3) 0 var(--space-1);
}
.spintax-preview-vars-wrap {
  border-top: 1px solid var(--border-subtle);
  padding: var(--space-2) var(--space-3);
}
.spintax-preview-vars-wrap summary {
  cursor: pointer;
  font-size: var(--fs-xs);
}
.spintax-preview-vars {
  margin-top: var(--space-2);
  max-height: 220px;
  overflow-y: auto;
  font-size: var(--fs-xs);
}
.spintax-var-row {
  padding: 2px 0;
  display: flex;
  gap: var(--space-2);
  align-items: baseline;
}
.spintax-var-row code {
  color: var(--primary);
  flex-shrink: 0;
}
.spintax-compare-block {
  padding: var(--space-2) var(--space-3);
  border-bottom: 1px solid var(--border-subtle);
}
.spintax-compare-block:last-child { border-bottom: none; }
.spintax-compare-label { margin-bottom: var(--space-1); }
.spintax-compare-text {
  font-size: var(--fs-sm);
  line-height: 1.5;
  color: var(--text-main);
}

/* ========== FAQ PAGE ========== */
.faq-parent-select { max-width: 400px; width: 100%; }

/* ========== STYLED SELECT ========== */
select.input {
  appearance: none;
  -webkit-appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='%238a8aa0'%3E%3Cpath d='M7.41 8.58 12 13.17l4.59-4.59L18 10l-6 6-6-6z'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right var(--space-3) center;
  padding-right: calc(var(--space-3) + 20px);
  cursor: pointer;
}
select.input:focus-visible {
  outline: none;
  border-color: var(--brand);
  box-shadow: 0 0 0 2px color-mix(in oklab, var(--brand), transparent 80%);
}

/* ========== WIZARD BRAND PREVIEW (New Site → Step 2 Brand) ========== */
/* Two slots side-by-side with equal height; SVG content scales to fit
   while keeping aspect ratio. Caption underneath each slot so the
   editor sees what they're picking, even when logo and icon have
   wildly different aspect ratios (text-logo vs square mark). */
.wizard-brand-preview {
  display: flex;
  align-items: stretch;
  gap: var(--space-3);
  margin-top: var(--space-3);
}
.wizard-brand-preview[hidden] { display: none; }
.wizard-brand-preview__slot {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  background: var(--bg-soft);
  border: 1px solid var(--border-subtle);
  border-radius: var(--r-field);
  padding: var(--space-3);
}
.wizard-brand-preview__slot--logo {
  flex: 0 1 320px;
  min-width: 0;
}
.wizard-brand-preview__slot--icon {
  flex: 0 0 auto;
  width: 96px;
}
.wizard-brand-preview__inner {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  height: 64px;
  overflow: hidden;
}
.wizard-brand-preview__inner svg {
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  display: block;
}
.wizard-brand-preview__caption {
  font-size: var(--fs-xs);
  color: var(--text-muted);
  text-align: center;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

/* ========== COVERAGE TABLE ========== */
.coverage-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--fs-sm);
}
.coverage-table th {
  text-align: left;
  padding: var(--space-2) var(--space-3);
  border-bottom: 1px solid var(--border-subtle);
  font-weight: var(--fw-medium);
  white-space: nowrap;
}
.coverage-table td {
  padding: var(--space-2) var(--space-3);
  border-bottom: 1px solid var(--border-subtle);
}
.coverage-table tr:last-child td { border-bottom: none; }
.coverage-label {
  font-weight: var(--fw-medium);
  white-space: nowrap;
  min-width: 80px;
  color: var(--text-muted);
}
.coverage-cell-link {
  display: block;
  color: inherit;
  text-decoration: none;
  border-radius: var(--radius-sm);
}
.coverage-cell-link:hover {
  background: var(--bg-soft);
}
.coverage-cell-link:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
}
.progress-cell {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-width: 140px;
}
.progress-label {
  font-size: var(--fs-xs);
  color: var(--text-muted);
  min-width: 40px;
  font-variant-numeric: tabular-nums;
}
.progress-bar {
  height: 6px;
  border-radius: 3px;
  background: var(--bg-soft);
  flex: 1;
  min-width: 60px;
}
.progress-bar__fill {
  height: 100%;
  border-radius: 3px;
  transition: width 0.3s;
}
.progress-bar__fill--green { background: var(--success); }
.progress-bar__fill--yellow { background: var(--warning); }
.progress-bar__fill--red { background: var(--danger); }
.progress-pct {
  font-size: var(--fs-xs);
  font-weight: var(--fw-medium);
  min-width: 32px;
  text-align: right;
  font-variant-numeric: tabular-nums;
}
/* Per-tier colour echoes the progress-bar fill so the bar can be
   hidden on narrow viewports without losing the signal. */
.progress-pct--green { color: var(--success); }
.progress-pct--yellow { color: var(--warning); }
.progress-pct--red { color: var(--danger); }

/* Mobile: drop the bar (~60-100px) — the row's two numbers (N/N and
   the colour-coded %) carry the same information in ~half the
   width. The coverage table is two locale columns wide at minimum,
   so even a 60px saving per cell makes it fit a 360px viewport
   without horizontal scroll. */
@media (max-width: 768px) {
  .progress-bar { display: none; }
  .progress-cell { min-width: 0; gap: var(--space-1); }
  .progress-label { min-width: 0; }
  .progress-pct { min-width: 0; }
}

/* Locales table mobile: hide the "default"/"inactive" badge column.
   The default-row is always first (ORDER BY is_default DESC) and the
   "Make default" button is absent on it, so the signal stays implicit
   without the dedicated column eating ~70px on a 360px viewport. */
@media (max-width: 768px) {
  .locale-table__badge-col { display: none; }
}

/* ========== SHARED ADMIN PATTERNS ========== */
.card__body--flush { padding: 0; }
.card__body--scroll { overflow-x: auto; }
.empty-state { padding: var(--space-4); text-align: center; }
.text-nowrap { white-space: nowrap; }
.text-ellipsis { max-width: 300px; overflow: hidden; text-overflow: ellipsis; }
.section-label { font-size: var(--fs-xs); text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-muted); }

/* Two-column key/value grid for definition lists. Browser default lays
   <dt>/<dd> vertically with a dd margin-left lesencer — useless for
   read-only meta panels. This pattern: label-min / value-rest, gap
   between rows, muted label, mono value alignment. */
.dl-grid {
  display: grid;
  grid-template-columns: max-content 1fr;
  column-gap: var(--space-4);
  row-gap: var(--space-2);
  margin: 0;
  align-items: baseline;
}
.dl-grid > dt {
  color: var(--text-muted);
  font-size: var(--fs-sm);
  font-weight: 500;
  white-space: nowrap;
}
.dl-grid > dd {
  margin: 0;
  font-size: var(--fs-sm);
  word-break: break-word;
}

/* ========== DESIGN PAGE ========== */
.design-actions { display: flex; gap: var(--space-2); margin-top: var(--space-4); flex-wrap: wrap; align-items: center; }
.design-section { margin-top: var(--space-3); }
.bg-ctrl label { cursor: pointer; }
.bg-ctrl input[type="range"] { width: 90px; }
.bg-enabled-label { cursor: pointer; }
.design-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-4);
  align-items: start;
}
@media (max-width: 1100px) {
  .design-grid { grid-template-columns: 1fr; }
}

/* Token palette */
.token-panel { display: none; }
.token-panel.is-active { display: block; }

.token-row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-1) 0;
  border-bottom: 1px solid var(--border-subtle);
}
.token-row:last-child { border-bottom: none; }

.token-swatch {
  width: 32px;
  height: 32px;
  min-width: 32px;
  border-radius: var(--r-field, 6px);
  border: 1px solid var(--border-subtle);
  cursor: pointer;
  padding: 0;
  background: none;
}
.token-swatch::-webkit-color-swatch-wrapper { padding: 2px; }
.token-swatch::-webkit-color-swatch { border: none; border-radius: 3px; }

.token-hex {
  width: 180px;
  font-family: var(--font-mono, monospace);
  font-size: var(--fs-sm);
}

.token-label {
  flex: 1;
  font-size: var(--fs-sm);
  color: var(--text-muted);
  white-space: nowrap;
}

/* Background preview */
.bg-preview {
  aspect-ratio: 16 / 9;
  border-radius: var(--r-field, 6px);
  overflow: hidden;
  border: 1px solid var(--border-subtle);
  background: var(--bg-soft);
  position: relative;
}

.bg-controls {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
}
.bg-ctrl {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.bg-ctrl input[type="range"] { width: 90px; }
.bg-ctrl select { min-width: 90px; }

/* Color pick circles */
.bg-color-picks {
  display: flex;
  gap: var(--space-1);
  flex-wrap: wrap;
  min-height: 36px;
  align-items: center;
  padding: var(--space-1) 0;
}
.bg-color-pick {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  border: 2px solid transparent;
  cursor: pointer;
  transition: all 0.15s;
  position: relative;
}
.bg-color-pick:hover { transform: scale(1.15); }
.bg-color-pick.is-selected {
  border-color: var(--primary);
  box-shadow: 0 0 0 2px var(--primary);
}
.bg-color-pick__remove {
  position: absolute;
  top: -4px;
  right: -4px;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--danger);
  color: #fff;
  font-size: 10px;
  line-height: 14px;
  text-align: center;
  cursor: pointer;
  display: none;
}
.bg-color-pick:hover .bg-color-pick__remove { display: block; }

.form-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: var(--space-3);
}
.form-grid .field { margin: 0; }

/* ========== PER-SITE OVERRIDE EDITOR ==========================
   Two-column row: canon (read-only, muted) on the left, override
   (editable) on the right. The card-level border switches accent
   colour based on three editor states:
     .differs-from-canon — override value !== canon (default for live
                           or any meaningfully-edited row)
     .is-dirty           — pending change since last save
     (no class)          — override row exists but matches canon, or
                           no override at all (canon-inherited render)
   Mobile: stack to single column under 1100px so each cell can
   keep readable line length without horizontal scroll. */
.override-row__grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-3);
}
@media (max-width: 1100px) {
  .override-row__grid { grid-template-columns: 1fr; }
}
/* Default browser <input>/<textarea> don't fill their grid cell — the
   intrinsic content width (~20-30 chars) wins unless we ask. Override
   rows live in a 1fr/1fr grid so each side wants 100% of its column. */
.override-row .input,
.override-row .textarea {
  width: 100%;
  display: block;
  box-sizing: border-box;
}
.override-row .textarea {
  resize: vertical;
  min-height: 96px;
}
/* Canon column is read-only but still selectable so editors can copy
   the seed text and paste it into the override side. We deliberately
   do NOT set `disabled` on these — `readonly` keeps the selection +
   keyboard cursor, `disabled` removes both. Visually muted via bg
   + opacity, not by browser-default greying. */
.override-row .override-canon {
  background: var(--bg-soft);
  border-color: transparent;
  font-family: var(--font-mono, monospace);
  font-size: 0.85em;
  color: var(--text-main);
  opacity: 0.85;
  cursor: text;
}
.override-row .override-canon:focus-visible {
  opacity: 1;
  border-color: color-mix(in srgb, var(--primary) 30%, transparent);
  box-shadow: none;
}
.override-row .override-value {
  font-family: var(--font-mono, monospace);
  font-size: 0.85em;
}
/* Backdrop must match the textarea sibling exactly — otherwise the
   syntax-highlighted layer wraps at a different column count, scrollHeight
   diverges, and content past the textarea's visible bottom is clipped
   (`overflow: hidden`) with no way to scroll it into view. The default
   `.code-editor__backdrop` uses --fs-sm; the override-row textarea narrows
   to 0.85em via `.override-row .override-value`, so we mirror that here. */
.override-row .code-editor__backdrop {
  font-size: 0.85em;
}
.override-row .override-col {
  min-width: 0;
}
.override-row .override-col__head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: 6px;
  min-height: 24px;
}
.override-row .override-col__head .btn-link {
  font-size: var(--fs-xs);
  white-space: nowrap;
}
.override-row.differs-from-canon .override-value {
  border-color: color-mix(in srgb, var(--primary) 40%, transparent);
}
.override-row.is-dirty {
  border-color: var(--primary);
}
.override-row.is-dirty .override-value {
  border-color: var(--primary);
  background: color-mix(in srgb, var(--primary) 4%, var(--panel));
}
/* When the dirty textarea sits inside `.code-editor`, the rule above
   paints an opaque background over the syntax-highlighted backdrop
   that lives one stacking level below — and since the textarea text
   itself is `color: transparent`, the field reads as empty until the
   editor types or focuses. Neutralise the background for the
   transparent-textarea variant and carry the dirty cue to the
   backdrop's own border (which is what the editor visually sees). */
.override-row.is-dirty .code-editor .content-editor {
  background: transparent;
}
.override-row.is-dirty .code-editor__backdrop {
  border-color: var(--primary);
}
.is-hidden { display: none !important; }

/* Local stat-card warning variant used on the override editor's
   coverage strip (drafts pop amber). 301-ui ships ok / danger /
   primary / neutral; warning fits the editor pattern naturally. */
.stat-card--warning {
  border-left: 3px solid var(--warning, #d28e1c);
}
.stat-card--warning .stat-card__value {
  color: var(--warning, #d28e1c);
}

/* Collapsible HelpBox — info-accent treatment over the 301-ui
   field-details primitive. Native <details> handles toggle; admin.js
   persists per-storageKey state in localStorage. Server-renders open;
   no-JS / no-storage path keeps it open. */
.helpbox {
  border-left: 3px solid var(--info, var(--primary));
  background: color-mix(in srgb, var(--info, var(--primary)) 4%, var(--panel));
  padding: var(--space-2) var(--space-3);
  margin-bottom: var(--space-4);
}
.helpbox__summary {
  gap: var(--space-2);
  padding: var(--space-1) 0;
}
.helpbox__icon {
  flex-shrink: 0;
  color: var(--info, var(--primary));
}
.helpbox__title {
  font-weight: var(--fw-medium, 500);
  flex: 1;
}
.helpbox__body {
  padding-left: calc(var(--space-2) + 1.25em);
}
.helpbox__body > *:first-child { margin-top: 0; }
.helpbox__body > *:last-child { margin-bottom: 0; }
.helpbox:not([open]) {
  background: transparent;
  border-left-color: var(--border-subtle);
}
.helpbox:not([open]) .helpbox__icon,
.helpbox:not([open]) .helpbox__title {
  color: var(--text-muted);
}

/* Dashboard section grouping — small uppercase subtitle that
   groups related card-grid blocks (Content / Configuration /
   Inspect & ops) without adding heavy chrome. */
.dashboard-section__title {
  margin: 0 0 var(--space-2);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-muted);
  font-size: var(--font-xs, 0.75rem);
  font-weight: var(--fw-medium, 500);
}

/* Zero-value stat-card — dim the chrome so populated/warning siblings
   draw the eye first. Used on the override-editor coverage strip. */
.stat-card.is-empty .stat-card__value,
.stat-card.is-empty .stat-card__label {
  color: var(--text-muted);
  opacity: 0.75;
}

/* Sites listing card collapse — each tenant card is a <details> the
   editor can fold so the page doesn't run 4 viewports per site. The
   summary keeps every header action live (anchors and selects handle
   their own clicks before the toggle fires). admin.js shares the
   helpbox handler for state persistence and force-opens via the URL
   hash if a #site-<id> deep link points at this card. */
.sites-listing__item > .sites-listing__summary {
  list-style: none;
  cursor: pointer;
  padding: 0;
}
.sites-listing__item > .sites-listing__summary::-webkit-details-marker {
  display: none;
}
.sites-listing__chevron {
  transition: transform 200ms ease;
  flex-shrink: 0;
  color: var(--text-muted);
}
.sites-listing__item[open] > .sites-listing__summary .sites-listing__chevron {
  transform: rotate(180deg);
}

/* UI-strings group divider — small uppercase subtitle row that breaks
   up the long flat key list into camelCase prefix families
   (banner / cta / footer / …). Keeps the existing filter + pagination
   handler simple: divider rows have no data-key, so the filter hides
   them automatically when a query is active. */
.strings-group-divider td {
  background: var(--bg-soft);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-size: var(--font-xs, 0.75rem);
  font-weight: var(--fw-medium, 500);
  color: var(--text-muted);
  padding-top: var(--space-3);
  padding-bottom: var(--space-1);
  border-bottom: 1px solid var(--border-subtle);
}

/* =====================================================================
   Phase 12-F deploy runner drawer.
   ===================================================================== */

.deploy-step {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--border-subtle);
  border-radius: var(--r-field);
  background: var(--bg-soft);
}

.deploy-step__icon {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.5rem;
  height: 1.5rem;
  color: var(--text-muted);
}

.deploy-step[data-status="running"] .deploy-step__icon { color: var(--text-warning, #d4a017); animation: deploy-spin 1.2s linear infinite; }
.deploy-step[data-status="done"]    .deploy-step__icon { color: var(--text-success, #4ade80); }
.deploy-step[data-status="failed"]  .deploy-step__icon { color: var(--text-warning, #ef4444); }
.deploy-step[data-status="skipped"] .deploy-step__icon,
.deploy-step[data-status="skipped"] .deploy-step__label,
.deploy-step[data-status="skipped"] .deploy-step__summary { opacity: 0.45; }

@keyframes deploy-spin {
  to { transform: rotate(360deg); }
}

/* EP-2A — operator-facing progress block. Sits above the step
   stack while the export render phase is active; hidden via the
   `hidden` attribute by deploy-runner.js renderProgress when
   phase !== "render" or progress is absent. */
.deploy-progress {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--border-subtle);
  border-radius: var(--r-field);
  background: var(--bg-soft);
}
.deploy-progress__text {
  font-variant-numeric: tabular-nums;
}
.deploy-progress__stale {
  color: var(--text-warning, #d4a017);
}

.deploy-step__main {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-width: 0;
}

.deploy-step__label {
  font-weight: var(--fw-medium, 500);
}

.deploy-step__summary {
  font-size: var(--fs-xs);
  color: var(--text-muted);
}

.deploy-step__status {
  flex: 0 0 auto;
  white-space: nowrap;
}

.deploy-error {
  border: 1px solid var(--border-subtle);
  border-left: 3px solid var(--text-warning, #ef4444);
  background: var(--bg-soft);
  padding: var(--space-3);
  border-radius: var(--r-field);
}

.deploy-error__title {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-weight: var(--fw-medium, 500);
  margin-bottom: var(--space-1);
}

.deploy-error__detail {
  color: var(--text-muted);
  margin-bottom: var(--space-2);
  word-wrap: break-word;
}

.deploy-error__cli {
  margin-top: var(--space-2);
  padding-top: var(--space-2);
  border-top: 1px solid var(--border-subtle);
}

.deploy-error__cli-text {
  display: block;
  font-family: var(--font-mono, monospace);
  font-size: var(--fs-xs);
  background: var(--bg-base);
  padding: var(--space-2);
  border-radius: var(--r-sm);
  word-break: break-all;
  white-space: pre-wrap;
}

.deploy-warnings__list {
  list-style: none;
  margin: var(--space-2) 0 0;
  padding: 0;
  max-height: 300px;
  overflow-y: auto;
}

.deploy-warnings__item {
  padding: var(--space-1) 0;
  border-bottom: 1px solid var(--border-subtle);
  font-size: var(--fs-sm);
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  flex-wrap: wrap;
}

.deploy-warnings__item:last-child {
  border-bottom: none;
}

/* P1-2: container subprocess log tails */
.deploy-logs {
  border: 1px solid var(--border-subtle);
  border-radius: var(--r-field);
  padding: var(--space-2) var(--space-3);
  background: var(--bg-soft);
}

.deploy-logs__phase + .deploy-logs__phase {
  margin-top: var(--space-2);
  padding-top: var(--space-2);
  border-top: 1px dashed var(--border-subtle);
}

.deploy-logs__stream {
  font-family: var(--font-mono, monospace);
  font-size: var(--fs-xs);
  background: var(--bg-default);
  border: 1px solid var(--border-subtle);
  border-radius: var(--r-field);
  padding: var(--space-2);
  margin: var(--space-1) 0 0;
  max-height: 240px;
  overflow: auto;
  white-space: pre-wrap;
  word-break: break-word;
}

.deploy-logs__stream--err {
  border-left: 3px solid var(--text-warning, #ef4444);
}

/* ========== Deploy drawer: domains panel (Slice 1 of
   docs/client-domain-attach-scope.md) ========== */
.deploy-domains {
  margin-top: var(--space-3);
  padding-top: var(--space-3);
  border-top: 1px solid var(--border-subtle);
}
.deploy-domains__header {
  margin-bottom: var(--space-2);
}
.deploy-domains__title {
  margin: 0;
  font-size: var(--fs-md);
  font-weight: var(--fw-semibold);
}
.deploy-domains__list {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  margin-bottom: var(--space-3);
}
.deploy-domains__row {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-2);
  background: var(--bg-elevated, var(--bg-default));
  border: 1px solid var(--border-subtle);
  border-radius: var(--r-field);
}
.deploy-domains__head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
}
.deploy-domains__hint {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-2);
  margin-top: var(--space-1);
  background: var(--bg-default);
  border: 1px dashed var(--border-subtle);
  border-radius: var(--r-field);
}
.deploy-domains__hint-row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
}
.deploy-domains__cname {
  font-family: var(--font-mono, monospace);
  font-size: var(--fs-xs);
  background: transparent;
  padding: 0;
  word-break: break-all;
}
.deploy-domains__cta a {
  color: var(--primary, #4ea1ff);
  text-decoration: underline;
}
.deploy-domains__host {
  font-family: var(--font-mono, monospace);
  font-size: var(--fs-sm);
  flex: 1;
  min-width: 0;
  word-break: break-all;
}
.deploy-domains__orphan {
  color: var(--text-warning, #ef4444);
}
.deploy-domains__attach {
  padding-top: var(--space-2);
  border-top: 1px dashed var(--border-subtle);
}
.deploy-domains__err {
  color: var(--text-warning, #ef4444);
}
.deploy-domains__ok {
  color: var(--text-success, #22c55e);
}

/* ========== Composition: assemblers card ========== */
.assembler-card {
  margin-bottom: var(--space-3);
}

.assembler-card .card__header h4 code {
  font-size: .9em;
  color: var(--text-muted);
}

.assembler-notes {
  font-size: var(--fs-xs);
  color: var(--text-muted);
  margin: 0;
  padding-left: var(--space-3);
}

.assembler-notes li {
  margin: 2px 0;
}

.assembler-divergence {
  background: rgba(220, 38, 38, .08);
  border: 1px solid rgba(220, 38, 38, .3);
  border-radius: var(--r-field);
  padding: var(--space-2) var(--space-3);
  font-size: var(--fs-xs);
  color: #fecaca;
}

.assembler-divergence strong {
  color: #fca5a5;
}

.assembler-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--fs-sm);
}

.assembler-table td {
  padding: var(--space-1) var(--space-2);
  border-bottom: 1px solid var(--border-subtle);
  vertical-align: top;
}

.assembler-table tr:last-child td {
  border-bottom: none;
}

.assembler-table td:first-child {
  width: 220px;
  min-width: 220px;
  font-family: var(--font-mono, monospace);
  font-size: var(--fs-xs);
}

.assembler-table td:first-child code {
  word-break: keep-all;
  white-space: nowrap;
}

.assembler-table td:nth-child(2) {
  width: 60px;
  white-space: nowrap;
}

.assembler-var.is-empty code {
  color: var(--text-muted);
}

.assembler-table td:nth-child(3) code {
  word-break: break-word;
  overflow-wrap: anywhere;
  white-space: pre-wrap;
  display: block;
  max-width: 100%;
}

.badge--xs {
  font-size: 10px;
  padding: 1px 6px;
  text-transform: uppercase;
  letter-spacing: .04em;
}