﻿/* ============================================================================
   WinWrx admin design system.

   Scoped under .wx so it can be adopted one page at a time without disturbing
   the Bootstrap/Blazorise/Syncfusion styling still in place elsewhere. Wrap a
   converted page (or the shell) in .wx and everything inside picks this up;
   nothing outside it changes.

   Light-first on purpose: the partner site is the dark consumer surface, this
   is the daylight tool people sit in for hours.
   ========================================================================== */

.wx {
  /* The main area is a real grey, not near-white: white cards lift off it, and
     anything dark sitting directly on it - the phone preview - has something to
     contrast against. The rail goes darker to keep the two clearly apart. */
  --ground:    #E8EDF2;
  --surface:   #FFFFFF;
  --surface-2: #EDF1F5;
  --rail:      #FFFFFF;
  --rail-2:    #E7ECF3;
  --rail-ink:  #55637A;
  /* Emphasis inside the rail - hover text, the partner name. Was hard-coded
     white, which is invisible once the rail itself goes light. */
  --rail-ink-strong: #0E1420;
  --line:      #DEE4EC;
  --line-soft: #EAEEF3;
  --ink:       #0E1420;
  --muted:     #5B6879;
  --muted-dim: #8794A6;

  /* The platform's own accent, not a partner's. Teal keeps red/amber/green
     free to mean status and nothing else. */
  --accent:      #0F766E;
  --accent-ink:  #FFFFFF;
  --accent-soft: #DCF1EF;

  /* Semantic only. Never used decoratively. */
  --good: #157F4B;  --good-soft: #DCF3E7;
  --warn: #B45309;  --warn-soft: #FBEBD7;
  --crit: #B42318;  --crit-soft: #FBE3E1;
  --info: #1D6FB8;  --info-soft: #DDEBF8;

  --radius: 6px;

  --f-ui:   "IBM Plex Sans", system-ui, -apple-system, "Segoe UI", sans-serif;
  --f-cond: "IBM Plex Sans Condensed", "IBM Plex Sans", system-ui, sans-serif;
  --f-mono: "IBM Plex Mono", ui-monospace, Consolas, monospace;

  font-family: var(--f-ui);
  font-size: 14px;
  line-height: 1.5;
  color: var(--ink);
}

/* Theme resolves in three states: no attribute follows the OS, and an explicit
   choice on <html> overrides it in either direction. The attribute lives on
   <html> rather than on .wx so the inline script in the layout head can apply a
   saved choice before anything paints. */
@media (prefers-color-scheme: dark) {
  html:not([data-theme="light"]) .wx {
    /* Swapped: the rail is now the lighter near-black and the main area is the
       darker one, so the content sits in the deeper field and the nav lifts off
       it rather than the other way round. */
    /* Inverted depth: the main content area is the lighter field and the chrome
       sitting on it - rail, top bar, sub nav, cards - is darker. The usual dark
       convention is the reverse, panels lighter than the page, which is what this
       replaces. */
    --ground:    #1B222C;
    --surface:   #10151C;
    --surface-2: #0C1116;
    --rail:      #080B10;
    --rail-2:    #141B25;
    --rail-ink:  #AEBBCC;
    --rail-ink-strong: #FFFFFF;
    --line:      #2C3542;
    --line-soft: #232B36;
    --ink:       #E8EDF4;
    --muted:     #93A0B2;
    --muted-dim: #6B7889;

    --accent:      #2DD4BF;
    --accent-ink:  #06231F;
    --accent-soft: #10322F;

    --good: #3ECF8E;  --good-soft: #0E2E22;
    --warn: #E9A23B;  --warn-soft: #33260F;
    --crit: #F2665A;  --crit-soft: #351A18;
    --info: #58A6FF;  --info-soft: #10263C;
  }
}

html[data-theme="dark"] .wx {
  --ground:    #1B222C;
  --surface:   #10151C;
  --surface-2: #0C1116;
  --rail:      #080B10;
  --rail-2:    #141B25;
  --rail-ink:  #AEBBCC;
  --rail-ink-strong: #FFFFFF;
  --line:      #2C3542;
  --line-soft: #232B36;
  --ink:       #E8EDF4;
  --muted:     #93A0B2;
  --muted-dim: #6B7889;

  --accent:      #2DD4BF;
  --accent-ink:  #06231F;
  --accent-soft: #10322F;

  --good: #3ECF8E;  --good-soft: #0E2E22;
  --warn: #E9A23B;  --warn-soft: #33260F;
  --crit: #F2665A;  --crit-soft: #351A18;
  --info: #58A6FF;  --info-soft: #10263C;
}

.wx *, .wx *::before, .wx *::after { box-sizing: border-box; }

.wx h1, .wx h2, .wx h3 { margin: 0; text-wrap: balance; letter-spacing: -0.01em; }
.wx :focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 3px; }

/* Digits that line up in columns. Used on every numeric cell. */
.wx-num { font-family: var(--f-mono); font-variant-numeric: tabular-nums; }

.wx-lab {
  font-family: var(--f-mono);
  font-size: 10.5px;
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted-dim);
}

.wx-muted { color: var(--muted); }

/* ------------------------------- page ----------------------------------- */

.wx-page { display: grid; gap: 16px; }

/* Grid items default to min-width:auto, so a wide nowrap table sets the card's
   minimum and drags the column past the viewport - the page scrolls sideways
   instead of the table scrolling in its own box. */
.wx-page > *, .wx-split > *, .wx-form-split > * { min-width: 0; }

/* The same trap one level down. A column inside the split is itself a grid, and
   its cards are grid items with min-width:auto - so the widest card sets the
   column's width and every sibling stretches to match, overflowing the track and
   sliding under the aside. Cards never need to exceed their column: anything
   genuinely wide inside one scrolls in its own box. */
.wx-split .wx-card, .wx-form-split .wx-card { min-width: 0; }

.wx-page-head {
  /* Flush left, so the title lines up with the visible left edge of the nav
     below it and of every card on the page - those are boxes with a border, and
     it is that border the eye lines up against, not the text inside it.
     Right-hand side is inset by the nav's own border and padding, so the trail
     stops level with the inside of the bar rather than running past it. */
  /* 5px in: the nav's border plus its padding, which is where the active tab's
     underline starts. That underline is the line the eye picks up, not the
     nav's outer border. Same on the right, so the trail stops level with the
     inside of the bar. */
  padding: 0 5px;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  /* No wrapping. A long post name used to shove the trail onto a line of its
     own; now the title truncates and the trail keeps its place. */
  flex-wrap: nowrap;
}

/* Every link in the chain needs to be allowed to shrink, or the h1's ellipsis
   never engages - a flex item's minimum is its content by default, so one long
   unbroken title holds the whole row open. */
.wx-page-head > .lead { min-width: 0; flex: 1 1 auto; display: grid; gap: 3px; }

/* The trail on the right lines up with the title rather than with the crumb
   under it, because the flex baseline is taken from the first line of the
   block. */
.wx-page-head .wx-section-head { flex: 0 0 auto; }
/* Title and subtitle share a line. Stacked, a breadcrumb plus title plus
   subtitle burned three rows of height before any content. */
.wx-page-head .titles {
  display: flex;
  align-items: baseline;
  gap: 10px;
  /* Nowrap so a long title is cut rather than the count under it being pushed
     to a line of its own. */
  flex-wrap: nowrap;
  min-width: 0;
}
/* The count never truncates - it is short, and it is the half that would be
   missed. */
.wx-page-head .titles p { flex: 0 0 auto; }
.wx-page-head h1 {
  font-size: 20px;
  font-weight: 700;
  min-width: 0;
  /* Belt to the character cap's braces: a name short enough to survive that can
     still be too long for a narrow window. */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.wx-page-head p { margin: 0; color: var(--muted); font-size: 13px; }

.wx-split { display: grid; gap: 18px; }
@media (min-width: 1080px) { .wx-split { grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr); } }

.wx-form-split { display: grid; gap: 18px; }
@media (min-width: 1080px) { .wx-form-split { grid-template-columns: minmax(0, 1fr) 320px; align-items: start; } }

/* Forms fill the page like everything else. The cap that used to be here made
   them the one thing on screen that stopped short, which read as a mistake.
   Past 1500px the aside takes the extra room instead of the form column, so
   filling the width buys a wider gutter rather than a 750px text input with
   its label a screen away from its value. */
@media (min-width: 1500px) {
  .wx-form-split { grid-template-columns: minmax(0, 1fr) 400px; }
}
@media (min-width: 1800px) {
  .wx-form-split { grid-template-columns: minmax(0, 1fr) 480px; }
}

/* ------------------------------- card ----------------------------------- */

.wx-card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}

.wx-card-head {
  padding: 13px 16px;
  border-bottom: 1px solid var(--line-soft);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}
.wx-card-head h2 { font-size: 14px; font-weight: 600; }

.wx-card-body { padding: 16px; display: grid; gap: 15px; }

.wx-card.is-danger { border-color: color-mix(in srgb, var(--crit) 40%, var(--line)); }

/* A house ad as the slot would show it - see Admin → Ads. */
.wx-ad-preview { display: grid; gap: 6px; justify-items: center; text-align: center; padding: 14px 10px; border-radius: 10px; background: var(--surface-2); }
.wx-ad-preview img { max-width: 60%; max-height: 6rem; object-fit: contain; }
.wx-ad-preview b { font-size: 1.05rem; }
.wx-ad-preview span { font-size: 0.9rem; color: var(--muted); max-width: 32ch; }
.wx-ad-preview em { font-size: 0.8rem; color: var(--accent); font-style: normal; word-break: break-all; }
.wx-ad-preview small { color: var(--muted); font-variant-numeric: tabular-nums; }
.wx-card.is-danger .wx-card-head h2 { color: var(--crit); }

/* ------------------------------ buttons --------------------------------- */

.wx-btn {
  appearance: none;
  font-family: var(--f-ui);
  font-size: 13px;
  font-weight: 600;
  padding: 8px 14px;
  border-radius: var(--radius);
  border: 1px solid transparent;
  background: var(--accent);
  color: var(--accent-ink);
  cursor: pointer;
  white-space: nowrap;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  text-decoration: none;
}
.wx-btn:hover { filter: brightness(1.06); }
.wx-btn:disabled { opacity: .5; cursor: default; filter: none; }

.wx-btn.is-ghost { background: var(--surface); border-color: var(--line); color: var(--ink); }
.wx-btn.is-ghost:hover { background: var(--surface-2); filter: none; }

.wx-btn.is-danger { background: transparent; border-color: var(--crit); color: var(--crit); }
.wx-btn.is-danger:hover { background: var(--crit-soft); filter: none; }

.wx-btn.is-sm { padding: 5px 10px; font-size: 12px; }

.wx-iconbtn {
  width: 34px; height: 34px;
  display: grid; place-items: center;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--surface); color: var(--muted);
  cursor: pointer; font-size: 14px;
}
.wx-iconbtn:hover { color: var(--ink); }

/* The other two buttons in the top bar hold text glyphs, which take the
   button's colour for free. A Font Awesome <i> does not: something in
   Bootstrap or Syncfusion's bundle gets there first, so the phone stayed dark
   while its neighbours followed the theme. Set explicitly for the same reason
   .wx-nav .glyph sets it - leaving an icon's colour to the cascade is how the
   rail glyphs ended up near-black and invisible. */
.wx-iconbtn .fas { color: var(--muted); }
.wx-iconbtn:hover .fas { color: var(--ink); }

/* ------------------------------- chips ---------------------------------- */

.wx-chip {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 11.5px; font-weight: 600;
  padding: 3px 8px; border-radius: 99px;
  white-space: nowrap;
  color: var(--muted); background: var(--surface-2);
}
.wx-chip.is-ok   { color: var(--good); background: var(--good-soft); }
.wx-chip.is-warn { color: var(--warn); background: var(--warn-soft); }
.wx-chip.is-crit { color: var(--crit); background: var(--crit-soft); }
.wx-chip.is-info { color: var(--info); background: var(--info-soft); }

/* ------------------------------ toolbar --------------------------------- */

.wx-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  padding: 11px 14px;
  border-bottom: 1px solid var(--line-soft);
}

.wx-filter {
  appearance: none;
  font-family: var(--f-ui);
  font-size: 12.5px;
  padding: 6px 10px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--ink);
  cursor: pointer;
}

.wx-search {
  flex: 1;
  min-width: 170px;
  display: flex; align-items: center; gap: 7px;
  padding: 6px 10px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--muted-dim);
  font-size: 12.5px;
}
.wx-search input {
  border: 0; background: none; outline: none; width: 100%;
  font-family: var(--f-ui); font-size: 12.5px; color: var(--ink);
}

/* ------------------------------- table ---------------------------------- */

.wx-tbl-scroll { overflow-x: auto; min-width: 0; }

.wx-tbl { width: 100%; border-collapse: collapse; font-size: 13px; }

.wx-tbl th {
  text-align: left;
  font-family: var(--f-mono);
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted-dim);
  padding: 9px 14px;
  border-bottom: 1px solid var(--line);
  white-space: nowrap;
  background: var(--surface);
}

.wx-tbl td { padding: 10px 14px; border-bottom: 1px solid var(--line-soft); white-space: nowrap; }
.wx-tbl tbody tr:last-child td { border-bottom: 0; }
.wx-tbl tbody tr:hover { background: var(--surface-2); }

/* The lead cell takes the leftover width and truncates rather than letting a
   long name or description push the table sideways. width:100% with max-width:0
   is the auto-layout idiom for "take what is left"; the ellipsis needs the
   overflow on a block child, not on the cell. */
/* max-width:0 belongs on the body cell only. On the header it collapses the
   column to nothing and the heading overlaps its neighbour - "Customer" printed
   across "Status". The header still needs width:100% so the column takes what
   is left. */
.wx-tbl th.is-clip { width: 100%; }
.wx-tbl td.is-clip { width: 100%; max-width: 0; }

.wx-tbl td.is-clip > * {
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.wx-tbl .r { text-align: right; }
.wx-tbl td .sub { display: block; color: var(--muted); font-size: 12px; }

.wx-tbl tr.is-clickable { cursor: pointer; }

/* ---- inline editing ---- */

/* The row being edited is lifted off the others - it is the only place on the
   page that holds unsaved work, so it should not read as just another row. */
.wx-tbl tr.is-editing { background: var(--surface-2); }
.wx-tbl tr.is-editing:hover { background: var(--surface-2); }
.wx-tbl tr.is-editing td {
  padding: 7px 10px;
  box-shadow: inset 0 1px 0 var(--accent), inset 0 -1px 0 var(--accent);
}
.wx-tbl tr.is-editing td:first-child { box-shadow: inset 1px 1px 0 var(--accent), inset 0 -1px 0 var(--accent); }
.wx-tbl tr.is-editing td:last-child  { box-shadow: inset -1px 1px 0 var(--accent), inset 0 -1px 0 var(--accent); }

/* Inputs sit inside a table cell, so they lose the block field's own spacing and
   size down to the row rather than the row growing to fit them. */
.wx-tbl .wx-in { padding: 5px 8px; font-size: 13px; min-width: 90px; }
.wx-tbl .wx-in.is-num { min-width: 70px; }

.wx-rowact { display: flex; gap: 6px; justify-content: flex-end; }

/* Holds its width whether the row shows Edit/Delete or Save/Cancel, so the data
   columns do not shift sideways when a row opens. */
.wx-tbl th.wx-rowact-head { width: 1%; }
.wx-tbl td:has(.wx-rowact) { width: 1%; white-space: nowrap; }

/* Sortable header: a button so it is reachable by keyboard, styled as a header. */
.wx-sort {
  appearance: none; background: none; border: 0; padding: 0;
  font: inherit; color: inherit; letter-spacing: inherit; text-transform: inherit;
  cursor: pointer; display: inline-flex; align-items: center; gap: 5px;
}
.wx-sort:hover { color: var(--ink); }
.wx-sort .arrow { opacity: 0; font-size: 9px; }
.wx-sort[aria-sort="ascending"] .arrow,
.wx-sort[aria-sort="descending"] .arrow { opacity: 1; color: var(--accent); }
.wx-sort[aria-sort="descending"] .arrow { transform: rotate(180deg); }

.wx-tbl-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 11px 14px;
  border-top: 1px solid var(--line-soft);
  font-size: 12.5px;
  color: var(--muted);
  flex-wrap: wrap;
}

.wx-pager { display: flex; gap: 4px; }
.wx-pager button {
  min-width: 30px; height: 28px;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--surface); color: var(--muted);
  font-family: var(--f-mono); font-size: 12px; cursor: pointer;
}
.wx-pager button[aria-current="true"] { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); font-weight: 600; }
.wx-pager button:disabled { opacity: .4; cursor: default; }

.wx-empty { padding: 30px 16px; text-align: center; color: var(--muted); font-size: 13px; }

/* ------------------------------- fields --------------------------------- */

.wx-frow { display: grid; gap: 15px; }
@media (min-width: 640px) { .wx-frow.is-two { grid-template-columns: 1fr 1fr; } }
@media (min-width: 640px) { .wx-frow.is-three { grid-template-columns: repeat(3, minmax(0, 1fr)); } }

/* align-content:start matters in a two-up row: one field having help text makes
   its partner's cell taller, and stretched rows would push that partner's input
   down out of line with it. */
.wx-field { display: grid; gap: 6px; min-width: 0; align-content: start; }
.wx-field > label { font-size: 12.5px; font-weight: 600; }
.wx .wx-help,
.wx-field .wx-help { font-size: 12px; color: var(--muted); line-height: 1.5; }

.wx-in {
  width: 100%;
  font-family: var(--f-ui);
  font-size: 13.5px;
  padding: 9px 11px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--ink);
}
.wx-in:focus { outline: 2px solid var(--accent); outline-offset: -1px; border-color: var(--accent); }
textarea.wx-in { resize: vertical; min-height: 92px; line-height: 1.5; }
.wx-in.is-num { font-family: var(--f-mono); font-variant-numeric: tabular-nums; }

/* A colour input renders its own swatch, so it only needs the height and border
   of the other fields - the padding a text input wants leaves a thin sliver. */
.wx-in.is-color { padding: 3px; height: 36px; cursor: pointer; }

/* Colour never carries the error alone - the message does the work. */
.wx-field.is-bad .wx-in { border-color: var(--crit); }
.wx-field.is-bad .wx-help { color: var(--crit); font-weight: 500; }
.wx .wx-help.is-bad { color: var(--crit); font-weight: 500; }

/* Image picker. The file input sits inside its own button label, so it is hidden
   rather than removed - display:none on a file input still opens the picker when
   its label is clicked, but keyboard focus needs it to exist. */
.wx-imgpick { display: grid; gap: 6px; min-width: 0; align-content: start; }
.wx-imgpick > label { font-size: 12.5px; font-weight: 600; }
.wx-imgpick-input { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
.wx-imgpick-body { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.wx-imgpick-acts { display: flex; gap: 8px; flex-wrap: wrap; }
.wx-imgpick-preview {
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 64px;
  height: 56px;
  padding: 4px 8px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface-2);
}
.wx-imgpick-preview img { max-height: 48px; max-width: 160px; display: block; }

.wx-switch { display: inline-flex; align-items: center; gap: 10px; cursor: pointer; }
.wx-switch input { position: absolute; opacity: 0; pointer-events: none; }
.wx-track {
  width: 38px; height: 22px; border-radius: 99px;
  background: var(--line); position: relative; flex: 0 0 auto;
  transition: background .15s ease;
}
.wx-track::after {
  content: ""; position: absolute; top: 3px; left: 3px;
  width: 16px; height: 16px; border-radius: 50%;
  background: var(--surface); transition: transform .15s ease;
  box-shadow: 0 1px 2px rgba(0,0,0,.25);
}
.wx-switch input:checked + .wx-track { background: var(--accent); }
.wx-switch input:checked + .wx-track::after { transform: translateX(16px); }
.wx-switch input:focus-visible + .wx-track { outline: 2px solid var(--accent); outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) { .wx-track, .wx-track::after { transition: none; } }


.wx-meta-row {
  display: flex; justify-content: space-between; gap: 12px;
  font-size: 12.5px; padding: 7px 0;
  border-bottom: 1px solid var(--line-soft);
}
.wx-meta-row:last-child { border-bottom: 0; }
.wx-meta-row span { color: var(--muted); }
.wx-meta-row b { font-weight: 600; }

/* ============================== app shell ================================ */

/* The shell is the one .wx that wraps everything, so converted and unconverted
   pages both sit inside the new chrome. Unconverted page content keeps its own
   Bootstrap styling - .wx only sets tokens plus a base font, and every component
   rule is class-scoped, so nothing here restyles markup that has not opted in. */

.wx-shell {
  display: grid;
  grid-template-columns: 1fr;
  min-height: 100vh;
  background: var(--ground);
}
@media (min-width: 1000px) { .wx-shell { grid-template-columns: 232px 1fr; } }

/* ---- left rail: the main nav ---- */

.wx-rail {
  background: var(--rail);
  color: var(--rail-ink);
  /* On a light theme the rail and the content field are both pale, so the edge
     has to be drawn rather than relying on the colour difference. */
  border-right: 1px solid var(--line);
  display: none;
  flex-direction: column;
  position: sticky;
  top: 0;
  height: 100vh;
  overflow-y: auto;
}
@media (min-width: 1000px) { .wx-rail { display: flex; } }

.wx-rail[data-open="true"] {
  display: flex;
  position: fixed;
  inset: 0 auto 0 0;
  width: 268px;
  z-index: 70;
}

.wx-rail-head {
  padding: 10px 18px 8px;
  display: flex;
  align-items: center;
  gap: 10px;
  border-bottom: 1px solid var(--rail-2);
}
.wx-rail-head img { max-height: 26px; width: auto; display: block; }

/* Two marks, one shown.
   
   Qualified with img on purpose: ".wx-rail-mark" alone loses to ".wx-rail-head img"
   above, which sets display:block - so the gear showed alongside the wordmark instead
   of waiting for the rail to collapse. Every rule below matches the same shape or
   stronger, so the cascade cannot go back to being a coin toss. */
.wx-rail-head img.wx-rail-mark { display: none; }

/* As wide as the rail allows. The lockup is 3:1, so 232px of rail less its padding
   makes the width the binding constraint and the height follows. */
.wx-rail-head img.wx-rail-logo { max-height: 48px; max-width: 100%; }

/* Centred: it is the only thing on this row, and left-aligning it against a rail that
   is otherwise symmetrical just looked like a mistake. */
.wx-rail-head { justify-content: center; }

.wx-rail-context {
  margin: 14px 12px;
  padding: 10px 12px;
  border: 1px solid var(--rail-2);
  border-radius: var(--radius);
  background: var(--rail-2);
  color: var(--rail-ink-strong);
  font-size: 13px;
  line-height: 1.35;
}
.wx-rail-context small { display: block; color: var(--rail-ink); font-size: 11px; }

.wx-nav { padding: 4px 12px 24px; display: grid; gap: 2px; }

.wx-nav-group { margin-top: 14px; padding: 0 8px 6px; }

.wx-nav a {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 10px;
  border-radius: var(--radius);
  text-decoration: none;
  color: var(--rail-ink);
  font-size: 13.5px;
  font-weight: 500;
}
.wx-nav a:hover { background: var(--rail-2); color: var(--rail-ink-strong); }
.wx-nav a.active { background: var(--accent); color: var(--accent-ink); font-weight: 600; }

/* Work waiting behind a rail item. Pushed to the far end so it sits in the same place
   on every row that has one, and in the critical tone because it is a queue of things
   the audience objected to, not a notification count. */
.wx-nav-count {
  margin-left: auto;
  min-width: 20px;
  padding: 1px 6px;
  border-radius: 999px;
  background: var(--crit, #c0392b);
  color: #fff;
  font-size: 11px;
  font-weight: 700;
  line-height: 18px;
  text-align: center;
}

.wx-nav a.active .wx-nav-count { background: var(--accent-ink); color: var(--accent); }

/* Icon colour is set explicitly rather than inherited. The old menu hung it on a
   .white-large-icon class that had no CSS behind it anywhere, so the glyphs took
   whatever colour the cascade happened to give them - which is how they ended up
   near-black and invisible. */
/* A section on the roadmap with no page behind it yet. Takes the same shape as
   a real item so the rail still reads as a map, but is not a destination -
   following one 404s, and the 404 takes the shell with it. */
.wx-nav .is-soon,
.wx-subnav .is-soon {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 10px;
  font-size: 13.5px;
  font-weight: 500;
  color: var(--rail-ink);
  opacity: .45;
  cursor: default;
}
.wx-subnav .is-soon { display: inline-flex; gap: 7px; padding: 8px 12px; font-size: 13px; color: var(--muted); }

.wx-soon {
  font-family: var(--f-mono);
  font-size: 9.5px;
  letter-spacing: .1em;
  text-transform: uppercase;
  border: 1px solid currentColor;
  border-radius: 3px;
  padding: 0 4px;
  margin-left: auto;
}
.wx-subnav .wx-soon { margin-left: 2px; }

.wx-nav .glyph { width: 16px; text-align: center; flex: 0 0 auto; color: var(--rail-ink); opacity: .85; }
.wx-nav a:hover .glyph { color: var(--rail-ink-strong); opacity: 1; }
.wx-nav a.active .glyph { color: var(--accent-ink); opacity: 1; }

/* ---- top bar ---- */

.wx-main { min-width: 0; display: flex; flex-direction: column; }

.wx-topbar {
  position: sticky;
  top: 0;
  z-index: 30;
  background: color-mix(in srgb, var(--surface) 92%, transparent);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--line);
  /* Matches .wx-content, so the first icon's left edge lands on the same
     vertical line as the sub nav below it and every card on the page. */
  padding: 0 14px;
  min-height: 56px;
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}

@media (min-width: 700px) { .wx-topbar { padding: 0 20px; } }

.wx-topbar .tools { display: flex; align-items: center; gap: 8px; }

.wx-topbar .who { margin-left: auto; display: flex; align-items: center; gap: 10px; font-size: 12.5px; color: var(--muted); }
.wx-topbar .who a { color: var(--muted); text-decoration: none; }
.wx-topbar .who a:hover { color: var(--ink); text-decoration: underline; }

.wx-menu-btn { display: grid; }
@media (min-width: 1000px) { .wx-menu-btn { display: none; } }

.wx-content { padding: 18px 14px 60px; min-width: 0; }
@media (min-width: 700px) { .wx-content { padding: 22px 20px 70px; } }

.wx-scrim { position: fixed; inset: 0; z-index: 60; background: rgba(4, 8, 14, .5); display: none; }
.wx-scrim[data-open="true"] { display: block; }
@media (min-width: 1000px) { .wx-scrim { display: none !important; } }

/* ---- sub nav: the section's own pages ---- */

/* A horizontal strip rather than nesting inside the rail. Store alone has seven
   entries in two visual groups; folding those into the rail would make it long
   and would bury which section you are in. */
/* A rounded strip rather than a full-bleed rule, so it reads as the same kind of
   surface as the cards below it. */
.wx-subnav {
  display: flex;
  gap: 2px;
  align-items: center;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 0 4px;
  overflow-x: auto;
  scrollbar-width: none;
  background: var(--surface);
}
.wx-subnav::-webkit-scrollbar { display: none; }

.wx-subnav a {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  margin: 3px 0;
  padding: 8px 12px;
  border-bottom: 2px solid transparent;
  text-decoration: none;
  color: var(--muted);
  font-size: 13px;
  font-weight: 500;
  white-space: nowrap;
}
.wx-subnav a:hover { color: var(--ink); }
.wx-subnav a.active { color: var(--ink); border-bottom-color: var(--accent); font-weight: 600; }

.wx-subnav .glyph { color: var(--muted-dim); width: auto; }
.wx-subnav a:hover .glyph { color: var(--muted); }
.wx-subnav a.active .glyph { color: var(--accent); }

/* Separates the groups the old menu expressed with left padding. */
.wx-subnav .sep { width: 1px; align-self: stretch; margin: 8px 8px; background: var(--line); flex: 0 0 auto; }

/* A plain button-row tab strip - same visual language as .wx-subnav above, but keyed
   off local component state rather than the route, since these tabs have no URL of
   their own (Automation's Store/Giveaways/Sponsors/Games panels). */
.wx-tabs {
  display: flex;
  gap: 2px;
  align-items: center;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 0 4px;
  overflow-x: auto;
  scrollbar-width: none;
  background: var(--surface);
}
.wx-tabs::-webkit-scrollbar { display: none; }

.wx-tab {
  display: inline-flex;
  align-items: center;
  margin: 3px 0;
  padding: 8px 12px;
  border: 0;
  border-bottom: 2px solid transparent;
  background: none;
  cursor: pointer;
  color: var(--muted);
  font-size: 13px;
  font-weight: 500;
  white-space: nowrap;
}
.wx-tab:hover { color: var(--ink); }
.wx-tab.is-active { color: var(--ink); border-bottom-color: var(--accent); font-weight: 600; }

/* A gutter that follows you down a long page. The automation page's field chips and
   preview act on whichever card you are editing, so they have to stay reachable while
   you scroll past six of them - otherwise you are scrolling back up to insert a field
   and back down to see where it went.

   Only once the split is actually two columns: below that the gutter is stacked under
   the content, where sticking it would pin it over what you are reading. Capped to the
   viewport with its own scrollbar, so a tall gutter cannot outgrow the screen. */
@media (min-width: 1080px) {
  .wx-sticky-gutter {
    position: sticky;
    /* Clear of .wx-topbar, which is itself sticky at top:0 and 56px tall. Pinned
       at 12px the gutter slid under it and lost its first card's heading - the
       one naming what the buttons below it do. */
    top: 68px;
    align-self: start;
    max-height: calc(100vh - 80px);
    overflow-y: auto;
    scrollbar-width: thin;
  }
}

/* Cards side by side, for a set of small peers - the automation page's six post
   types.

   No align-items here on purpose: grid stretches by default, so the two cards in a
   row take the height of the taller one and their bottom edges line up. A row of
   cards ending at ragged heights reads as a mistake when they are the same kind of
   thing.

   Two columns, and only once there is room for them to be worth reading. auto-fit
   would technically fit a pair from about 660px of content width, but that is 320px
   a card once the gutter has taken its 320 - narrow enough that the wording fields
   are the thing being squeezed. Below the breakpoint one full-width column beats two
   cramped ones. */
.wx-cardgrid {
  display: grid;
  gap: 18px;
  min-width: 0;
}

@media (min-width: 1280px) {
  .wx-cardgrid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* A customer's photograph, at review size.

   Not the 40px avatar the rest of the product draws: somebody deciding whether a
   picture may be published has to be able to see what is in it. Capped so a tall
   portrait cannot push the gutter's other cards off the screen, and on the checker
   ground so a transparent PNG cannot hide anything against white. */
.wx-photo-review {
  display: grid;
  place-items: center;
  padding: 10px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface-2);
}

.wx-photo-review img {
  max-width: 100%;
  max-height: 240px;
  border-radius: var(--radius);
  display: block;
}

/* ---- an order, read rather than edited ---- */

/* The lines of an order, and the deliveries under them. Not a table: a line is a
   picture, a name with things underneath it, and two numbers - which a table row
   flattens into cells of wildly different weight. A grid gives the picture a fixed
   column and lets the name take whatever is left. */
.wx-lines { display: grid; gap: 2px; min-width: 0; }

.wx-line {
  display: grid;
  grid-template-columns: auto 1fr auto auto;
  align-items: center;
  gap: 12px;
  padding: 10px 0;
  border-bottom: 1px solid var(--line-soft);
  min-width: 0;
}
.wx-line:last-child { border-bottom: 0; }

.wx-line-pic {
  width: 46px; height: 46px;
  border-radius: var(--radius);
  border: 1px solid var(--line);
  background: var(--surface-2);
  overflow: hidden;
}
.wx-line-pic img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* min-width:0 so a long product name ellipsises instead of widening the grid and
   pushing the money off the card. */
.wx-line-body { display: grid; gap: 2px; min-width: 0; }
.wx-line-body b { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.wx-line-body .sub { font-size: 12px; color: var(--muted); }

.wx-line-num { display: grid; gap: 2px; justify-items: end; text-align: right; white-space: nowrap; }
.wx-line-num .sub { font-size: 12px; color: var(--muted); }

/* The money, in two columns with the figures aligned on their right edge. Reading a
   column of amounts is the whole job here, and ragged digits make it a chore. */
.wx-sums { display: grid; gap: 6px; }
.wx-sums > div {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  font-size: 13px;
  color: var(--muted);
}
.wx-sums > div span:last-child { font-variant-numeric: tabular-nums; color: var(--ink); }

/* The line that is actually the answer. */
.wx-sums > div.is-total {
  margin-top: 4px;
  padding-top: 8px;
  border-top: 1px solid var(--line);
  font-weight: 700;
  color: var(--ink);
}

.wx-addr { font-size: 13px; line-height: 1.55; }

/* A line that opens something. Same box as the others - only the cursor and the hover
   say so, because a row of buttons where a summary should be is noise. */
a.wx-line.is-link { text-decoration: none; color: inherit; cursor: pointer; }
a.wx-line.is-link:hover .wx-line-body b { color: var(--accent); }

/* Permanent deletion, set apart from the rest of the card. The one control here with
   no undo should not sit in the same visual breath as Approve and Reject. */
.wx-purge {
  display: grid;
  gap: 8px;
  margin-top: 4px;
  padding: 10px;
  border: 1px solid var(--crit);
  border-radius: var(--radius);
  background: var(--crit-soft);
}

/* Why somebody was reported, under the picture. A count says people objected; these
   say what to, which is the difference between a decision and a number. */
/* ---- the moderation queue ---- */

/* One column of cards, each a whole decision. Not a table: a table row cannot hold a
   quoted paragraph, a photograph and a list of reasons and still be read at a glance. */
/* Cards in a row are the same height: the grid stretches them, the card is a column,
   and the foot is pushed to the bottom so the buttons line up across the row. The
   quote is capped and scrolls, which is what keeps a long comment from setting the
   height for its neighbour. */
.wx-mod-queue { display: grid; gap: 16px; align-items: stretch; }
@media (min-width: 1100px) { .wx-mod-queue { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

.wx-mod-item { display: flex; flex-direction: column; }
.wx-mod-item .wx-card-body { display: flex; flex-direction: column; gap: 12px; flex: 1 1 auto; }

.wx-mod-foot { margin-top: auto; display: grid; gap: 10px; padding-top: 4px; }

.wx-mod-decided { overflow-wrap: anywhere; }

.wx-mod-head {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 8px;
}

.wx-mod-head .wx-help a { color: inherit; text-decoration: underline; }

/* Somebody else's words: quoted, wrapped wherever they have to be, and kept to a
   height that leaves the buttons on the screen under a long one. */
.wx-mod-quote {
  margin: 0;
  padding: 10px 12px;
  border-left: 3px solid var(--line);
  background: var(--surface-2);
  border-radius: var(--radius);
  max-height: 12rem;
  overflow-y: auto;
  overflow-wrap: anywhere;
  white-space: pre-wrap;
}

.wx-mod-photo img { max-height: 240px; }

/* The person at the top of a card: face on the left, name and customer on the right. */
.wx-mod-person {
  display: flex;
  align-items: flex-start;
  gap: 14px;
}

.wx-mod-face {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
}

.wx-mod-face img {
  width: 96px;
  height: 96px;
  object-fit: cover;
  border-radius: 50%;
  border: 1px solid var(--line);
}

.wx-mod-face img.is-down { opacity: 0.45; filter: grayscale(1); }
.wx-mod-face img.is-was { width: 56px; height: 56px; opacity: 0.8; }

.wx-mod-noface {
  width: 96px;
  height: 96px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--surface-2);
  color: var(--muted-dim);
  font-size: 34px;
}

.wx-mod-who {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 4px;
  min-width: 0;
}

.wx-mod-handle { font-size: 16px; overflow-wrap: anywhere; }

/* One block per reported comment: tick, the comment once, the reasons rolled up on
   the end, then what people wrote. */
.wx-mod-blocks { display: grid; gap: 8px; }

.wx-mod-block {
  display: grid;
  gap: 6px;
  padding: 8px 10px;
  border-radius: var(--radius);
  background: var(--surface-2);
}

.wx-mod-block.is-down { opacity: 0.6; }

.wx-mod-block-head {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: start;
  column-gap: 10px;
}

.wx-mod-block-head { grid-template-columns: 1fr auto; }
.wx-mod-block-head .wx-mod-quote { margin: 0; background: var(--surface); border-left-color: var(--crit); }
.wx-mod-block-what { font-size: 12.5px; color: var(--muted); align-self: center; display: inline-flex; gap: 6px; align-items: center; }
.wx-mod-block .wx-flags { padding-left: 2px; }

/* The Take down / Put back switch, wherever it sits: small, and off by default reads
   as "leave it". */
.wx-mod-switch { font-size: 12px; gap: 6px; white-space: nowrap; }
.wx-mod-switch .wx-track { width: 32px; height: 18px; }
.wx-mod-switch .wx-track::after { width: 12px; height: 12px; }
.wx-mod-switch input:checked + .wx-track { background: var(--crit); }
.wx-mod-switch input:checked + .wx-track::after { transform: translateX(14px); }
/* Always on the right edge, in the list and under More from alike. */
.wx-mod-block-head .wx-mod-switch, .wx-mod-comment .wx-mod-switch { justify-self: end; align-self: center; }

.wx-mod-comment { grid-template-columns: 1fr auto; }
.wx-mod-comment-body { display: grid; gap: 2px; min-width: 0; }
.wx-mod-comment .wx-help { grid-column: auto; }

.wx-mod-summary { font-weight: 600; color: var(--ink); }
.wx-btn.is-danger-solid { background: var(--crit); border-color: var(--crit); color: #fff; }

/* The compact header: a small face and the name, nothing to decide up here. */
.wx-mod-person.is-compact { align-items: center; gap: 10px; }

.wx-mod-thumb {
  width: 44px; height: 44px; flex: 0 0 auto;
  object-fit: cover; border-radius: 50%;
  border: 1px solid var(--line);
}

.wx-mod-thumb.is-none {
  display: grid; place-items: center;
  background: var(--surface-2); color: var(--muted-dim); font-size: 18px;
}

.wx-mod-thumb.is-was { width: 36px; height: 36px; opacity: 0.8; }

/* A thing in the list - the picture or the name - with its text beside it. */
.wx-mod-thing { display: flex; align-items: center; gap: 10px; min-width: 0; }
.wx-mod-thing-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; font-size: 12.5px; }
.wx-mod-thing-text .wx-mod-handle { font-size: 13px; }

/* The standard-wording buttons over the warning box. */
.wx-mod-picks { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; }
.wx-btn.is-xs { padding: 3px 9px; font-size: 11.5px; }

/* A report's own block: the line, then the comment it came from. */
.wx-flag.is-block { flex-direction: column; align-items: stretch; gap: 4px; }
.wx-flag-line { display: flex; align-items: baseline; flex-wrap: wrap; gap: 6px; }
.wx-mod-quote.is-small { font-size: 12.5px; max-height: 6rem; padding: 6px 10px; }

/* The rest of what they said, folded until wanted. */
.wx-mod-more-toggle {
  appearance: none;
  border: 0;
  background: none;
  padding: 0;
  font: inherit;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--ink);
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 6px;
}

.wx-mod-comments { display: grid; gap: 6px; margin-top: 8px; }

.wx-mod-comment {
  display: grid;
  grid-template-columns: auto 1fr;
  column-gap: 8px;
  align-items: start;
  padding: 6px 8px;
  border-radius: var(--radius);
  background: var(--surface-2);
  font-size: 12.5px;
  cursor: pointer;
}

.wx-mod-comment.is-down { opacity: 0.55; }
.wx-mod-comment input { margin-top: 3px; }
.wx-mod-comment-text { overflow-wrap: anywhere; white-space: pre-wrap; }
.wx-mod-comment .wx-help { grid-column: 2; }

/* The three ticks, and the warning under them. */
.wx-mod-ticks {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 16px;
  font-size: 13px;
}

.wx-mod-ticks label, .wx-mod-warn { display: inline-flex; align-items: center; gap: 6px; cursor: pointer; }
.wx-mod-warn { font-size: 13px; }

/* Two buttons, equal, text centred - a pair of verbs rather than a form row. */
.wx-mod-actions {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
  max-width: 360px;
}

.wx-mod-actions .wx-btn { justify-content: center; text-align: center; }
.wx-mod-actions.is-one { grid-template-columns: 1fr; max-width: 175px; }

/* The reasons, as chips with counts, and the sum on the end of the line. */
.wx-mod-reasons {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
}

.wx-mod-reasons-sum { margin-left: 4px; }

.wx-flags { display: grid; gap: 6px; }

.wx-flag {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 6px;
  font-size: 12px;
}

/* Somebody else's words, so they wrap wherever they must rather than stretching the
   gutter they sit in. */
.wx-flag-note { color: var(--ink); overflow-wrap: anywhere; }
.wx-flag-when { color: var(--muted-dim); margin-left: auto; white-space: nowrap; }

/* ---- stat tiles: the numbers you read before you read anything ---- */

/* A row that reflows rather than a fixed column count: the set of numbers worth
   showing is going to change, and a grid that has to be re-counted every time one
   is added is a grid that will be wrong. auto-fit with a floor means four across
   on a wide screen, two on a tablet, one on a phone, and nobody edits this. */
.wx-tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 14px;
  min-width: 0;
}

.wx-tile {
  display: grid;
  gap: 2px;
  align-content: start;
  padding: 14px 16px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
}

/* The label above the number, not below it: the eye lands on the big figure and
   needs to already know what it is counting. */
.wx-tile-label {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--muted);
}

.wx-tile-note { font-size: 12px; color: var(--muted); }

/* A tile is a link when there is somewhere to go and look at what it counted.
   Same box, so the row stays even - only the cursor and the hover say so. */
a.wx-tile { text-decoration: none; color: inherit; }
a.wx-tile:hover { border-color: var(--accent); }

/* Wanting attention, in the tile's own number rather than a badge beside it: the
   figure is the thing being read, so that is where the colour belongs. Only ever
   when the number is not zero - a red 0 is a false alarm. */
.wx-tile.is-warn .wx-stat { color: var(--warn); }
.wx-tile.is-crit .wx-stat { color: var(--crit); }

/* ---- the logo, one file per ground ---- */

/* The wordmark is white in one file and ink in the other; the strapline and the
   gear are the same teal in both. Both are in the document and CSS shows one, the
   same way the rail picks the gear when collapsed - no src swap, no flash. The
   theme test is the one the tokens use: an explicit choice on <html> wins, else the
   system preference. */
.wx-logo.is-dark { display: none; }

html[data-theme="dark"] .wx-logo.is-light { display: none; }
html[data-theme="dark"] .wx-logo.is-dark { display: inline; }

@media (prefers-color-scheme: dark) {
  html:not([data-theme="light"]) .wx-logo.is-light { display: none; }
  html:not([data-theme="light"]) .wx-logo.is-dark { display: inline; }
}

/* ---- the media library: a wall of thumbnails you pick from ---- */

/* auto-fill rather than a fixed count: this grid sits in a column whose width depends
   on the rail and the gutter, and a tile is a fixed-ish size rather than a share of
   the row - twelve small images should make three rows, not one row of twelve slivers. */
.wx-assetgrid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(132px, 1fr));
  gap: 12px;
}

.wx-asset {
  display: grid;
  gap: 6px;
  padding: 8px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  cursor: pointer;
  text-align: left;
  min-width: 0;
}
.wx-asset:hover { border-color: var(--accent); }
.wx-asset.is-selected { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent) inset; }

/* A fixed box with the image contained inside it, so a wall of mixed portrait and
   landscape pictures still lines up - and object-fit keeps them from being squashed
   to fit, which on a prize photograph would misrepresent the prize. */
.wx-asset-frame {
  display: flex;
  align-items: center;
  justify-content: center;
  aspect-ratio: 1;
  overflow: hidden;
  border-radius: calc(var(--radius) - 2px);
  background: var(--surface-2, var(--line-soft));
}
.wx-asset-frame img { max-width: 100%; max-height: 100%; object-fit: contain; }

.wx-asset-name {
  font-size: 12.5px;
  font-weight: 500;
  color: var(--ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.wx-asset-meta { display: flex; flex-wrap: wrap; gap: 4px; }

.wx-asset-tag {
  font-size: 10.5px;
  font-weight: 600;
  padding: 1px 6px;
  border-radius: 999px;
  border: 1px solid var(--line);
  color: var(--muted);
}
.wx-asset-tag.is-used { border-color: var(--accent); color: var(--accent); }
.wx-asset-tag.is-archived { border-color: var(--muted-dim); color: var(--muted); }

/* The detail panel's preview - taller than a tile, since this is the one you are
   actually looking at. */
.wx-asset-detail {
  display: flex;
  align-items: center;
  justify-content: center;
  max-height: 220px;
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface-2, var(--line-soft));
}
.wx-asset-detail img { max-width: 100%; max-height: 220px; object-fit: contain; }

/* ---- the image drawer, and the slots that open it ---- */

/* Dims the page behind rather than blocking it outright: the drawer is a choice being
   made about something still visible, and clicking away is the ordinary way to say
   "not now". */
.wx-drawer-scrim {
  position: fixed;
  inset: 0;
  z-index: 70;
  background: rgba(4, 8, 14, .45);
}

.wx-drawer {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  z-index: 71;
  width: min(460px, 100vw);
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border-left: 1px solid var(--line);
  box-shadow: -18px 0 40px rgba(4, 8, 14, .18);
  animation: wx-drawer-in .16s ease-out;
}

@keyframes wx-drawer-in { from { transform: translateX(24px); opacity: 0; } }

@media (prefers-reduced-motion: reduce) { .wx-drawer { animation: none; } }

.wx-drawer-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 14px 16px;
  border-bottom: 1px solid var(--line);
}
.wx-drawer-head h2 { font-size: 14px; font-weight: 600; }

.wx-drawer-close {
  border: 1px solid var(--line);
  background: none;
  border-radius: var(--radius);
  width: 28px;
  height: 28px;
  cursor: pointer;
  color: var(--muted);
  font-size: 16px;
  line-height: 1;
}
.wx-drawer-close:hover { border-color: var(--crit); color: var(--crit); }

/* The body scrolls, the head and foot do not - so search and the cancel button stay
   reachable however many images a partner has. */
.wx-drawer-body {
  flex: 1;
  overflow-y: auto;
  padding: 14px 16px;
  display: grid;
  gap: 14px;
  align-content: start;
}

.wx-drawer-upload {
  display: grid;
  gap: 6px;
  padding: 12px;
  border: 1px dashed var(--line);
  border-radius: var(--radius);
}

.wx-drawer-foot {
  display: flex;
  gap: 8px;
  justify-content: flex-end;
  padding: 12px 16px;
  border-top: 1px solid var(--line);
}

/* Several image slots side by side - a store item has six and a variant three, and
   stacked they made a column taller than the form beside them. auto-fill so the pair
   becomes three or four across on a wide screen rather than staying at two. */
.wx-slotgrid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
  gap: 12px;
  align-items: start;
  min-width: 0;
}

/* An image slot on a form: the picture, or an invitation to pick one. */
.wx-slot { display: grid; gap: 8px; justify-items: start; min-width: 0; }

.wx-slot-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  max-width: 240px;
  aspect-ratio: 4 / 3;
  padding: 0;
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface-2, var(--line-soft));
  cursor: pointer;
}
.wx-slot-btn:hover:not(:disabled) { border-color: var(--accent); }
.wx-slot-btn:disabled { cursor: default; opacity: .6; }
.wx-slot-btn img { max-width: 100%; max-height: 100%; object-fit: contain; }

.wx-slot-btn.is-empty { border-style: dashed; }

.wx-slot-empty {
  display: grid;
  gap: 6px;
  justify-items: center;
  color: var(--muted);
  font-size: 12.5px;
}
.wx-slot-empty i { font-size: 18px; color: var(--muted-dim); }

/* The art a slot inherits when nothing has been chosen over it - the catalog's picture,
   shown faded so it reads as "this is what you would get" rather than as a choice that
   has already been made. */
.wx-slot-inherited {
  max-width: 100%;
  max-height: 84px;
  border-radius: 6px;
  opacity: 0.75;
}

/* ---- section header: breadcrumb trail carrying the selected-record chain ---- */

/* Sits under the sub nav: you pick the page first, then see which record that
   page is working on. */
.wx-section-head {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  padding: 10px 2px 0;
  font-size: 12.5px;
}
.wx-section-head a { color: var(--muted); text-decoration: none; }
.wx-section-head a:hover { color: var(--ink); text-decoration: underline; }
.wx-section-head .sep { color: var(--muted-dim); font-size: 11px; }
.wx-section-head .current { font-weight: 600; }

/* Lets a side panel - the phone preview - start level with the first card
   rather than the page heading. One variable so the drop is tuned in a single
   place if the heading height changes. */
.wx-aside-offset { padding-top: var(--wx-aside-drop); }

/* ------------------------------ dialogs ---------------------------------- */

/* Bootstrap modals render inside the page, so they inherit the tokens - but
   their own theme paints a white card with dark text regardless. Repaint the
   parts that carry colour so a dialog matches the mode it opens in. */
.wx .modal-content {
  background: var(--surface);
  color: var(--ink);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}

.wx .modal-header,
.wx .modal-footer {
  border-color: var(--line-soft);
}

.wx .modal-title { font-weight: 600; }

.wx .modal-header .close,
.wx .modal-header .btn-close {
  background: none;
  border: 0;
  color: var(--muted);
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
  opacity: 1;
}
.wx .modal-header .close:hover { color: var(--ink); }

.wx .modal-backdrop { background-color: #04080E; }

/* The alerts inside these dialogs carry the warning, so they follow the
   semantic tokens rather than Bootstrap's fixed pastels. */
.wx .modal-body .alert-danger {
  background: var(--crit-soft);
  border-color: color-mix(in srgb, var(--crit) 35%, transparent);
  color: var(--crit);
}
.wx .modal-body .alert-warning {
  background: var(--warn-soft);
  border-color: color-mix(in srgb, var(--warn) 35%, transparent);
  color: var(--warn);
}

/* StatusMessage renders the same Bootstrap alerts outside a dialog - on the
   account pages it is the only feedback the user gets, so give it the tokens
   too rather than leaving one pastel box in an otherwise themed card. */
.wx .alert {
  border: 1px solid transparent;
  border-radius: var(--radius);
  padding: 10px 12px;
  font-size: 13px;
  margin: 0;
}
.wx .alert-danger {
  background: var(--crit-soft);
  border-color: color-mix(in srgb, var(--crit) 35%, transparent);
  color: var(--crit);
}
.wx .alert-success {
  background: var(--good-soft);
  border-color: color-mix(in srgb, var(--good) 35%, transparent);
  color: var(--good);
}
.wx .alert-warning {
  background: var(--warn-soft);
  border-color: color-mix(in srgb, var(--warn) 35%, transparent);
  color: var(--warn);
}
.wx .alert a { color: inherit; font-weight: 600; }

/* ------------------------------- toasts ---------------------------------- */

/* Bottom-right so a confirmation never covers the form controls or the save
   action that produced it. */
.wx-toasts {
  position: fixed;
  right: 16px;
  bottom: 16px;
  z-index: 80;
  display: grid;
  gap: 8px;
  justify-items: end;
  pointer-events: none;
}

.wx-toast {
  background: var(--ink);
  color: var(--surface);
  padding: 10px 14px;
  border-radius: var(--radius);
  font-size: 13px;
  font-weight: 600;
  box-shadow: 0 8px 24px rgba(0, 0, 0, .28);
  max-width: 340px;
}

@media (prefers-reduced-motion: no-preference) {
  .wx-toast { animation: wx-toast-in .18s ease-out; }
}
@keyframes wx-toast-in {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: none; }
}

/* ------------------------- stream library cards -------------------------- */

/* streamlibrary.css lays these out well but hard-codes #f9f9f9 / #ddd, so the
   cards stayed light in dark mode. Only the colours are overridden here; the
   layout in that file is left alone. */
.wx .feed-provider {
  background-color: var(--surface);
  border-color: var(--line);
  border-radius: var(--radius);
  color: var(--ink);
}
.wx .feed-provider:hover { background-color: var(--surface-2); }

.wx .provider-name { font-size: 16px; font-weight: 700; }

.wx .provider-description { color: var(--muted); }

.wx .feed-details span {
  color: var(--muted);
  font-size: 12.5px;
}

.wx .feed-links a { color: var(--accent); text-decoration: none; }
.wx .feed-links a:hover { text-decoration: underline; }

.wx .provider-logo { background: var(--surface-2); border-radius: 4px; }

/* A single-column form has no reason to run the full width of a wide admin -
   the labels end up a screen away from the inputs they belong to. */
.wx .wx-card.wx-narrow { max-width: 640px; }

/* Checkbox list, for a short set of options where seeing all of them at once
   beats a token box that hides the ones you did not pick. */
.wx-checks { display: grid; gap: 8px; }
.wx-check {
  display: flex;
  align-items: center;
  gap: 9px;
  font-size: 13px;
  cursor: pointer;
}
.wx-check input { accent-color: var(--accent); width: 15px; height: 15px; }

/* Longer lists - roles, permissions - laid out across the card rather than as
   one tall column. Wraps on its own, so no counting to four and a manual
   line break every fourth item. */
.wx-checks.is-cols { grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); gap: 8px 16px; }

/* Capped for lists long enough to push the rest of the card off screen -
   all fifty states, say. Scrolls itself rather than the page. */
.wx-checks.is-scroll {
  max-height: 260px;
  overflow-y: auto;
  padding: 10px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
}

/* Two-level option picker. Groups stay open rather than collapsing: there are
   only ever a handful, and hiding them behind disclosure buys nothing. */
.wx-tree {
  display: grid;
  gap: 14px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 12px;
  background: var(--surface-2);
  max-height: 320px;
  overflow-y: auto;
}
.wx-tree-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}
.wx-tree-group { display: grid; gap: 7px; }

.wx-tree-grouphead { display: flex; align-items: center; gap: 8px; }
.wx-tree-grouphead .wx-check { flex: 1 1 auto; min-width: 0; }

/* Square so + and - do not shift the label between states. */
.wx-tree-toggle {
  flex: 0 0 auto;
  width: 20px;
  height: 20px;
  line-height: 1;
  font-size: 14px;
  font-family: var(--f-mono);
  border: 1px solid var(--line);
  border-radius: 4px;
  background: var(--surface);
  color: var(--muted);
  cursor: pointer;
  display: grid;
  place-items: center;
}
.wx-tree-toggle:hover { background: var(--surface-2); color: var(--ink); }
.wx-check.is-group { font-weight: 600; }

/* Always shown, so a collapsed group still says how much is inside it. Muted
   until something is selected, so the used groups stand out when scanning. */
.wx-tree-count {
  flex: 0 0 auto;
  font-size: 11px;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  color: var(--muted);
  padding: 1px 6px;
  border: 1px solid var(--line);
  border-radius: 999px;
}
.wx-tree-count.is-on { color: var(--accent); border-color: var(--accent); }

/* Indented so a value reads as belonging to the option above it. */
.wx-tree-children {
  display: grid;
  gap: 7px;
  padding-left: 22px;
  border-left: 1px solid var(--line);
  margin-left: 6px;
}

/* A single number worth reading at a glance - entry counts, totals. */
.wx-stat {
  font-size: 22px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  letter-spacing: -.01em;
}

/* Recovery codes and authenticator keys: monospace, and set wide enough that a
   character can't be misread while it is being copied by hand. */
.wx-codes { display: flex; flex-wrap: wrap; gap: 8px; }

/* A merge field you click to insert, rather than one you copy. Same look as the
   static codes below so the two read as the same kind of thing. */
.wx-code-btn {
  font-family: var(--f-mono);
  font-size: 12.5px;
  padding: 5px 9px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface-2);
  color: var(--ink);
  cursor: pointer;
  white-space: nowrap;
}
.wx-code-btn:hover { border-color: var(--accent); color: var(--accent); }
.wx-code-btn.is-on { border-color: var(--accent); color: var(--accent); background: var(--accent-soft, transparent); }

.wx-codes code {
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
  font-size: 13px;
  letter-spacing: .06em;
  padding: 6px 10px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface-2);
  color: var(--ink);
  user-select: all;
}

/* Numbered because the authenticator setup genuinely is a sequence - the key
   has to reach the app before it can produce the code that verifies it. */
.wx-steps { list-style: none; counter-reset: wx-step; display: grid; gap: 20px; }
.wx-steps > li {
  counter-increment: wx-step;
  display: grid;
  gap: 6px;
  padding-left: 38px;
  position: relative;
}
.wx-steps > li::before {
  content: counter(wx-step);
  position: absolute;
  left: 0;
  top: 0;
  width: 24px;
  height: 24px;
  border-radius: 999px;
  display: grid;
  place-items: center;
  font-size: 12px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  background: var(--surface-2);
  border: 1px solid var(--line);
  color: var(--muted);
}

/* ------------------------------ sign in ---------------------------------- */

.wx-auth {
  min-height: 100vh;
  background: var(--ground);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px 16px;
}

.wx-auth-card {
  width: 100%;
  max-width: 380px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 28px 26px 24px;
  display: grid;
  gap: 16px;
}

/* Register pairs its fields, so its card needs more room than the single-column
   sign-in card. Keying off the pairs themselves keeps the width decision with
   the page instead of adding a flag the layout would have to pass down. */
.wx-auth-card:has(.wx-frow) { max-width: 470px; }

/* Without :has() the card stays narrow, and .wx-frow.is-two splits on viewport
   width, not card width - so it would pair fields into ~170px columns. */
@supports not selector(:has(*)) {
  .wx-auth-card .wx-frow.is-two { grid-template-columns: 1fr; }
}

/* StatusMessage sits beside the form rather than inside its grid, so the section
   needs its own gap or the alert butts up against the heading below it. */
.wx-auth-card > section { display: grid; gap: 16px; }

/* Centred and given some room: it is the only thing on the card above the form,
   and at 30px hard against the left edge it read as a stray favicon. */
.wx-auth-logo { max-height: 46px; width: auto; justify-self: center; }

.wx-auth-card h2 { font-size: 19px; font-weight: 700; }

.wx-auth-card hr { display: none; }

/* Links stack under the form rather than sitting in a row - on a phone a row of
   three would wrap into something unreadable. */
.wx-auth-links { display: grid; gap: 7px; font-size: 13px; }
.wx-auth-links a { color: var(--accent); text-decoration: none; }
.wx-auth-links a:hover { text-decoration: underline; }

.wx-auth-card .validation-message,
.wx-auth-card .text-danger { color: var(--crit); font-size: 12.5px; }

/* ------------------------------ editor ----------------------------------- */

/* Quill wrapper. quill.snow.css draws a light editor with dark icons, so
   everything here is either layout or re-theming it onto the tokens - without
   that, the editor stays white while the rest of the page goes dark. */

.wx-editor {
  position: relative;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  overflow: hidden;
}

/* Fullscreen is a CSS state rather than the Fullscreen API: it never prompts,
   it cannot be refused, and Escape-to-exit is not something we have to wire up. */
.wx-editor.is-full {
  position: fixed;
  inset: 0;
  z-index: 200;
  border-radius: 0;
  display: grid;
  grid-template-rows: auto 1fr;
}
.wx-editor.is-full .wx-editor-host,
.wx-editor.is-full .wx-editor-source { height: auto !important; }

/* Source and fullscreen sit over the right-hand end of Quill's own toolbar,
   which leaves that toolbar as Quill renders it. */
.wx-editor-extra {
  position: absolute;
  top: 5px;
  right: 6px;
  z-index: 3;
  display: flex;
  gap: 4px;
}
.wx-editor-btn {
  font-family: var(--f-mono);
  font-size: 12px;
  line-height: 1;
  padding: 5px 7px;
  border-radius: 4px;
  border: 1px solid var(--line);
  background: var(--surface);
  color: var(--muted);
  cursor: pointer;
}
.wx-editor-btn:hover { background: var(--surface-2); color: var(--ink); }
.wx-editor-btn.is-on { background: var(--accent-soft); border-color: var(--accent); color: var(--accent); }

/* Hidden rather than removed while the source view is open, so Quill survives
   the toggle instead of being destroyed and rebuilt. */
.wx-editor[data-source="true"] .wx-editor-host { display: none; }

.wx-editor-source {
  display: block;
  width: 100%;
  border: 0;
  padding: 12px 14px;
  background: var(--surface-2);
  color: var(--ink);
  font-family: var(--f-mono);
  font-size: 12.5px;
  line-height: 1.6;
  resize: vertical;
}
.wx-editor-source:focus { outline: 2px solid var(--accent); outline-offset: -2px; }

/* ---- re-theming Quill ---- */

.wx .ql-toolbar.ql-snow,
.wx .ql-container.ql-snow { border: 0; }
.wx .ql-toolbar.ql-snow { border-bottom: 1px solid var(--line); padding: 7px 92px 7px 8px; }

.wx .ql-container { font-family: var(--f-ui); font-size: 14px; }
.wx .ql-editor { color: var(--ink); }
.wx .ql-editor.ql-blank::before { color: var(--muted-dim); font-style: normal; }

/* Quill draws its icons as SVG strokes and fills, so colour has to be set on
   those rather than on the button. */
.wx .ql-snow .ql-stroke { stroke: var(--muted); }
.wx .ql-snow .ql-fill, .wx .ql-snow .ql-stroke.ql-fill { fill: var(--muted); }
.wx .ql-snow .ql-picker { color: var(--muted); }

.wx .ql-snow button:hover .ql-stroke,
.wx .ql-snow button.ql-active .ql-stroke,
.wx .ql-snow .ql-picker-label:hover .ql-stroke { stroke: var(--accent); }
.wx .ql-snow button:hover .ql-fill,
.wx .ql-snow button.ql-active .ql-fill { fill: var(--accent); }
.wx .ql-snow button.ql-active,
.wx .ql-snow .ql-picker-label.ql-active { background: var(--accent-soft); border-radius: 4px; }

/* The dropdown panels are drawn on their own white surface. */
.wx .ql-snow .ql-picker-options {
  background: var(--surface);
  border-color: var(--line);
  border-radius: var(--radius);
}
.wx .ql-snow .ql-picker.ql-expanded .ql-picker-label { border-color: var(--line); }
.wx .ql-snow .ql-tooltip {
  background: var(--surface);
  border-color: var(--line);
  color: var(--ink);
  box-shadow: 0 6px 18px rgba(0, 0, 0, .18);
  border-radius: var(--radius);
}
.wx .ql-snow .ql-tooltip input[type=text] {
  background: var(--surface-2);
  border-color: var(--line);
  color: var(--ink);
}

/* ---- collapsible field group ---- */

/* <details> rather than component state: it opens without a server round trip
   and keeps its own state across re-renders. */
.wx-fold { display: grid; gap: 6px; }

.wx-fold > summary {
  font-size: 12.5px;
  font-weight: 600;
  cursor: pointer;
  list-style: none;
  display: flex;
  align-items: center;
  gap: 7px;
}
.wx-fold > summary::-webkit-details-marker { display: none; }

/* Own marker, so it is the same triangle in every browser and rotates. */
.wx-fold > summary::before {
  content: "";
  width: 0;
  height: 0;
  border-left: 5px solid var(--muted);
  border-top: 4px solid transparent;
  border-bottom: 4px solid transparent;
  transition: transform .12s ease;
}
.wx-fold[open] > summary::before { transform: rotate(90deg); }
.wx-fold > summary:hover { color: var(--accent); }

/* Two-up row where the first field wants the room and the second is a number. */
.wx-frow.is-wide-narrow { display: grid; gap: 15px; }
@media (min-width: 640px) {
  .wx-frow.is-wide-narrow { grid-template-columns: minmax(0, 1fr) 140px; }
}

/* ------------------------------- board ---------------------------------- */

/* Column board, replacing SfKanban. Columns are a grid rather than flex so
   every swimlane's cells line up with the headings above them. */
.wx-board { display: grid; gap: 10px; }

.wx-board-head,
.wx-board-row { display: grid; gap: 10px; }

.wx-board-col-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 8px 10px;
  border-bottom: 2px solid var(--line);
  font-size: 12.5px;
  font-weight: 600;
}
.wx-board-count {
  font-family: var(--f-mono);
  font-size: 11px;
  color: var(--muted);
}

.wx-board-row-head {
  font-size: 12.5px;
  font-weight: 600;
  color: var(--muted);
  padding: 6px 2px 0;
  border-top: 1px solid var(--line-soft);
}

.wx-board-cell {
  display: grid;
  gap: 8px;
  align-content: start;
  min-height: 72px;
  min-width: 0;
  padding: 8px;
  border-radius: var(--radius);
  background: var(--surface-2);
}
/* Only while something is over it - a permanently outlined drop zone reads as
   five empty boxes rather than five columns. */
.wx-board-cell.is-over {
  outline: 2px dashed var(--accent);
  outline-offset: -2px;
  background: var(--accent-soft);
}

.wx-board-card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-left-width: 3px;
  border-radius: var(--radius);
  padding: 9px 10px;
  font-size: 13px;
  cursor: grab;
  min-width: 0;
}
.wx-board-card:hover { border-color: var(--accent); }
.wx-board-card.is-dragging { opacity: .4; cursor: grabbing; }
.wx-board-card b { display: block; margin-bottom: 3px; }
.wx-board-card .sub { display: block; color: var(--muted); font-size: 12px; }

/* ------------------------------ modal ----------------------------------- */

/* Rendered only while open, so there is no hidden dialog sitting in the DOM and
   no JS to show or hide it - which is what SfDialog and the Bootstrap modals
   both needed. */
.wx-modal-back {
  position: fixed;
  inset: 0;
  z-index: 300;
  display: grid;
  place-items: center;
  padding: 20px;
  background: rgba(6, 10, 16, .55);
  overflow-y: auto;
}
.wx-modal {
  width: 100%;
  max-width: 560px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: 0 18px 48px rgba(6, 10, 16, .28);
}
.wx-modal-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 13px 16px;
  border-bottom: 1px solid var(--line-soft);
}
.wx-modal-head h2 { font-size: 14px; font-weight: 600; }
.wx-modal-body { padding: 16px; display: grid; gap: 15px; }
.wx-modal-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 13px 16px;
  border-top: 1px solid var(--line-soft);
}
.wx-modal-foot .right { display: flex; gap: 8px; }

/* The card's colour marker. The colour classes live in app.css and are chosen
   per task, so this only supplies the shape. */
.wx-board-dot {
  display: inline-block;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  margin-right: 6px;
  vertical-align: baseline;
  border: 1px solid var(--line);
}

/* --------------------------- phone preview ------------------------------ */

/* Floats over the page rather than sitting in it, so every page keeps its full
   width and the fifteen that used to carry a phone lose their left/right split.
   Positioned by wxPreview.js, which also remembers where it was left. */
.wx-preview {
  position: fixed;
  z-index: 250;
  display: none;
  /* Matches .iphone-frame plus the gutter below. Without a width a fixed block
     fills the viewport, which puts the close button on the far side of the
     screen and makes the drag clamp meaningless. */
  width: 441px;

  /* A real iPhone is 872px of frame, and at full size that does not fit under
     the top bar on a laptop. Scaling the panel rather than shrinking the phone
     keeps the screen inside it 393px wide whatever this is set to, so the app
     still lays out exactly as it does on the device - it is only painted
     smaller. transform, not zoom: zoom would shrink the iframe's own viewport
     and the layout would go back to lying. */
  --wx-preview-scale: 1;
  transform: scale(var(--wx-preview-scale));
  transform-origin: top left;
  /* A transparent grab gutter. The phone's own bezel is only 10px, which is a
     mean target to hit; this widens it to something you can grab without
     aiming, while still looking like nothing but the phone. */
  padding: 14px;
  touch-action: none;          /* the drag handles pointer moves itself */
  cursor: grab;
  filter: drop-shadow(0 16px 34px rgba(6, 10, 16, .34));
}
/* The phone itself: 393 x 852 of screen inside a 10px bezel, which is the iPhone
   16 / 15 / 14 Pro logical viewport and the most common size in use. The size is
   not decoration - the store's grid falls to one column below 312px, so a narrower
   screen would preview a layout the device never shows. */
.iphone-frame {
  width: 413px;
  height: 872px;
  box-sizing: border-box;      /* the 10px bezel counts inside the 413, not on top */
  border: 10px solid #000;
  border-radius: 40px;
  background-color: #000;
  margin: 0;                   /* the panel's padding provides the gutter */
  padding: 0;
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  align-items: center;
  overflow: hidden;
  box-shadow: 0 10px 20px rgba(0, 0, 0, .2);
}

/* The notch, and the camera in it. */
.iphone-frame::before {
  content: '';
  position: absolute;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  width: 100px;
  height: 20px;
  background-color: #000;
  border-radius: 0 0 10px 10px;
  z-index: 2;
}

.iphone-frame::after {
  content: '';
  position: absolute;
  top: 5px;
  left: calc(50% - 40px);
  width: 8px;
  height: 8px;
  background-color: #333;
  border-radius: 50%;
  z-index: 3;
  box-shadow: 0 0 5px rgba(255, 255, 255, .4);
}

/* The screen inside it. The app fills this, and scrolls within it. */
.iphone-screen {
  width: 100%;
  height: 100%;
  border-radius: 30px;
  background-color: #fff;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  position: relative;
}

/* The frame carries its own shadow, which doubles up with the one above. */
.wx-preview .iphone-frame { box-shadow: none; }
.wx-preview[data-open="true"] { display: block; }
.wx-preview.is-dragging { cursor: grabbing; user-select: none; }

/* Sits off the phone's shoulder rather than on the screen it would cover. */
.wx-preview-close {
  position: absolute;
  top: 2px;
  right: 2px;
  z-index: 4;
  width: 26px;
  height: 26px;
  padding: 0;
  border-radius: 50%;
  border: 1px solid var(--line);
  background: var(--surface);
  color: var(--ink);
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
}
.wx-preview-close:hover { border-color: var(--crit); color: var(--crit); }

.wx-preview-foot {
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: 8px;
  padding-top: 8px;
  font-size: 12px;
  text-align: center;
}

/* Start Over and its confirmation: quiet text buttons under the phone. */
.wx-preview-reset {
  padding: 0;
  border: 0;
  background: none;
  color: var(--accent);
  font: inherit;
  font-weight: 600;
  cursor: pointer;
}
.wx-preview-reset.is-crit { color: var(--crit); }
.wx-preview-reset:disabled { opacity: .5; cursor: default; }

.wx-preview-empty {
  display: grid;
  place-content: center;
  justify-items: center;
  gap: 10px;
  height: 100%;
  padding: 24px;
  text-align: center;
  color: var(--muted);
  background: var(--surface);
}
.wx-preview-empty span { font-size: 30px; color: var(--muted-dim); }
.wx-preview-empty p { margin: 0; font-size: 13px; }

/* The app running inside the phone. It is a document of its own so the partner
   theme can land on a root without reaching the admin - see AppPhone.razor. */
.wx-app-phone {
  display: block;
  width: 100%;
  height: 100%;
  border: 0;
}

/* The phone is a fixed 295px and does not shrink, so below the point where it
   would cover the form it is hidden rather than made useless. */
/* Below 720px the phone takes the whole screen instead - see the block at the
   end of this file. Nothing is hidden here any more. */

/* --------------------------- rail collapse ------------------------------ */

/* Icon-only rail on desktop, toggled by the same ☰ that opens the overlay on
   mobile - one affordance, and the one everybody already reaches for. State
   lives on the shell so the grid track and the rail contents change together. */
@media (min-width: 1000px) {
  [data-rail="collapsed"] .wx-shell { grid-template-columns: 64px 1fr; }

  [data-rail="collapsed"] .wx-shell .wx-rail-context,
  [data-rail="collapsed"] .wx-shell .wx-nav-group,
  [data-rail="collapsed"] .wx-shell .wx-nav a > span,
  [data-rail="collapsed"] .wx-shell .wx-nav .is-soon > span { display: none; }

  /* The wordmark goes and the gear takes its place. Squashing a lockup into 64px is
     how a logo stops being one. */
  [data-rail="collapsed"] .wx-shell .wx-rail-head img.wx-rail-logo { display: none; }

  [data-rail="collapsed"] .wx-shell .wx-rail-head img.wx-rail-mark {
    display: block;
    max-height: 40px;
  }

  /* The label is gone, so the glyph carries the item on its own and wants the
     whole width to sit in rather than an indent meant for text beside it. */
  [data-rail="collapsed"] .wx-shell .wx-nav a,
  [data-rail="collapsed"] .wx-shell .wx-nav .is-soon {
    justify-content: center;
    padding: 10px 0;
  }
  [data-rail="collapsed"] .wx-shell .wx-nav { padding: 8px 8px 24px; }
  [data-rail="collapsed"] .wx-shell .wx-nav .glyph { width: auto; font-size: 15px; }

  [data-rail="collapsed"] .wx-shell .wx-rail-head { padding: 16px 0 14px; justify-content: center; }


  /* The overlay's own toggle is hidden above 1000px; this is the same button
     doing the other job, so it has to come back. */
  .wx-menu-btn { display: grid; }
}

/* Fit the phone to the window it is floating in. Height is what runs out first:
   the panel is 900px tall, so anything short of a desktop monitor needs it
   scaled. The steps are chosen so the phone stays clear of the top bar with a
   gutter left under it. */
@media (max-height: 1000px) {
  .wx-preview { --wx-preview-scale: 0.88; }
}
@media (max-height: 880px) {
  .wx-preview { --wx-preview-scale: 0.78; }
}
@media (max-height: 780px) {
  .wx-preview { --wx-preview-scale: 0.68; }
}

/* --------------------- phone preview on a phone ------------------------- */

/* Below the point where a 295px phone can float beside anything, it stops
   pretending to be a phone and just becomes the screen - the device is already
   the bezel. The X stays, so there is a way back to the admin. */
@media (max-width: 720px) {
  .wx-preview[data-open="true"] {
    display: block;
    /* Beats the inline left/top the drag writes, which are meaningless here. */
    left: 0 !important;
    top: 0 !important;
    width: 100vw;
    height: 100dvh;
    padding: 0;
    z-index: 300;
    cursor: default;
    filter: none;
    /* The device is the bezel here, so it is already the right size. */
    transform: none;
  }

  .wx-preview .iphone-frame {
    width: 100%;
    height: 100%;
    border: 0;
    border-radius: 0;
  }
  /* The notch and the camera lens are drawn on; on a real phone they are just
     a fake cut-out over the top of the actual content. */
  .wx-preview .iphone-frame::before,
  .wx-preview .iphone-frame::after { display: none; }
  .wx-preview .iphone-screen { border-radius: 0; }

  .wx-preview-foot { display: none; }
  .wx-preview-close {
    top: 10px;
    right: 10px;
    width: 32px;
    height: 32px;
    font-size: 18px;
    box-shadow: 0 2px 8px rgba(6, 10, 16, .3);
  }
}

/* ----------------------------- page foot -------------------------------- */

/* Sticks to the bottom of the viewport rather than the bottom of the document,
   so the save button is reachable from anywhere in a long form. Sits outside
   the card stack, on the page ground, so it reads as belonging to the page
   rather than to whichever card happens to be last. */
.wx-page-foot {
  position: sticky;
  bottom: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  margin-top: 2px;
  padding: 12px 16px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--surface) 92%, transparent);
  backdrop-filter: blur(10px);
}
.wx-page-foot .acts { display: flex; gap: 8px; flex-wrap: wrap; margin-left: auto; }
.wx-page-foot .danger { display: flex; gap: 8px; flex-wrap: wrap; }

/* The page already ends in 60px of padding; without this the bar sits on top of
   the last card instead of clear of it. */
.wx-page > .wx-page-foot { margin-top: 6px; }

/* --------------------- section trail (Streams trial) --------------------- */

/* The trail shares the title's line, hard right, rather than taking a row of
   its own. Its old top padding separated it from the nav that used to sit above
   it; nothing is above it now. */
.wx-page-head .wx-section-head { padding: 0; justify-content: flex-end; }

/* The nav belongs with the title block above it more than with the content
   below, and wx-page's even 16px gap gave it neither. */
.wx-page > .wx-subnav { margin-top: -6px; }

/* ------------------------------ calendar -------------------------------- */

.wx-cal {
  display: grid;
  gap: 12px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 12px;
  min-width: 0;
}

.wx-cal-bar {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
}
.wx-cal-nav { display: flex; align-items: center; gap: 6px; }
.wx-cal-period { font-size: 15px; font-weight: 700; margin: 0; }

.wx-cal-views { display: flex; gap: 2px; margin-left: auto; padding: 2px; border: 1px solid var(--line); border-radius: var(--radius); }
.wx-cal-views button {
  border: 0;
  background: transparent;
  color: var(--muted);
  font-size: 12.5px;
  font-weight: 500;
  padding: 5px 10px;
  border-radius: 4px;
  cursor: pointer;
  white-space: nowrap;
}
.wx-cal-views button:hover { color: var(--ink); }
.wx-cal-views button.is-on { background: var(--accent); color: var(--accent-ink); font-weight: 600; }

/* ---- month ---- */

.wx-cal-month { display: grid; gap: 1px; }

.wx-cal-dow { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 1px; }
.wx-cal-dow span { padding: 4px 6px; font-size: 10.5px; font-weight: 600; letter-spacing: .08em; text-transform: uppercase; color: var(--muted-dim); }

.wx-cal-week { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 1px; }

.wx-cal-day {
  min-height: 108px;
  min-width: 0;
  display: grid;
  align-content: start;
  gap: 3px;
  padding: 4px;
  background: var(--surface);
  border: 1px solid var(--line-soft);
  border-radius: 4px;
}
/* Days either side of the month are shown but recede, so the grid keeps its
   shape without those days competing with the ones in it. */
.wx-cal-day.is-outside { background: var(--surface-2); }
.wx-cal-day.is-outside .num { color: var(--muted-dim); }
.wx-cal-day.is-today { border-color: var(--accent); }
.wx-cal-day .head { display: flex; justify-content: flex-end; }
.wx-cal-day .num {
  border: 0; background: transparent; cursor: pointer;
  font-family: var(--f-mono); font-size: 11.5px; color: var(--muted);
  padding: 1px 4px; border-radius: 3px;
}
.wx-cal-day .num:hover { background: var(--surface-2); color: var(--ink); }
.wx-cal-day.is-today .num { background: var(--accent); color: var(--accent-ink); font-weight: 700; }

.wx-cal-chip {
  display: flex;
  align-items: baseline;
  gap: 5px;
  min-width: 0;
  width: 100%;
  text-align: left;
  border: 0;
  border-left: 3px solid currentColor;
  border-radius: 3px;
  padding: 2px 5px;
  font-size: 11.5px;
  cursor: pointer;
  background: var(--surface-2);
  color: var(--ink);
}
.wx-cal-chip .at { font-family: var(--f-mono); font-size: 10px; color: var(--muted); flex: 0 0 auto; }
.wx-cal-chip .what { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.wx-cal-chip.is-movable { cursor: grab; }
.wx-cal-chip:hover { filter: brightness(.97); }

.wx-cal-more {
  border: 0; background: transparent; cursor: pointer;
  font-size: 11px; color: var(--muted); text-align: left; padding: 1px 5px;
}
.wx-cal-more:hover { color: var(--accent); text-decoration: underline; }

/* ---- week / work week / day ---- */

.wx-cal-time { display: grid; gap: 0; min-width: 0; }

.wx-cal-time-head,
.wx-cal-allday,
.wx-cal-time-body { display: grid; }

.wx-cal-time-head { border-bottom: 1px solid var(--line); }
.wx-cal-time-head .gutter { }
.wx-cal-time-head .day {
  display: grid; gap: 1px; justify-items: center;
  border: 0; background: transparent; cursor: pointer;
  padding: 6px 4px;
}
.wx-cal-time-head .dow { font-size: 10.5px; font-weight: 600; letter-spacing: .08em; text-transform: uppercase; color: var(--muted-dim); }
.wx-cal-time-head .num { font-size: 16px; font-weight: 700; color: var(--ink); }
.wx-cal-time-head .day.is-today .num { color: var(--accent); }

.wx-cal-allday {
  border-bottom: 1px solid var(--line);
  min-height: 30px;
  align-items: start;
}
.wx-cal-allday .gutter {
  font-size: 10px; text-transform: uppercase; letter-spacing: .08em;
  color: var(--muted-dim); padding: 6px 6px 0 0; text-align: right;
}
.wx-cal-allday .cell { display: grid; gap: 2px; padding: 4px 3px; min-width: 0; }

.wx-cal-time-body { position: relative; max-height: 62vh; overflow-y: auto; }

.wx-cal-hours { border-right: 1px solid var(--line); }
.wx-cal-hours .hour { position: relative; }
.wx-cal-hours .hour span {
  position: absolute; top: -7px; right: 6px;
  font-family: var(--f-mono); font-size: 10px; color: var(--muted-dim);
}

.wx-cal-col { position: relative; border-right: 1px solid var(--line-soft); min-width: 0; }
.wx-cal-col .slot { border-bottom: 1px solid var(--line-soft); }
/* The working day, shaded rather than outlined - it is context, not content. */
.wx-cal-col .slot.is-work { background: var(--surface-2); }

.wx-cal-now { position: absolute; left: 0; right: 0; height: 2px; background: var(--crit); z-index: 3; }
.wx-cal-now::before {
  content: ''; position: absolute; left: -3px; top: -3px;
  width: 8px; height: 8px; border-radius: 50%; background: var(--crit);
}

.wx-cal-block {
  position: absolute;
  z-index: 2;
  display: grid;
  align-content: start;
  gap: 1px;
  overflow: hidden;
  text-align: left;
  border: 0;
  border-left: 3px solid currentColor;
  border-radius: 3px;
  padding: 2px 5px;
  font-size: 11.5px;
  cursor: pointer;
  background: var(--surface-2);
  color: var(--ink);
}
.wx-cal-block .at { font-family: var(--f-mono); font-size: 10px; color: var(--muted); }
.wx-cal-block .what { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 500; }
.wx-cal-block:hover { filter: brightness(.97); }

@media (max-width: 700px) {
  .wx-cal-day { min-height: 76px; }
  .wx-cal-views button { padding: 5px 7px; font-size: 12px; }
}

/* The event colour classes in app.css set a solid background with !important,
   so a chip carrying one is a mid-tone block rather than the pale default. They
   are all muted mid-tones, so white reads on every one of them; an event with
   no colour keeps the default and its own text colour. */
.wx-cal-chip[class*="event-"],
.wx-cal-block[class*="event-"] { color: #fff; border-left-color: rgba(0, 0, 0, .25); }
.wx-cal-chip[class*="event-"] .at,
.wx-cal-block[class*="event-"] .at { color: rgba(255, 255, 255, .8); }

/* Empty space in a day cell adds an event there, so it needs to look like it
   will do something. The slots in the time grid do the same. */
.wx-cal-day { cursor: pointer; }
.wx-cal-day:hover { background: var(--surface-2); }
.wx-cal-day.is-outside:hover { background: var(--line-soft); }
.wx-cal-col .slot { cursor: pointer; }
.wx-cal-col .slot:hover { background: var(--accent-soft); }

/* Only while something is being dragged over it. */
.wx-cal-day.is-over { outline: 2px dashed var(--accent); outline-offset: -2px; background: var(--accent-soft); }

/* ---- theme picker ------------------------------------------------------ */

/* A control and its reset on one line. */
.wx .wx-inline {
    display: flex;
    align-items: center;
    gap: 10px;
}

.wx .wx-themes {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
    gap: 12px;
    margin-top: 10px;
}

/* The card is the control: the radio inside it is hidden, so the browser still
   gives us the group and arrow keys while the card carries the state. */
.wx .wx-theme {
    display: grid;
    gap: 4px;
    padding: 10px;
    border: 1px solid var(--line);
    border-radius: 8px;
    cursor: pointer;
    background: var(--surface);
    transition: border-color .15s ease, box-shadow .15s ease;
}

.wx .wx-theme:hover {
    border-color: var(--muted-dim);
}

.wx .wx-theme.is-on {
    border-color: var(--accent);
    box-shadow: 0 0 0 1px var(--accent);
}

.wx .wx-theme input {
    position: absolute;
    opacity: 0;
    width: 0;
    height: 0;
}

.wx .wx-theme:has(input:focus-visible) {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

/* A miniature painted from the theme's own tokens - never a screenshot, which
   would go stale the first time a theme is edited. */
.wx .wx-theme-chip {
    display: block;
    position: relative;
    height: 74px;
    border-radius: 5px;
    overflow: hidden;
    border: 1px solid var(--line);
}

.wx .wx-theme-bar {
    display: block;
    height: 8px;
    width: 100%;
}

.wx .wx-theme-card {
    display: block;
    position: absolute;
    left: 10px;
    right: 10px;
    top: 10px;
    bottom: 10px;
    border-radius: 3px;
    padding: 9px 8px;
}

.wx .wx-theme-line {
    display: block;
    height: 5px;
    width: 45%;
    border-radius: 2px;
    opacity: .75;
    margin-bottom: 5px;
}

.wx .wx-theme-line.is-wide {
    width: 72%;
    opacity: .9;
}

.wx .wx-theme-name {
    font-weight: 600;
    font-size: .85rem;
}

.wx .wx-theme-blurb {
    font-size: .75rem;
    color: var(--muted);
    line-height: 1.35;
}

/* ---- home page builder -------------------------------------------------- */

.wx .wx-layouts {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(165px, 1fr));
    gap: 10px;
    margin: 10px 0 18px;
}

.wx .wx-layout {
    display: grid;
    gap: 4px;
    padding: 9px;
    border: 1px solid var(--line);
    border-radius: 8px;
    background: var(--surface);
    cursor: pointer;
    transition: border-color .15s ease, box-shadow .15s ease;
}

.wx .wx-layout:hover { border-color: var(--muted-dim); }

.wx .wx-layout.is-on {
    border-color: var(--accent);
    box-shadow: 0 0 0 1px var(--accent);
}

.wx .wx-layout input {
    position: absolute;
    opacity: 0;
    width: 0;
    height: 0;
}

.wx .wx-layout:has(input:focus-visible) {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

/* A miniature of the shape, drawn from the layout's own slots so it cannot
   disagree with what the page will do. */
.wx .wx-layout-mini {
    display: grid;
    gap: 3px;
    height: 62px;
    padding: 4px;
    border-radius: 5px;
    background: var(--surface-2);
    border: 1px solid var(--line);
}

.wx .wx-layout-mini span {
    background: var(--muted-dim);
    opacity: .38;
    border-radius: 2px;
    min-height: 6px;
}

.wx .wx-layout-mini[data-layout="stack"] { grid-template-areas: "hero" "full"; }

.wx .wx-layout-mini[data-layout="rail-right"] {
    grid-template-columns: repeat(3, 1fr);
    grid-template-areas: "hero hero hero" "main main rail" "fold1 fold2 fold3";
}

.wx .wx-layout-mini[data-layout="rail-left"] {
    grid-template-columns: repeat(3, 1fr);
    grid-template-areas: "hero hero hero" "rail main main" "fold1 fold2 fold3";
}

.wx .wx-layout-mini[data-layout="halves"] {
    grid-template-columns: repeat(2, 1fr);
    grid-template-areas: "hero hero" "left right" "full full";
}

.wx .wx-layout-mini[data-layout="showcase"] {
    grid-template-columns: repeat(3, 1fr);
    grid-template-areas: "hero hero hero" "feature feature feature" "fold1 fold2 fold3";
}

.wx .wx-layout-mini span[data-slot="hero"]    { grid-area: hero; opacity: .6; }
.wx .wx-layout-mini span[data-slot="main"]    { grid-area: main; }
.wx .wx-layout-mini span[data-slot="rail"]    { grid-area: rail; }
.wx .wx-layout-mini span[data-slot="left"]    { grid-area: left; }
.wx .wx-layout-mini span[data-slot="right"]   { grid-area: right; }
.wx .wx-layout-mini span[data-slot="feature"] { grid-area: feature; }
.wx .wx-layout-mini span[data-slot="fold1"]   { grid-area: fold1; }
.wx .wx-layout-mini span[data-slot="fold2"]   { grid-area: fold2; }
.wx .wx-layout-mini span[data-slot="fold3"]   { grid-area: fold3; }
.wx .wx-layout-mini span[data-slot="full"]    { grid-area: full; }

.wx .wx-layout-name { font-weight: 600; font-size: .84rem; }
.wx .wx-layout-blurb { font-size: .72rem; color: var(--muted); line-height: 1.35; }

/* ---- the slot grid ------------------------------------------------------ */

/* Mirrors the layout being edited, so the arrangement on screen is the
   arrangement on the page rather than a list that has to be imagined. */
.wx .wx-builder {
    display: grid;
    gap: 10px;
}

/* The two shapes that are one column. Without their own areas, the slots' named
   grid-areas (hero, full) pointed at areas that did not exist, and the grid made
   stray columns for them sized to their text - the app's rows sat in a narrow strip
   instead of across the card. */
.wx .wx-builder[data-layout="app"] {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas: "full";
}

.wx .wx-builder[data-layout="stack"] {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas: "hero" "full";
}

.wx .wx-builder[data-layout="rail-right"] {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    grid-template-areas: "hero hero hero" "main main rail" "fold1 fold2 fold3";
}

.wx .wx-builder[data-layout="rail-left"] {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    grid-template-areas: "hero hero hero" "rail main main" "fold1 fold2 fold3";
}

.wx .wx-builder[data-layout="halves"] {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    grid-template-areas: "hero hero" "left right" "full full";
}

.wx .wx-builder[data-layout="showcase"] {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    grid-template-areas: "hero hero hero" "feature feature feature" "fold1 fold2 fold3" "full full full";
}

.wx .wx-bslot[data-slot="hero"]    { grid-area: hero; }
.wx .wx-bslot[data-slot="main"]    { grid-area: main; }
.wx .wx-bslot[data-slot="rail"]    { grid-area: rail; }
.wx .wx-bslot[data-slot="left"]    { grid-area: left; }
.wx .wx-bslot[data-slot="right"]   { grid-area: right; }
.wx .wx-bslot[data-slot="feature"] { grid-area: feature; }
.wx .wx-bslot[data-slot="fold1"]   { grid-area: fold1; }
.wx .wx-bslot[data-slot="fold2"]   { grid-area: fold2; }
.wx .wx-bslot[data-slot="fold3"]   { grid-area: fold3; }
.wx .wx-bslot[data-slot="full"]    { grid-area: full; }

.wx .wx-bslot {
    display: grid;
    gap: 6px;
    align-content: start;
    min-width: 0;
    min-height: 92px;
    padding: 8px;
    border: 1px dashed var(--line);
    border-radius: 8px;
    background: var(--surface);
}

/* wxDrag.js adds is-over; doing it from Blazor state re-renders the dragged
   node and Firefox cancels the drag. */
.wx .wx-bslot.is-over {
    border-color: var(--accent);
    border-style: solid;
    background: var(--accent-soft);
}

.wx .wx-bslot-head {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 8px;
    font-size: .7rem;
    text-transform: uppercase;
    letter-spacing: .1em;
    color: var(--muted-dim);
}

.wx .wx-bslot-hint { text-transform: none; letter-spacing: 0; font-size: .68rem; }

.wx .wx-bslot-add {
    padding: 4px 7px;
    font-size: .76rem;
    color: var(--muted);
}

/* ---- a placed module ----------------------------------------------------
   wx-module, not wx-chip: this was wx-chip, and since it sits under .wx it
   outranked the status pill's own tone rules everywhere in the admin - every
   Active / Pending chip drew as a grey bordered tile with a grab cursor. */

.wx .wx-module {
    display: grid;
    gap: 1px;
    padding: 7px 9px;
    border: 1px solid var(--line);
    border-radius: 6px;
    background: var(--surface-2);
    cursor: grab;
    position: relative;
    min-width: 0;
}

.wx .wx-module:hover { border-color: var(--muted-dim); }

.wx .wx-module.is-on {
    border-color: var(--accent);
    box-shadow: 0 0 0 1px var(--accent);
}

/* Set by wxDrag.js on the node being dragged. */
.wx .wx-module.is-dragging { opacity: .45; }

/* A module that renders nothing yet should not look like working furniture. */
.wx .wx-module.is-inert { opacity: .62; border-style: dashed; }

.wx .wx-module-name {
    font-size: .82rem;
    font-weight: 600;
    padding-right: 74px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.wx .wx-module-sum {
    font-size: .7rem;
    color: var(--muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    padding-right: 74px;
}

/* A placed module that is switched off elsewhere - the daily streak with the mechanic
   off in Customers. Red on the chip so it reads before anybody selects it. */
.wx .wx-module-sum.is-bad { color: var(--crit); font-weight: 600; text-transform: uppercase; letter-spacing: .06em; }

.wx .wx-module-ctl {
    position: absolute;
    top: 4px;
    right: 5px;
    display: flex;
    gap: 2px;
}

.wx .wx-module-ctl .wx-btn { padding: 2px 6px; font-size: 11px; }

.wx .wx-orphans {
    display: grid;
    gap: 6px;
    margin-top: 14px;
    padding: 10px;
    border: 1px solid var(--warn);
    border-radius: 8px;
}

.wx .wx-module.is-orphan { background: var(--surface); }

@media (max-width: 900px) {
    .wx .wx-builder {
        grid-template-columns: minmax(0, 1fr) !important;
        grid-template-areas: none !important;
    }

    .wx .wx-bslot { grid-area: auto !important; }
}

/* ---- add-to-display picker ---------------------------------------------- */

.wx .wx-picks {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));
    gap: 6px;
    margin-top: 10px;
    max-height: 260px;
    overflow-y: auto;
}

.wx .wx-pick {
    display: grid;
    gap: 1px;
    text-align: left;
    padding: 7px 10px;
    border: 1px solid var(--line);
    border-radius: 6px;
    background: var(--surface);
    cursor: pointer;
    min-width: 0;
    font: inherit;
    color: inherit;
}

.wx .wx-pick:hover:not(:disabled) {
    border-color: var(--accent);
    background: var(--accent-soft);
}

.wx .wx-pick:disabled { opacity: .5; cursor: default; }

.wx .wx-pick:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

.wx .wx-pick-name {
    font-size: .84rem;
    font-weight: 600;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.wx .wx-pick-sub {
    font-size: .72rem;
    color: var(--muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* A date that is set but not in force. The status decides whether it counts,
   so the field says "recorded, not applying" rather than disappearing - the
   value is still there and comes back when the status changes.

   On the input as well as the cell, because a disabled field otherwise looks
   unavailable rather than deliberately not in use. */
.wx .wx-dates-off {
    text-decoration: line-through;
    opacity: .55;
}

/* A section held back by one it depends on - see AppNavRules. Dimmed rather than
   hidden, so an operator can see it exists and the note underneath says why it is
   not available yet. */
.wx-check.is-disabled {
  opacity: .5;
  cursor: not-allowed;
}
.wx-check.is-disabled input { cursor: not-allowed; }

/* The half of a policy a partner can read and cannot edit. Set back and boxed so it
   reads as somebody else's text on their page - which is exactly what it is. */
.wx-policy-locked {
    max-height: 320px;
    overflow-y: auto;
    padding: 12px 14px;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: var(--ground);
    color: var(--muted);
    font-size: 0.9rem;
    line-height: 1.5;
}

.wx-policy-locked h1,
.wx-policy-locked h2,
.wx-policy-locked h3 {
    font-size: 1rem;
    margin: 0 0 6px;
    color: var(--ink);
}

/* A field whose value is always built on something fixed - the partner's handle in
   front of a campaign code. Shown rather than explained, so what gets stored is what
   the screen reads. */
.wx-prefixed {
    display: flex;
    align-items: stretch;
    min-width: 0;
}

.wx-prefixed .wx-prefix {
    display: flex;
    align-items: center;
    padding: 0 10px;
    border: 1px solid var(--line);
    border-right: 0;
    border-radius: var(--radius) 0 0 var(--radius);
    background: var(--ground);
    color: var(--muted);
    font-family: var(--f-mono);
    white-space: nowrap;
}

.wx-prefixed .wx-in {
    border-radius: 0 var(--radius) var(--radius) 0;
    min-width: 0;
}

/* The handle itself, read-only, for a partner who cannot change it. */
.wx-handle {
    padding: 10px 12px;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: var(--ground);
    font-family: var(--f-mono);
    font-size: 1.25rem;
    font-weight: 700;
}

/* ── The assistant ───────────────────────────────────────────────────────────
   A panel rather than a page, because asking is something you do beside the
   work rather than instead of it. Off-canvas on a phone, docked on a desktop. */

.wx-assistant {
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    width: min(420px, 100vw);
    display: flex;
    flex-direction: column;
    background: var(--surface);
    border-left: 1px solid var(--line);
    z-index: 60;
    transform: translateX(100%);
    transition: transform 160ms ease;
}

.wx-assistant[data-open="true"] { transform: translateX(0); }

@media (prefers-reduced-motion: reduce) { .wx-assistant { transition: none; } }

.wx-assistant-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 12px 14px;
    border-bottom: 1px solid var(--line);
    font-family: var(--f-cond);
    color: var(--ink);
}

.wx-assistant-log {
    flex: 1;
    overflow-y: auto;
    padding: 14px;
    display: grid;
    gap: 12px;
    align-content: start;
}

.wx-assistant-empty { display: grid; gap: 8px; color: var(--muted); font-size: 0.85rem; }
.wx-assistant-empty p { margin: 0 0 4px; }

.wx-assistant-chip {
    justify-self: start;
    padding: 6px 11px;
    border: 1px solid var(--line);
    border-radius: 999px;
    background: none;
    color: var(--accent);
    font: inherit;
    font-size: 0.8rem;
    cursor: pointer;
    text-align: left;
}

.wx-assistant-msg { display: grid; gap: 6px; font-size: 0.87rem; line-height: 1.5; }

.wx-assistant-msg.is-user .wx-assistant-text {
    justify-self: end;
    max-width: 85%;
    padding: 8px 12px;
    border-radius: var(--radius);
    background: var(--surface-2);
    color: var(--ink);
}

.wx-assistant-link { color: var(--accent); text-decoration: underline; text-underline-offset: 2px; cursor: pointer; }
.wx-assistant-link:hover { text-decoration-thickness: 2px; }

.wx-assistant-msg.is-assistant .wx-assistant-text {
    color: var(--ink);
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

/* Something went wrong, said quietly rather than in red - most of these are
   "you don't have access to that", which is not an error on the reader's part. */
.wx-assistant-msg.is-system .wx-assistant-text {
    color: var(--muted);
    font-size: 0.82rem;
    font-style: italic;
}

.wx-assistant-steps { display: flex; flex-wrap: wrap; gap: 5px; }

.wx-assistant-step {
    padding: 2px 8px;
    border: 1px solid var(--line);
    border-radius: 999px;
    color: var(--muted);
    font-family: var(--f-mono);
    font-size: 0.7rem;
}

/* The one thing in the panel that should stop somebody. */
.wx-assistant-approve {
    padding: 12px;
    border: 1px solid var(--accent);
    border-radius: var(--radius);
    display: grid;
    gap: 10px;
}

.wx-assistant-approve p { margin: 0; font-size: 0.85rem; color: var(--ink); }
.wx-assistant-approve-actions { display: flex; gap: 8px; justify-content: flex-end; }

.wx-assistant-ask {
    display: flex;
    gap: 8px;
    padding: 12px 14px;
    border-top: 1px solid var(--line);
}

/* The microphone: the same quiet icon button as the header's, red while it listens,
   with a slow pulse so "it's on" is visible from across the room. */
.wx-assistant-mic { flex: none; align-self: center; width: 34px; height: 34px; border-radius: 50%; }
.wx-assistant-mic.is-listening { color: #fff; background: var(--crit); animation: wx-mic-pulse 1.4s ease-in-out infinite; }
@keyframes wx-mic-pulse {
    0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--crit) 45%, transparent); }
    50% { box-shadow: 0 0 0 7px color-mix(in srgb, var(--crit) 0%, transparent); }
}
@media (prefers-reduced-motion: reduce) { .wx-assistant-mic.is-listening { animation: none; } }

.wx-assistant-ask input {
    flex: 1;
    min-width: 0;
    padding: 8px 11px;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: var(--surface-2);
    color: var(--ink);
    font: inherit;
    font-size: 0.87rem;
}

/* The confirmation code a postcard entry issues.

   Sized to be copied onto a card by hand without being re-read twice: monospace so
   the groups line up, and wide letter spacing so nothing runs together. */
.wx-code-out {
    font-family: var(--f-mono);
    font-size: 30px;
    font-weight: 700;
    letter-spacing: .18em;
    margin: 2px 0 8px;
    color: var(--ink);
}

/* Wraps the body of a form that can be switched off wholesale.

   A fieldset because disabling it disables every control inside for free, keyboard
   and all - the alternative is remembering to pass disabled to each input, and
   forgetting one leaves a field that looks live and is not. The default fieldset
   chrome is removed; only the dimming is ours. */
.wx-fieldset {
    border: 0;
    margin: 0;
    padding: 0;
    min-width: 0;
}

.wx-fieldset:disabled {
    opacity: .55;
    cursor: not-allowed;
}

/* A note carrying the reason something is refused, rather than ordinary help text. */
.wx-note.is-warn { color: var(--warn); }

/* ---- a return request on an order line ---------------------------------- */

.wx-return-box {
  margin: 0 0 14px;
  padding: 12px 14px;
  border: 1px solid var(--line);
  border-left-width: 4px;
  border-radius: var(--radius);
  background: var(--surface-2);
  display: grid;
  gap: 10px;
}
.wx-return-box.is-waiting  { border-left-color: var(--warn); }
.wx-return-box.is-approved { border-left-color: var(--good); }
.wx-return-box.is-declined { border-left-color: var(--crit); }
.wx-return-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.wx-return-why { margin: 0; font-size: 13px; color: var(--ink); font-style: italic; }
.wx-return-now { display: flex; align-items: center; gap: 8px; font-size: 13px; color: var(--ink); cursor: pointer; }
.wx-return-actions { display: flex; gap: 8px; flex-wrap: wrap; }

/* ---- a support ticket's thread ------------------------------------------ */

.wx-thread { display: grid; gap: 10px; margin-bottom: 18px; }
.wx-msg {
  padding: 10px 14px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface-2);
  max-width: 85%;
}
.wx-msg.is-them { justify-self: start; }
.wx-msg.is-us { justify-self: end; background: var(--info-soft); border-color: transparent; }
.wx-msg.is-internal { background: var(--warn-soft); border-style: dashed; }
.wx-msg-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 4px; font-size: 13px; }
.wx-msg-body { font-size: 14px; line-height: 1.5; white-space: pre-wrap; color: var(--ink); }
.wx-reply { display: grid; gap: 10px; padding-top: 14px; border-top: 1px solid var(--line); }
.wx-reply-actions { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.wx-context {
  display: block;
  white-space: pre-wrap;
  font-size: 12px;
  line-height: 1.5;
  color: var(--ink);
  max-height: 360px;
  overflow: auto;
}

.wx-list { display: grid; }
.wx-list-row {
  display: grid;
  gap: 2px;
  width: 100%;
  padding: 10px 14px;
  border: 0;
  border-bottom: 1px solid var(--line);
  background: none;
  color: var(--ink);
  font: inherit;
  font-size: 13px;
  text-align: left;
  cursor: pointer;
}
.wx-list-row:last-child { border-bottom: 0; }
.wx-list-row:hover { background: var(--surface-2); }

/* ---- the assistant's read of a ticket ----------------------------------- */

.wx-read { display: grid; gap: 12px; }
.wx-read-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.wx-read-summary { margin: 0; font-size: 14px; color: var(--ink); }
.wx-read-draft, .wx-read-action {
  display: grid;
  gap: 8px;
  padding: 12px 14px;
  border: 1px dashed var(--line);
  border-radius: var(--radius);
  background: var(--surface-2);
  font-size: 14px;
}
.wx-read-draft .wx-btn, .wx-read-action .wx-btn { justify-self: start; }

/* The conversation on a ticket: a fixed pane that scrolls, newest at the bottom,
   with the reply box pinned under it rather than a page's length away. */
.wx-thread.is-pane {
  max-height: 46vh;
  min-height: 160px;
  overflow-y: auto;
  padding-right: 6px;
  margin-bottom: 0;
}
.wx-sup-from { width: 200px; max-width: 200px; overflow: hidden; }
.wx-sup-from b, .wx-sup-from .sub { display: block; overflow: hidden; text-overflow: ellipsis; }

/* Pictures on a support message: thumbnails that open full size in a new tab. */
.wx-msg-pics { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 8px; }
.wx-msg-pics img { width: 120px; height: 120px; object-fit: cover; border-radius: var(--radius); border: 1px solid var(--line); display: block; }

/* A sample win card on the automation page. */
.wx-card-preview { margin-top: 12px; max-width: 600px; }
.wx-card-preview img { width: 100%; display: block; border-radius: var(--radius); border: 1px solid var(--line); }
