/* ===== CMS admin — responsive (mobile-first). Phone / tablet / laptop. ===== */
/* ===== Speedee =========================================================== */
/* Three weights with matching italics, plus a condensed family that is available but not
   used by default.

   font-display:swap, deliberately. This is a working interface rather than a screen: text
   that is briefly in the wrong face is better than a page that shows nothing for a second
   while somebody is trying to reorder a running order. The stage preview inside the iframe
   is a separate document and unaffected.

   The name records in these files are stripped, which is normal for licensed webfonts. The
   weight and italic flags survive, and those are what @font-face needs. */

@font-face{font-family:Speedee;src:url("/fonts/speedee/Speedee_W_Lt.woff2") format("woff2");
  font-weight:300;font-style:normal;font-display:swap}
@font-face{font-family:Speedee;src:url("/fonts/speedee/Speedee_W_LtIt.woff2") format("woff2");
  font-weight:300;font-style:italic;font-display:swap}
@font-face{font-family:Speedee;src:url("/fonts/speedee/Speedee_W_Rg.woff2") format("woff2");
  font-weight:400;font-style:normal;font-display:swap}
@font-face{font-family:Speedee;src:url("/fonts/speedee/Speedee_W_It.woff2") format("woff2");
  font-weight:400;font-style:italic;font-display:swap}
@font-face{font-family:Speedee;src:url("/fonts/speedee/Speedee_W_Bd.woff2") format("woff2");
  font-weight:700;font-style:normal;font-display:swap}
@font-face{font-family:Speedee;src:url("/fonts/speedee/Speedee_W_BdIt.woff2") format("woff2");
  font-weight:700;font-style:italic;font-display:swap}

@font-face{font-family:"Speedee Condensed";src:url("/fonts/speedee/SpeedeeCd_W_Lt.woff2") format("woff2");
  font-weight:300;font-style:normal;font-display:swap}
@font-face{font-family:"Speedee Condensed";src:url("/fonts/speedee/SpeedeeCd_W_Rg.woff2") format("woff2");
  font-weight:400;font-style:normal;font-display:swap}
@font-face{font-family:"Speedee Condensed";src:url("/fonts/speedee/SpeedeeCd_W_Bd.woff2") format("woff2");
  font-weight:700;font-style:normal;font-display:swap}

/* The stack has a tail on purpose. Speedee has no arrows, ticks or geometric shapes, so
   those characters come from the system font behind it — see docs/Fonts.md. */
:root{
  --font-ui:Speedee,system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;
  --font-condensed:"Speedee Condensed",var(--font-ui);
}

/* Dark is the default: these screens are usually edited in a back office or on a shop floor,
   and the preview shows a lit 1920x1080 stage that reads better against a dark surround.
   Light is available for bright rooms and for anyone who simply prefers it. */
/* Three accent roles, not one.

   A single --accent works only while it happens to be dark enough for white text and legible
   enough to write with. A light brand colour breaks both at once — yellow on white is about
   1.7:1, and white on yellow is no better. So:

     --accent      a filled surface: a primary button, a ticked chip
     --on-accent   what goes ON that surface, which is not always white
     --accent-ink  the accent used AS text or a hairline, which may need to be darker

   For this palette all three agree; for the gold brand they do not, which is why the split has
   to exist in the base rather than only in the overlay.

   The status pairs are here for the same reason: a chip needs a background and an ink that
   reads on it. Without them .err, .notice and .pill had no colours to reach for, so they were
   either unstyled or carried hard-coded values that only suited the dark scheme. */
:root{
  --bg:#0f1115; --panel:#171a21; --panel2:#12141a; --line:#262b36;
  --text:#e7e9ee; --muted:#9aa1b0; --accent:#e11d55; --ok:#3cb44b; --radius:12px;
  --shadow:0 6px 20px rgba(0,0,0,.35);
  --btn:#20242e; --hover:#20242e; --toolbar:#1b1f27;

  --on-accent:#ffffff; --accent-ink:#ff7a9c;

  /* The edge of a card or a field, kept separate from --line so a scheme can draw a hard
     outline without also darkening every internal rule. On a dark surface a near-black edge
     measures 1.0:1 and simply is not there, so the dark schemes keep a lighter one and take
     their definition from the surface change instead. */
  --edge:var(--line); --edge-w:1px;
  --edge-control:var(--edge); --edge-control-w:var(--edge-w);

  /* The switch track, off and on. Separate from the accent: it is a state, not an action,
     and colouring it with the accent makes it look pressable. */
  --sw-off:#3a3f4a; --sw-on:#5b6373;

  /* The container is soft and the things inside it are crisp — that reads as deliberate,
     where one middling radius everywhere reads as a default nobody chose.
     --radius stays at its own value for now: existing rules read it, and changing what it
     resolves to would move every corner in the interface. */
  --r-card:18px;      /* cards, rows, template tiles, the stage preview */
  --r-control:6px;    /* buttons, fields, thumbnails, toolbars */
  --r-chip:5px;
  --r-pill:999px;     /* badges and filters */

  --ok-bg:rgba(60,180,75,.16);    --ok-ink:#84e08e;
  --warn-bg:rgba(245,130,49,.16); --warn-ink:#ffbf87;
  --bad-bg:rgba(225,29,85,.16);   --bad-ink:#ff8fae;

  /* The nav's hover and current-page marks, as their own tokens rather than --hover.

     --hover is a control colour: it belongs to a button sitting on the page, and on the top
     bar it is simply the wrong surface. Under the gold brand that showed — the bar is crimson
     and --hover is #fdefc9, so the current page was marked in pale yellow on red.

     Declared once. A custom property that references another resolves where it is used, so
     these follow --hover through every scheme without being restated in each. A brand whose
     bar is not the page colour overrides them; see admin-brand-gold.css. */
  --bar-hover:var(--hover);
  --bar-active:var(--hover);

  color-scheme:dark;
}

/* the light palette, kept to the same variable names so nothing else changes.

   The status inks are restated here rather than inherited. They have to be: the dark values
   above are a pale pink and a pale green chosen to sit on near-black, and left in place they
   would be pink-on-white and green-on-white — technically a colour, practically illegible.
   These are the same light values the gold overlay uses, which were measured rather than
   picked by eye. */
:root[data-theme="light"]{
  --bg:#f6f7f9; --panel:#ffffff; --panel2:#ffffff; --line:#dde1e8;
  --text:#1a1d23; --muted:#5b6472; --accent:#c1174a; --ok:#1f7a3a;
  --shadow:0 4px 14px rgba(16,20,30,.10);
  --btn:#eef0f4; --hover:#e8ebf1; --toolbar:#f2f4f7;

  --on-accent:#ffffff;            /* white on #c1174a is 6.1:1 */
  --accent-ink:#a11244;           /* the accent as text on white, 7.4:1 */

  --edge:var(--line); --edge-w:1px;
  --edge-control:var(--edge); --edge-control-w:var(--edge-w);
  --sw-off:#c9cfda; --sw-on:#8b94a5;

  --ok-bg:#e2efe4;   --ok-ink:#1f5c30;
  --warn-bg:#fdeccd; --warn-ink:#7a5a1e;
  --bad-bg:#fbe0dd;  --bad-ink:#96140f;

  color-scheme:light;
}

/* follow the device when the person hasn't chosen */
@media (prefers-color-scheme: light){
  :root:not([data-theme]){
    --bg:#f6f7f9; --panel:#ffffff; --panel2:#ffffff; --line:#dde1e8;
    --text:#1a1d23; --muted:#5b6472; --accent:#c1174a; --ok:#1f7a3a;
    --shadow:0 4px 14px rgba(16,20,30,.10);
    --btn:#eef0f4; --hover:#e8ebf1; --toolbar:#f2f4f7;

    /* Same restatement as the chosen-light block above, and for the same reason. Somebody
       whose device asks for light and who has never touched the toggle gets this one. */
    --on-accent:#ffffff;
    --accent-ink:#a11244;
    --edge:var(--line); --edge-w:1px;
    --edge-control:var(--edge); --edge-control-w:var(--edge-w);
    --sw-off:#c9cfda; --sw-on:#8b94a5;
    --ok-bg:#e2efe4;   --ok-ink:#1f5c30;
    --warn-bg:#fdeccd; --warn-ink:#7a5a1e;
    --bad-bg:#fbe0dd;  --bad-ink:#96140f;

    color-scheme:light;
  }
}
*{box-sizing:border-box}
html,body{margin:0}
body{background:var(--bg);color:var(--text);font-family:var(--font-ui);line-height:1.5}
/* A link is coloured, as in the demo. It was color:inherit, so "Open this screen" sitting in a
   muted paragraph rendered in exactly the grey of the sentence around it — a link nobody could
   see was a link.

   --accent-ink rather than --accent: this is the accent used as text, which is the whole reason
   the two are separate. On the gold brand the fill is a red that works under white and the ink
   is a darker red that can be read on a white card.

   The structural anchors keep inheriting. A row, a template tile, a button and the brand mark
   are all anchors that are not read as links — colouring them would turn a whole card of text
   red. .nav a already sets its own colour against the bar. */
a{color:var(--accent-ink);text-decoration:none}
a:hover{text-decoration:underline}
a.card,a.tpl-card,a.btn,a.brand,.nav a{color:inherit;text-decoration:none}
a.card:hover,a.tpl-card:hover,a.btn:hover,a.brand:hover{text-decoration:none}
.muted{color:var(--muted)}
hr{border:0;border-top:1px solid var(--line);margin:18px 0}

/* top bar + nav */
/* The bar takes --bar where a brand sets one and the panel colour otherwise. Written as a
   fallback rather than as a rule in the overlay, so a brand only has to name the colour. */
.topbar{position:sticky;top:0;z-index:30;display:flex;align-items:center;gap:6px;
  padding:10px 16px;background:var(--bar,var(--panel));
  border-bottom:1px solid var(--bar,var(--line));color:var(--bar-text,var(--text))}
.brand{font-weight:800;font-size:17px;margin-right:14px;white-space:nowrap;
  display:inline-flex;align-items:center;gap:5px}

/* The wordmark.

   Painted with a mask rather than embedded as an <img> or inlined as markup, and the reason is
   colour. The file carries no fill of its own, so as an <img> it draws black — invisible on the
   dark bar and wrong under the gold overlay. Inlining it would let currentColor through, but the
   artwork is 27 KB of path data and inlining puts that in the HTML of every page, uncached, on
   every load. As a mask the file is fetched and cached once, and the colour comes from
   background-color — so it follows the theme, light or dark, brand overlay or not, for free.

   The name beside it is the fallback and the accessible name at once: shown as text where masks
   are unsupported, and read aloud in either case. That is why it is a real word rather than
   alt="" on an image — a masked element has no content of its own to announce. */
/* ---- the confirm dialog ------------------------------------------------- */
/* A <dialog>, so focus trapping, Escape, the backdrop and returning focus come from the browser
   rather than from another hand-rolled overlay. Only the clothes are ours. */
.confirm{border:1px solid var(--line);border-radius:14px;background:var(--panel);color:var(--text);
  padding:0;max-width:440px;width:calc(100% - 32px);box-shadow:0 18px 50px rgba(0,0,0,.45)}
.confirm::backdrop{background:rgba(6,8,11,.6)}
.confirm-body{padding:20px 22px 4px}
.confirm-title{margin:0;font-size:16px;font-weight:700;line-height:1.35}
.confirm-detail{margin:10px 0 0;font-size:13.5px;line-height:1.5}
.confirm-actions{display:flex;justify-content:flex-end;gap:8px;padding:16px 22px 20px}
.confirm-actions form{margin:0}
/* The destructive answer, when a form asks for it. Bordered rather than filled: a solid red
   button beside a plain Cancel reads as the recommended one, which it never is. */
/* The ink follows the theme. Hard-coded, it was the DARK pink in both — unreadable on a
   light card, which is the same fault .flash and .err were fixed for above and the reason
   the status pairs exist at all. The border stays a fixed dark red: it reads on either. */
.btn.danger{background:var(--btn);border-color:#7a2233;color:var(--bad-ink)}

/* For status text a script injects, where there is no element already carrying a class.
   Named after the token rather than the meaning, so it is obvious at the call site that
   the colour follows the theme. */
.bad-ink{color:var(--bad-ink)}
.ok-ink{color:var(--ok-ink)}
.warn-ink{color:var(--warn-ink)}
.btn.danger:hover{border-color:#a32d44}

.brand-name{font:inherit}
.brand-logo{display:none}

/* Tested against `none`, not against the real url().

   `@supports (mask-image: url("…"))` is the obvious way to write this and it answers false in
   browsers that mask perfectly well — the condition is evaluated by the CSS parser, and a url()
   there is not reliably resolved to a yes. The result was the fallback showing everywhere: the
   wordmark never painted and the page said "Store Screens" in plain text instead.

   `none` is unambiguously valid for the property, so the test asks the only thing worth asking —
   does this browser know what mask-image is. */
@supports ((mask-image: none) or (-webkit-mask-image: none)) {
  .brand-logo{
    display:block;
    /* 706:146 from the viewBox. Height drives it, so the bar's rhythm decides the size. */
    height:20px;width:97px;
    background-color:currentColor;
    /* Longhands rather than the `mask:` shorthand. The shorthand's position/size half —
       `center / contain` — is the part browsers disagree about parsing, and a shorthand that
       fails to parse takes the mask-image with it and leaves a solid coloured block. These four
       are understood everywhere the property exists at all. */
    -webkit-mask-image:url("../img/screens-logo.svg");
    mask-image:url("../img/screens-logo.svg");
    -webkit-mask-repeat:no-repeat;  mask-repeat:no-repeat;
    -webkit-mask-position:center;   mask-position:center;
    -webkit-mask-size:contain;      mask-size:contain;
  }
  /* Still in the accessibility tree, just not on screen. */
  .brand-name{position:absolute;width:1px;height:1px;margin:-1px;padding:0;
    overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}
}
.menu-btn{display:none;margin-left:auto;background:none;border:0;
  color:var(--bar-text,var(--text));line-height:1;cursor:pointer;
  min-width:44px;min-height:44px;padding:6px 8px}
/* Name and controls at the far end, in the bar's own muted colour.

   Inside the nav rather than beside it. On a wide screen margin-left:auto puts it exactly where
   it was — hard against the right-hand end — because the nav fills the space between the brand
   and the edge. On a phone the nav becomes the dropdown, and the switch goes down there with the
   links instead of competing for the last inches of the bar, which is where three attempts to
   keep it visible had already failed. */
.whoami{display:flex;align-items:center;gap:8px;font-size:13px;margin-left:auto;
  color:var(--bar-muted,var(--muted))}
/* flex:1 rather than margin-left:auto — the nav takes the space between the brand and the
   controls, and wraps rather than overflowing when somebody has every link. */
.nav{display:flex;gap:4px;flex:1;flex-wrap:wrap}
.nav a{display:flex;align-items:center;min-height:40px;padding:6px 14px;
  border-radius:var(--r-control);color:var(--bar-muted,var(--muted))}
/* Text follows the bar where a brand sets one, and the page text otherwise — the fallback is
   what keeps the unbranded schemes reading correctly without a rule of their own. */
.nav a:hover{background:var(--bar-hover);color:var(--bar-text,var(--text))}
.nav a.active{background:var(--bar-active);color:var(--bar-text,var(--text))}

/* ---- the account menu, under the name -----------------------------------
   Password and Sign out are not places to go. One changes your account and the other ends it,
   and sitting in the row beside Posts, Screens and Settings they read as two more sections of
   the CMS — the one that signs you out being a click away from the one you actually wanted.

   Under the name is where people look for them, and it costs the row two slots.

   WIDE SCREENS ONLY. On a phone the whole nav is already behind the menu button and the name is
   hidden to make room for the theme switch, so there would be nothing to click: the two links
   stay in the nav there, exactly as before. That is the `.acct-link` pair, hidden here and shown
   at the phone breakpoint below.

   The menu is `hidden` in the markup rather than only `display:none` here, so it is shut for a
   browser that gets the HTML and not the stylesheet, and so the attribute is the one thing that
   says whether it is open. */
.acct{position:relative}
.acct-btn{display:inline-flex;align-items:center;gap:6px;min-height:40px;padding:6px 10px;
  background:none;border:0;border-radius:var(--r-control);cursor:pointer;
  font:inherit;font-size:13px;color:var(--bar-muted,var(--muted))}
.acct-btn:hover{background:var(--bar-hover);color:var(--bar-text,var(--text))}
.acct-btn[aria-expanded="true"]{background:var(--bar-active);color:var(--bar-text,var(--text))}
.acct-btn:focus-visible{outline:2px solid var(--bar-text,var(--text));outline-offset:2px}
.acct-caret{width:10px;height:6px;flex:0 0 auto;transition:transform .12s}
.acct-btn[aria-expanded="true"] .acct-caret{transform:rotate(180deg)}

/* Right-aligned to the button: the name is the last thing in the bar, so a menu hanging left
   from its right edge is the only one that stays on the screen. */
.acct-menu{position:absolute;right:0;top:100%;margin-top:6px;z-index:40;min-width:170px;
  display:flex;flex-direction:column;padding:6px;
  background:var(--panel);border:1px solid var(--line);border-radius:10px;
  box-shadow:0 10px 30px rgba(0,0,0,.35)}
.acct-menu[hidden]{display:none}
/* `.acct .acct-menu a`, and the extra class is doing real work.

   This menu is inside `.nav`, so `.nav a` and `.nav a:hover` reach it. Those are written for
   links sitting ON the coloured bar — `color:var(--bar-text)`, which is WHITE where a brand
   paints the bar red — and this menu is a white panel hanging below it. Matched on specificity
   they were settled by source order, so the hover rule below set the background and `.nav a:hover`
   still supplied the colour: white text on a near-white panel, unreadable exactly when somebody
   was pointing at it.

   Outranking both rules outright is what fixes it, rather than relying on this file's order.

   `--hover` rather than `--panel2` for the background as well: in the default light scheme
   `--panel2` IS `--panel`, so the highlight was invisible there even where the text survived.
   `--hover` is the token meant for this and has a real value in every scheme. */
.acct .acct-menu a{display:flex;align-items:center;min-height:40px;padding:6px 12px;
  border-radius:8px;color:var(--text);font-size:14px}
.acct .acct-menu a:hover{background:var(--hover);color:var(--text)}
/* Keyboard users get the same highlight — they cannot hover, and an invisible selection in a
   two-item menu where one item signs you out is worth more than the tidiness of leaving it out. */
.acct .acct-menu a:focus-visible{background:var(--hover);color:var(--text);
  outline:2px solid var(--accent);outline-offset:-2px}

/* The nav's own copies, which the phone uses.

   `.nav a.acct-link` and not `.acct-link`: the base rule is `.nav a`, which carries a class AND
   an element and so outranks a bare class — the links stayed in the bar with the menu beside
   them offering the same two things twice. */
.nav a.acct-link{display:none}

.container{max-width:1120px;margin:0 auto;padding:20px 16px}
.page-head{display:flex;align-items:center;justify-content:space-between;gap:12px;margin:4px 0 18px}
h1{font-size:24px;margin:0}

/* buttons */
/* hidden means hidden.
   
   The browser's own [hidden] rule is `display:none` at user-agent weight, and ANY author
   `display` beats it. `.btn{display:inline-flex}` therefore silently un-hides every button the
   JavaScript hides — which is why an empty media slot has always shown a Remove button that
   removes nothing, and why the editor's "Choose from the library" would have stayed on screen
   after the slot was cleared. Both look like bugs in the script; neither is.
   
   !important because the point is to outrank whatever a component sets. */
[hidden]{display:none !important}

.btn{display:inline-flex;align-items:center;justify-content:center;gap:8px;min-height:44px;
  padding:10px 18px;border-radius:10px;border:1px solid var(--line);background:var(--btn);
  color:var(--text);font-weight:700;cursor:pointer;font-size:15px}
.btn:hover{border-color:#39414f}
.btn.primary{background:var(--accent);border-color:var(--accent);color:var(--on-accent)}

/* list of pages: cards on mobile, row-grid on wider screens */
.list{display:grid;gap:12px}
.card{background:var(--panel);border:1px solid var(--line);border-radius:var(--radius);padding:16px;display:block}
.card:hover{border-color:#39414f}

/* Air between panels stacked down a page.
 *
 * .card carries padding and no margin, so two of them in a row meet at their borders: two
 * rounded corners and a 2px line, which reads as one box with a rule through it rather than as
 * two things. On a page that is a sequence of separate jobs — Figures is three — that is the
 * difference between "three panels" and "a wall of form".
 *
 * OPT-IN rather than `.card + .card`, and that is the whole point of it. `.card` is also the row
 * in every list, and those sit in a `.list.rows` whose own `gap` already does this job — a
 * blanket sibling rule would add a margin on top of that gap and space the users and posts lists
 * twice. Stacked panels are the minority case, so they are the ones that say so.
 *
 * 22px because _AuditPanel already chose it inline for exactly this, and two numbers for one
 * idea is how a layout starts to drift. That inline copy still wins where it sits; it is the
 * same value. */
.card.stacked{margin-top:22px}
.badge{display:inline-block;font-size:12px;padding:3px 10px;border-radius:999px;background:var(--btn);color:var(--muted)}
.badge.pub{background:rgba(60,180,75,.16);color:#84e08e}

/* editor: single column on phone, split when there is room for two.

   display:grid was never declared. The rule below set grid-template-columns at 960px and
   nothing made the form a grid, so the columns were inert and the editor stacked at every
   width — fields, then the preview underneath, on a 27-inch monitor as much as on a phone.

   auto-fit rather than a second breakpoint: it splits as soon as two 320px columns fit, so a
   tablet or a half-width laptop window gets the preview beside the fields instead of waiting
   for an arbitrary 960. Below that it collapses on its own. */
.editor{display:grid;gap:18px}
.editor.split{grid-template-columns:repeat(auto-fit,minmax(320px,1fr))}
/* The title and the buttons belong to the whole screen, not to the left column. Spanning
   every track keeps them full width whatever the grid resolves to. */
.editor > .page-head{grid-column:1/-1;margin-bottom:0}
/* The post's own controls stay put, the way the preview beside them already does.

   Save draft, Submit, Publish, Undo and the "Saved 14:32" note all lived in a strip at the top
   of the page, and a template with a dozen slots is a long way down. Somebody who has just
   finished typing has to scroll back up to save — and the one thing this editor has learned the
   hard way is that work which is not saved is work that a lapsed session takes.

   Under the topbar rather than at 0, because the topbar is sticky too and would be painted over.
   The background is not decoration: without it the fields scroll UNDER transparent buttons.

   `.editor > .page-head` and not `.page-head`, which a dozen other pages use — a list page has
   nothing below the strip worth pinning it for. */
.editor > .page-head{position:sticky;top:78px;z-index:20;background:var(--bg);
  padding-top:10px;padding-bottom:10px}

/* An action offered inside the message that explains it, on its own line under the words.
   .btn is inline-flex, so without this it sits at the end of the last sentence like a full
   stop somebody can press. */

/* THE STRIP ON A PHONE.

   It is sticky, so its height is not lost once when somebody scrolls past — it is lost for as
   long as the page is open. Measured at 390px: 201px of an 844px screen, a third of the display,
   before a single field. Two things were paying for it and neither was a button anybody needed.

   THE HEADING. "Edit post" was squeezed into a two-line column by justify-content:space-between
   and took width the buttons then had to wrap around. The Title field is forty pixels below it
   and says the same thing, in the post's own words.

   THE WORDS ON BACK, UNDO AND REDO. All three carry a glyph already. The word doubles the width
   of a control whose meaning an arrow carries perfectly well, and these are the three least-used
   things in the strip — Undo and Redo are disabled most of the time.

   44px square, not 40: this is the one place in the CMS where a control is reached by thumb on a
   moving bus, and .btn.icon-only's 40px is sized for a mouse on a list page. */
@media (max-width:719px){
  .editor > .page-head > h1{display:none}
  .editor > .page-head .head-icon .btn-label{display:none}
  .editor > .page-head .head-icon{width:44px;min-width:44px;padding-left:0;padding-right:0;gap:0}
}

/* The preview then starts below the strip rather than behind it. `--preview-top` is written by
   admin.js from the strip's measured height, because the buttons wrap onto a second row on a
   narrow window and a number hard-coded here would be wrong exactly when the screen is smallest.

   The fallback CLEARS a one-row strip rather than restoring the old 78px, which would put the
   preview back underneath it. It is only reached if admin.js never ran — in which case there is
   no editor to preview anyway, but a number that hides the picture is not the one to leave in a
   stylesheet. */
.preview-col{position:sticky;top:var(--preview-top,142px);align-self:start}
.preview-head{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:6px}

/* full-screen preview */
#fullPreview{position:fixed;inset:0;z-index:70;background:#000;display:flex;flex-direction:column}
#fullPreview[hidden]{display:none}

/* The running order, watched in place. Same shape as the full-screen preview above: the loop
   fills the space and the controls sit on a bar under it. */
#loop-preview{position:fixed;inset:0;z-index:70;background:#000;display:flex;flex-direction:column}
/* Restated at matching specificity, or the id selector above outranks the hidden attribute and
   the overlay is painted over the page permanently — the fault the screen's empty state had. */
#loop-preview[hidden]{display:none}
#loop-preview .loop-stage{flex:1;min-height:0;display:flex}
#loop-preview iframe{flex:1;width:100%;height:100%;border:0;display:block}
/* Three parts, not one right-aligned row: play on the left, the carousel control in the middle,
   the way out on the right. A grid of `1fr auto 1fr` rather than flex, because the middle group
   has to sit under the centre of the picture above it — with flex it would be shoved off-centre
   by whichever side happened to be wider, and both sides change width as you use them (Pause is
   a letter longer than Play, and the count grows from "1 of 9" to "10 of 12"). */
#loop-preview .loop-bar{display:grid;grid-template-columns:1fr auto 1fr;align-items:center;
  gap:8px;padding:10px 14px;background:var(--panel);border-top:1px solid var(--line)}
#loop-preview .loop-bar-left{display:flex;align-items:center;gap:8px;justify-self:start}
#loop-preview .loop-bar-right{display:flex;align-items:center;gap:8px;justify-self:end}

/* Back, the number, Next — the arrangement every carousel uses, so nobody has to read the
   buttons to know what they do. The number is between them because it is what they move. */
#loop-preview .loop-nav{display:flex;align-items:center;gap:10px;justify-self:center}
/* Tabular figures and a floor on the width, for "10 of 12": without it the arrows shuffle
   sideways the moment the count crosses into two digits, and a control that moves under the
   cursor is one people stop trusting to press. */
#loop-preview .loop-count{font-size:14px;font-weight:700;color:var(--text);
  font-variant-numeric:tabular-nums;min-width:7ch;text-align:center;white-space:nowrap}

/* Square, and bigger than the .btn.sm they sit beside, because the triangle is the whole label
   — there is no word to fall back on. 44px is the minimum every other real target here carries. */
#loop-preview .loop-arrow{width:44px;min-width:44px;height:44px;min-height:44px;padding:0;
  border-radius:10px}
#loop-preview .loop-arrow svg,#loop-preview .loop-play svg{display:block;fill:currentColor}
/* Same height as the arrows, so the bar reads as one row of controls rather than two sizes.
   The marks are drawn at the 18px the shared partials default to and scaled to 20 here: this is
   the only place they carry the job on their own, and 18px beside 13px type is a mark you have
   to look for. */
#loop-preview .loop-play{min-height:44px;padding:0 16px;gap:8px}
#loop-preview .loop-play svg{width:20px;height:20px}
#loop-preview .loop-play span{display:inline-flex;align-items:center}

/* A phone held upright, where four groups will not fit across and stacking them into three rows
   eats the picture they are controls for.

   The bar Close goes and the corner X carries the whole job. That is the one thing on this bar
   that is genuinely duplicated — two controls calling the same closeLoop() — so it is the one
   that can be dropped without losing a way to do anything. It is also the exit that survives
   best on a phone: a 44px target in the corner your thumb already reaches, rather than a word
   at the bottom competing with three other controls.

   Width, not `orientation:portrait`. The thing that decides whether this fits is how much room
   the bar has, and a phone on its side has 650px+ and takes the full bar happily — while a
   portrait monitor is `orientation:portrait` and would wrongly lose its Close.

   The grid keeps all three tracks: the empty right-hand 1fr is what holds the arrows and the
   count centred on the bar rather than centred in whatever is left beside Pause. */
@media (max-width:560px){
  #loop-preview .loop-bar-right{display:none}
  /* 6ch still clears "10 of 12"; the tabular figures are what stop it twitching, not the floor. */
  #loop-preview .loop-count{min-width:6ch}
  #loop-preview .loop-bar{gap:6px;padding:10px}
}
.fp-stage{flex:1;position:relative;overflow:hidden}
#fullPreviewFrame{position:absolute;left:50%;top:50%;width:1920px;height:1080px;border:0;
  transform:translate(-50%,-50%) scale(var(--fps,1));transform-origin:center center}
.fp-bar{display:flex;align-items:center;justify-content:space-between;gap:12px;
  padding:10px 14px;background:var(--panel);border-top:1px solid var(--line);font-size:13px}

/* The way out of a full-screen preview, in two places, because the overlay covers the whole
   window and the exit has to be where the eye already is: the bar along the bottom, and an X in
   the corner where every full-screen viewer puts one.

   Classes now, not the ids they started as. There are five overlays in this CMS — the post
   editor's preview, the running-order loop, the approvals viewer, the library enlargement and
   the template preview — and each had grown its own quiet grey "Close", so the same act looked
   like a different act on every page. One pair of classes is the only version of this that
   stays true: the sixth overlay wears them and is right by default, rather than being right
   until somebody forgets.

   Red fill with white type rather than `.btn.danger` — that class is a quiet dark button with
   pink ink, built for a destructive action sitting in a row of ordinary ones. Nothing here is
   destructive and there is no row; this only has to be found at a glance against a black stage.

   Stated once, outside the light/dark blocks. A solid red carrying white type reads the same on
   both schemes, and #c8102e against #fff measures 5.9:1 — above the 4.5:1 body text is held to,
   at a weight and size well above it. */
.preview-close{background:#c8102e;border-color:#c8102e;color:#fff}
.preview-close:hover{background:#a50d26;border-color:#a50d26}
.preview-close:focus-visible{outline:2px solid #fff;outline-offset:2px}

/* The edge, restated at a weight that survives the brand overlay.

   admin-brand-gold.css re-borders every button at `:root[data-brand="gold"] .btn` — specificity
   (0,3,0), in a file that loads after this one — so the two rules above lose the border and the
   exit ends up red with a gold outline. This did not show up while the button was `#fullPreviewClose`,
   because an id outranks anything the overlay can write; turning it into a class is what exposed it.

   Matched here rather than by adding an exception over there. The overlay's job is to say what a
   brand looks like, and this red is deliberately not a brand colour — it is the same red on gold as
   it would be on any other skin, for the same reason it is the same red in light and dark. */
:root[data-brand] .btn.preview-close{border-color:#c8102e}
:root[data-brand] .btn.preview-close:hover{border-color:#a50d26}

/* Positioned against the overlay itself, each of which is `position:fixed` and so is already
   the containing block. A sibling of the stage, not a child of it: the picture is centred and
   scaled by a transform, so anything placed inside would be scaled with it and drift as the
   fit changes.

   z-index above the stage rather than 1: on the library enlargement the artwork is a plain
   img filling its box, which would otherwise paint over the corner.

   44px square to match the min-height every other button here carries — the corner of a
   full-screen overlay is exactly where a small target is most often missed. */
.preview-x{position:absolute;top:14px;right:14px;z-index:2;
  width:44px;height:44px;padding:0;display:inline-flex;align-items:center;justify-content:center;
  border:1px solid #c8102e;border-radius:50%;background:#c8102e;color:#fff;
  font-size:22px;line-height:1;font-weight:700;cursor:pointer}
.preview-x:hover{background:#a50d26;border-color:#a50d26}
.preview-x:focus-visible{outline:2px solid #fff;outline-offset:2px}
.field{display:flex;flex-direction:column;gap:6px;margin-bottom:14px}
.field label{font-size:13px;color:var(--muted)}
/* A field that shows something instead of asking for it — the post's template, which is chosen
   when the post is made. Sized like the inputs above and below it so it reads as that field's
   value rather than as a sentence somebody dropped into the form. */
.field .static{margin:0;padding:2px 0;font-size:15px}

/* ---- the editor's two halves ------------------------------------------------------------ */
/*
   The boxes above the divider name the post and decide where it plays; the boxes below are the
   words and pictures that reach the wall. Both are `.field` with a `<label>` in the same size and
   colour, and the only thing between them was an `<hr />`.

   Reported as editors confusing the two — which is the reasonable reading of a page whose first
   box is called "Title" and whose template opens with a headline. Typing the headline there puts
   it in the posts list and nowhere on the poster, and nothing on the page says so.

   A heading and a rail, not a colour change to the fields themselves: the fields below are still
   ordinary fields and should still look like the ones above, or every template's slots start
   looking like a special case. What changes is that the group is named and has an edge.

   The heading carried an explanatory line under it and it has been taken out. Four words that
   name the group do the work; a sentence restating them is noise in a column somebody is trying
   to type down, and the Title box carries its own hint where the actual mistake is made.
*/
.edit-section{margin:22px 0 12px;padding-top:18px;border-top:1px solid var(--line)}
.edit-section h2{margin:0;font-size:15px;color:var(--text)}
/* The rail runs beside the slots so the eye can see where the poster's own fields begin and end
   without going back to read the heading. `--accent-ink` rather than `--accent`: this is a
   hairline being used as ink, which is the distinction that role exists for, and it stays legible
   in the light and gold schemes where the filled accent would not. */
#slot-fields{border-left:2px solid var(--accent-ink);padding-left:14px}
/* Nothing to draw a rail beside until the template's slots arrive. */
#slot-fields:empty{border-left:0;padding-left:0}
/* Every input, not only the ones inside a .field.

   This rule used to be `.field input, .field select, .field textarea`, so anything outside a
   field — every search box on every list page, most obviously — fell through to the browser's
   own styling and looked like it belonged to a different application.

   Checkboxes and radios are excluded rather than left to a later override: width:100% turns a
   checkbox into a full-width bar, and only the ones inside .stores .chk had a rule to save
   them. An exclusion here does not depend on every checkbox remembering to wear a class.

   font-size stays at 16px where the demo uses 15. Below 16 iOS zooms the page when a field
   takes focus, and the manual is clear that most people are on a handset — the zoom is worse
   than the point of difference. */
input:not([type="checkbox"]):not([type="radio"]),select,textarea{
  background:var(--panel2);color:var(--text);
  border:var(--edge-control-w) solid var(--edge-control);
  border-radius:var(--r-control);
  padding:9px 12px;font:inherit;font-size:16px;min-height:44px;width:100%}
/* Except beside a heading, where full width sends a two-item store picker across the page.
   Scoped to these two wrappers rather than lifted off the base rule: the toolbar inputs on
   Library, Templates and Devices are sized by their flex row and would collapse to the
   browser's default twenty characters, which is narrower than their own placeholders. */
.page-head select,.inline-form select{width:auto}

/* Placeholders, said once for every box on the site.

   There was no rule at all, so they took the browser's default — a grey chosen against a white
   page, with Firefox applying 54% opacity on top of it. On these panels that is close to
   invisible, and several boxes here use the placeholder to say what the field DOES when left
   empty rather than merely to give an example. The seconds box on the running order is the
   clearest: empty means "use the screen's own setting", and the placeholder is the only thing
   that says so.

   opacity:1 is not redundant — without it Firefox fades whatever colour is set. */
::placeholder{color:var(--muted);opacity:1}
textarea{min-height:96px;resize:vertical}
/* One size for a tick box wherever it appears, not only in the store picker. */
/* And one arrangement. The box and its caption were only held together on the user editor, by an
   inline style repeated there and nowhere else; the other five .chk labels left them as inline
   content, so an 18px square sat on the text baseline instead of centred against it. Said here,
   the inline copy becomes redundant rather than load-bearing. */
.chk{display:inline-flex;align-items:center;gap:8px}
.chk input{width:18px;min-width:18px;height:18px;min-height:18px;accent-color:var(--accent)}
/* The focus ring follows the inputs out of .field. The demo has no rule for this at all and
   takes the browser's default, which is the one place its styling is worse — a visible,
   consistent focus ring is how anybody working down a form by keyboard knows where they are. */
input:not([type="checkbox"]):not([type="radio"]):focus,select:focus,textarea:focus{
  outline:2px solid var(--accent);border-color:var(--accent)}

/* show/hide on a password box — see wwwroot/js/password-reveal.js, which builds the markup.
   The wrapper is a flex item in .field's column, so it has to fill the width the input used to. */
.pw-wrap{position:relative;display:block;width:100%}
.pw-wrap input{width:100%;padding-right:46px}   /* room for the button, so text never runs under it */
.pw-toggle{
  position:absolute;top:0;right:0;height:100%;width:44px;                 /* 44px: a finger */
  display:flex;align-items:center;justify-content:center;
  background:none;border:0;padding:0;cursor:pointer;color:var(--muted)}
.pw-toggle:hover{color:var(--text)}
.pw-toggle:focus-visible{outline:2px solid var(--accent);border-radius:8px}

.preview-col label{display:block;margin-bottom:6px}
.preview-wrap{background:#000;border:1px solid var(--line);border-radius:var(--radius);overflow:hidden;aspect-ratio:16/9}
.preview-wrap iframe{width:100%;height:100%;border:0;display:block}

/* Quill fits the dark theme */
.ql-snow.ql-toolbar.cms-toolbar,.ql-container.ql-snow{border-color:var(--line)}
.ql-container{background:var(--panel2);border-bottom-left-radius:10px;border-bottom-right-radius:10px}
.ql-snow.ql-toolbar.cms-toolbar{background:var(--toolbar);border-top-left-radius:10px;border-top-right-radius:10px}
.ql-editor{min-height:150px;color:var(--text)}
/* The same paragraph gap the stage gives this copy — see the note in stage.css.

   Quill's own stylesheet sets `.ql-editor p{margin:0}`, so without this the box an editor types
   into runs its paragraphs together while the poster that comes out of it spaces them. That is
   the disagreement the stage rule's note has always warned about, only pointing the other way:
   the fault it was written for was the wall spacing copy the editor did not, and the fix for
   that is what has just been narrowed.

   Same value and same unit as stage.css, deliberately. If you change one, change both. */
.ql-editor p,
.ql-editor ol,
.ql-editor ul,
.ql-editor blockquote{margin:0 0 .5em}
.ql-editor>:last-child{margin-bottom:0}
/* The template's font and weight pickers, sitting in Quill's toolbar.

   Without this they inherit `.field select`, which is sized for a form field — 44px tall with
   12px padding — and a 44px control in a 40px toolbar makes the strip grow and the buttons
   sit off-centre. Sized to the toolbar instead, and auto width so a long typeface name is not
   truncated to fit a column that is not there. */
.ql-toolbar select{
  width:auto;min-height:0;height:26px;padding:2px 6px;font-size:12.5px;
  vertical-align:middle;background:var(--panel2);color:var(--text);
  border:1px solid var(--line);border-radius:var(--r-chip)}
.ql-toolbar .ql-formats select+select{margin-left:4px}
.ql-snow .ql-stroke{stroke:var(--muted)} .ql-snow .ql-fill{fill:var(--muted)}

/* ---- the rich-text toolbar's own icons -------------------------------------
   Quill's defaults are replaced in admin.js (see toolbarIcons): every button is a 46x31 SVG,
   an 18x18 drawing with its word beneath. These rules are the colours and the sizing; the
   drawings are in the script, because they have to exist before Quill builds the buttons.

   .tbi-* rather than Quill's .ql-stroke / .ql-fill on purpose. Those two are styled by the rule
   above AND by Quill's own stylesheet, so reusing them would mean fighting both for a shape
   that is no longer theirs. */
.ql-snow.ql-toolbar.cms-toolbar button{width:46px;height:31px;padding:0;display:inline-flex;align-items:center;
  justify-content:center;border-radius:6px}
.ql-snow.ql-toolbar.cms-toolbar button svg{width:46px;height:31px;float:none}
.tbi-st{stroke:var(--text);stroke-width:1.9;fill:none;stroke-linecap:round}
.tbi-fl{fill:var(--text)}
.tbi-gl{font:700 13px/1 system-ui;fill:var(--text)}
.tbi-it{font-style:italic}
/* The list numerals are NOT the B/I size: at 13px in an 18px box the 1, 2 and 3 collide with
   each other and with their own rules, and the icon reads as a scribble. */
.tbi-num{font:700 6.2px/1 system-ui;fill:var(--text)}
.tbi-lbl{font:600 7.5px/1 system-ui;letter-spacing:.04em;fill:var(--muted);text-anchor:middle}
/* Quill marks the button, not the svg. */
.ql-snow.ql-toolbar.cms-toolbar button.ql-active .tbi-st{stroke:var(--accent)}
.ql-snow.ql-toolbar.cms-toolbar button.ql-active .tbi-fl,
.ql-snow.ql-toolbar.cms-toolbar button.ql-active .tbi-gl,
.ql-snow.ql-toolbar.cms-toolbar button.ql-active .tbi-num,
.ql-snow.ql-toolbar.cms-toolbar button.ql-active .tbi-lbl{fill:var(--accent)}
.ql-snow.ql-toolbar.cms-toolbar button.ql-active{background:var(--btn)}
.ql-snow.ql-toolbar.cms-toolbar button:hover{background:var(--hover)}

/* The emoji toggle is ours, not Quill's, but it sits in the same strip and is drawn the same
   way — so it is sized like a button rather than like the text control it used to be. */
.emoji-toggle{width:46px;height:31px;padding:0;display:inline-flex;align-items:center;
  justify-content:center;border:0;background:transparent}
.emoji-toggle[aria-expanded="true"]{background:var(--btn);border-color:transparent}
.emoji-toggle[aria-expanded="true"] .tbi-lbl{fill:var(--accent)}

/* ---- one even grid, at every width -----------------------------------------
   Not a phone rule, and that is the finding rather than the design. The editor's field is a
   narrow column, not the page: measured at 519px on a 1280 desktop and 376px on a tablet. Ten
   labelled buttons are 460px before a single gap, so the strip wraps EVERYWHERE, and a width
   breakpoint would be answering the wrong question — the viewport is not what runs out.

   So the toolbar is a five-column grid always: exactly two full rows, the same shape on every
   screen, and no ragged edge to tidy at any width. The group dividers go with it; they are rules
   BETWEEN groups and they only work on a single line. At this size the labels already say what
   each control is, which is the job the grouping was doing by hint. */
.ql-snow.ql-toolbar.cms-toolbar{display:grid;grid-template-columns:repeat(5,1fr);gap:2px;
  padding:6px}
.ql-snow.ql-toolbar.cms-toolbar .ql-formats{display:contents}
.ql-snow.ql-toolbar.cms-toolbar .ql-formats + .ql-formats{margin:0;padding:0;border:0}
/* The CELLS are even, the BUTTONS are not stretched to fill them. Five 1fr columns in a 519px
   field is a 100px cell, and a 46px drawing centred in one reads as a sparse grid rather than a
   toolbar. Capped and centred: the spacing stays regular, the controls stay the size they were
   drawn at. */
.ql-snow.ql-toolbar.cms-toolbar button,
.ql-snow.ql-toolbar.cms-toolbar .emoji-toggle{width:100%;max-width:62px;margin:0 auto}

/* THE ORDER IS THE POINT OF THE LAYOUT, not a detail of it. Left to the toolbar's own order,
   five columns puts LEFT at the end of row one and the other three alignments on row two —
   splitting the one group that most needs reading together, which is a worse fault than the
   dividers this replaces. Row one is the two that change letters and the two that make lists,
   with Emoji on the end; row two is the four alignments, with Clear on the end. The two odd
   ones out bookend, and every group stays whole.

   THE ROW IS NAMED, THE COLUMN IS NOT, and both halves of that matter. Naming the row is what
   makes the split deterministic: left to auto-placement the emoji button ended up on a line of
   its own and pushed the alignments to a third, because an explicit column makes the placement
   cursor move on and it never goes back. Leaving the COLUMN to the grid is what lets a missing
   Italic — it is offered only when the template allows it — close the gap instead of leaving a
   hole between BOLD and NUMBER. Row one simply becomes four wide. */
.ql-snow.ql-toolbar.cms-toolbar .ql-bold{grid-row:1;order:1}
.ql-snow.ql-toolbar.cms-toolbar .ql-italic{grid-row:1;order:2}
.ql-snow.ql-toolbar.cms-toolbar .ql-list[value="ordered"]{grid-row:1;order:3}
.ql-snow.ql-toolbar.cms-toolbar .ql-list[value="bullet"]{grid-row:1;order:4}
.ql-snow.ql-toolbar.cms-toolbar .emoji-toggle{grid-row:1;order:5}
.ql-snow.ql-toolbar.cms-toolbar .ql-align[value=""]{grid-row:2;order:6}
.ql-snow.ql-toolbar.cms-toolbar .ql-align[value="center"]{grid-row:2;order:7}
.ql-snow.ql-toolbar.cms-toolbar .ql-align[value="right"]{grid-row:2;order:8}
.ql-snow.ql-toolbar.cms-toolbar .ql-align[value="justify"]{grid-row:2;order:9}
.ql-snow.ql-toolbar.cms-toolbar .ql-clean{grid-row:2;order:10}

/* The template's font and weight, when it offers them: a full-width row of their own rather
   than two half-width selects trailing off the end of the grid. */
.ql-snow.ql-toolbar.cms-toolbar select{width:100%;max-width:none}
.ql-snow.ql-toolbar.cms-toolbar .ql-formats:has(select){display:flex;grid-column:1/-1;gap:6px;
  order:11;margin-top:3px}

@media (min-width:960px){
  .editor.split{grid-template-columns:1fr 1fr}
  .editor.split .preview-col{position:sticky;top:var(--preview-top,142px);align-self:start}
}
@media (max-width:719px){
  /* the menu collapses behind a button */
  .menu-btn{display:block}
  /* The dropdown carries the bar's own colour, so it reads as the bar extending downwards
     rather than a panel falling out of it — under the gold brand that is the difference
     between a white sheet on crimson and one continuous band.

     Positioned against the header rather than at a fixed offset from the top of the window.
     The demo uses top:52px, which is its bar; ours is a different height and changes with the
     controls in it, so top:100% keeps the two flush whatever that height turns out to be.
     .topbar is sticky, which makes it the containing block for an absolute child. */
  .nav{position:absolute;left:0;right:0;top:100%;flex-direction:column;gap:2px;padding:8px;
    background:var(--bar,var(--panel));border-bottom:1px solid var(--line);
    display:none;z-index:30}
  .nav.open{display:flex}
  .nav a{min-height:48px}
  .hide-sm{display:none}

  /* Room for the bar's controls once the links are behind the button: the name is the first
     thing to go, since the person reading it knows who they are. */
  .whoami span{display:none}

  /* And with the name gone there is nothing to open a menu from, so the account links go back
     into the nav — which on a phone is already a dropdown of its own, behind the menu button.
     This is the arrangement that was here before the wide-screen menu existed, unchanged. */
  .nav a.acct-link{display:flex}
  .acct{display:none}

  /* The controls keep their place; the brand gives way.

     .topbar is a flex row that does not wrap and .brand is white-space:nowrap, so anything wide
     at the left end pushes the right end past the edge of the screen — and the right end is the
     light/dark switch. That is what happened when the wordmark fell back to the words "Store
     Screens": far wider than the ◆ CMS it replaced, and the switch went off the side of the
     phone with nothing to say it was there.

     Fixed at the layout rather than by trimming the brand, because the next thing put in that
     bar would do it again. The switch is the last thing that should be sacrificed for room: a
     phone used on a shop floor in daylight is exactly where somebody needs to change it. */
  .whoami{flex:0 0 auto;margin-left:0}
  .menu-btn{flex:0 0 auto}
  .brand{min-width:0;overflow:hidden;margin-right:8px}
  .brand-name{overflow:hidden;text-overflow:ellipsis}

  /* The switch moves into the menu.

     Three attempts to make room for it in the bar all failed, and the third made things worse:
     ordering it first pushed the brand across the screen, which is what finally showed that the
     element was not being crowded out at all — it was rendering with no width. Whatever the
     reason for that, a 30px control competing for the last inches of a phone's top bar is a
     poor place for it.

     Inside the dropdown it has a whole row, a label beside it, and a touch target the size of
     the menu items around it. Nothing has to fit. */
  .nav .whoami{display:flex;width:100%;justify-content:space-between;align-items:center;
    min-height:48px;padding:0 14px;margin:2px 0 0;border-top:1px solid var(--line)}
  /* The name is hidden in the bar for room; in the menu there is room, and it is the one place
     somebody can check which account they are signed in as. */
  .nav .whoami span{display:block}
  .container{padding:14px 12px 48px}
}

/* store-target checkboxes in the editor */
.stores{display:flex;flex-wrap:wrap;gap:8px}
.stores .chk{display:inline-flex;align-items:center;gap:8px;min-height:40px;padding:8px 12px;
  background:var(--panel2);border:1px solid var(--line);border-radius:10px;font-size:14px;cursor:pointer}
.stores .chk input{width:18px;height:18px}

/* The two date boxes, side by side where there is room and stacked on a phone. Each is its own
   label so "From" and "Until" stay attached to their box when the row wraps. */
.show-dates{display:flex;flex-wrap:wrap;gap:12px}
.show-dates .show-day{display:flex;flex-direction:column;gap:4px;flex:1 1 150px;max-width:220px;
  font-size:13px;color:var(--muted)}

/* "Schedule this post" at its own size, on the left. A .field is a flex column, so a child
   stretches to the full width — which made an optional button look like one more box that had
   to be filled in, the exact thing it exists to stop the dates looking like. */
#schedule-open{align-self:flex-start}

/* The schedule on a Scheduled-tab row, and the dates chip in an On screen row. Full text colour
   on the tab, because there the schedule is the thing being read rather than a footnote. */
.sched-line{color:var(--text);font-weight:600;margin-top:2px}
.sched-chip{display:inline-block;margin-left:6px;padding:1px 8px;border-radius:999px;font-size:12px;
  background:var(--panel2);border:1px solid var(--line);color:var(--text);white-space:nowrap}

.preview-col{position:sticky;top:var(--preview-top,142px);align-self:start}

/* media fields */
.media-row{display:flex;gap:12px;align-items:flex-start}
.media-side{display:flex;flex-direction:column;gap:6px;padding-top:2px}
.media-thumb{position:relative;width:180px;height:101px;flex:0 0 auto;padding:0;cursor:pointer;
  border:1px solid var(--line);border-radius:10px;overflow:hidden;background:#0d1015}
.media-thumb:hover{border-color:var(--accent)}
.media-thumb img,.media-thumb video{display:block;width:100%;height:100%;object-fit:cover}
/* Fill or fit, beside the picture it applies to. Narrower than a full-width field control: it
   sits in the column next to a 96px thumbnail, and a select stretched across the editor would
   read as more important than the picture. */
.media-fit{width:auto;max-width:100%;font-size:13px;min-height:36px;padding:5px 8px}
/* the same checkerboard the template builder uses, so both show the same thing */
.media-thumb.is-empty{background-color:#4a4e56;background-image:
  linear-gradient(45deg,#5d626b 25%,transparent 25%),
  linear-gradient(-45deg,#5d626b 25%,transparent 25%),
  linear-gradient(45deg,transparent 75%,#5d626b 75%),
  linear-gradient(-45deg,transparent 75%,#5d626b 75%);
  background-size:20px 20px;background-position:0 0,0 10px,10px -10px,-10px 0}
.media-thumb-hint{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;
  font:600 12.5px system-ui;color:rgba(255,255,255,.82);text-shadow:0 1px 3px rgba(0,0,0,.6)}
.media-thumb-swap{position:absolute;right:6px;bottom:6px;font:11.5px system-ui;color:#fff;
  background:rgba(0,0,0,.6);border-radius:6px;padding:2px 7px;opacity:0;transition:opacity .12s}
.media-thumb:hover .media-thumb-swap{opacity:1}
/* Tokens, not literals. These two were written before --warn-*/--bad-* existed and kept the
   DARK values hard-coded, so they never took the light ones: #ffbf87 on rgba(245,130,49,.16) is
   pale orange ink on a pale orange ground, which on the light brand is a badge you can see and
   cannot read. `.pill.warn` two hundred lines below has been on the tokens all along, so the
   same status has been rendering two different ways depending on which component drew it.
   Caught when "Pending" arrived on the users list; it fixes the posts list's Pending badge too,
   which had the same fault and the same word.

   `.badge.pub` above is the last one still holding literals. Left alone deliberately — it is
   legible, nobody asked, and changing the colour of every published post is not a thing to do
   on the way past. */
.badge.warn{background:var(--warn-bg);color:var(--warn-ink)}
.badge.rej{background:var(--bad-bg);color:var(--bad-ink)}

/* emoji picker for body copy — big enough to tap, quiet enough to ignore */
/* .emoji-bar is gone: the toggle lives in the editor's toolbar now, so there is no separate
   strip for it to sit in. */
.emoji-toggle{font-size:12.5px;line-height:1;height:26px;padding:2px 8px;cursor:pointer;
  background:var(--panel2);color:var(--text);border:1px solid var(--line);
  border-radius:var(--r-chip)}
.emoji-toggle:hover{background:var(--hover)}
.emoji-toggle[aria-expanded="true"]{background:var(--accent);border-color:var(--accent);
  color:var(--on-accent)}
/* Opened from the toolbar, so it drops between the toolbar and the text rather than hanging
   below the whole field. Square top corners to sit flush under the strip it came from. */
.emoji-panel{margin-top:0;padding:10px;background:var(--panel2);
  border:1px solid var(--line);border-top:0;border-radius:0;
  max-height:230px;overflow:auto}
.emoji-group-name{font-size:11px;letter-spacing:.5px;text-transform:uppercase;color:var(--muted);
  margin:8px 0 4px}
.emoji-group-name:first-child{margin-top:0}
.emoji-row{display:flex;flex-wrap:wrap;gap:4px}
.emoji-btn{width:44px;height:44px;font-size:22px;line-height:1;background:none;border:1px solid transparent;
  border-radius:8px;cursor:pointer;color:inherit}
.emoji-btn:hover,.emoji-btn:focus{background:var(--hover);border-color:var(--line);outline:none}

/* theme toggle in the top bar */
/* ===== the light/dark switch ==============================================================
   Shaped after the one on a phone: a pill track, a round knob that slides the width of it, and
   the two icons living beside it rather than swapping on press.

   Replaces a ◐ button, which said nothing about which way it would go. A switch has a
   position, so it reads as a state rather than an action. */
.switch{display:inline-flex;align-items:center;gap:9px;height:30px;padding:0;border:0;
  background:none;cursor:pointer;flex:0 0 auto;
  color:var(--bar-text,var(--text))}          /* currentColor for the icon */
.switch:focus-visible{outline:2px solid var(--accent);outline-offset:3px}
.switch-track{position:relative;display:block;width:52px;height:30px;border-radius:var(--r-pill);
  background:var(--sw-off);border:1px solid var(--edge);
  transition:background .32s cubic-bezier(.32,.72,0,1)}
.switch[aria-checked="true"] .switch-track{background:var(--sw-on)}

.switch-knob{position:absolute;top:2px;left:2px;width:24px;height:24px;border-radius:50%;
  background:#fff;box-shadow:0 1px 3px rgba(0,0,0,.35);
  /* The overshoot is what makes it feel like a physical switch rather than a fade:
     quick away, settling at the end. */
  transition:transform .32s cubic-bezier(.32,.72,0,1)}
.switch[aria-checked="true"] .switch-knob{transform:translateX(22px)}

/* The icon sits beside the track, in whatever colour the surrounding text is. Inside the
   track it always fought the knob — 24px of a 52px rail, so one end of the travel covered it
   wherever it sat. Outside there is nothing to dodge, and taking the text colour makes it read
   as a label for the switch rather than part of it. */
.switch-mark{position:relative;width:18px;height:18px;flex:0 0 auto}
.switch-ico{position:absolute;inset:0;width:18px;height:18px;
  fill:none;stroke:currentColor;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.6;
  transition:opacity .26s ease,transform .3s cubic-bezier(.32,.72,0,1)}
.switch-ico.moon{opacity:0;transform:scale(.55) rotate(-60deg)}
.switch[aria-checked="true"] .switch-ico.sun{opacity:0;transform:scale(.55) rotate(60deg)}
.switch[aria-checked="true"] .switch-ico.moon{opacity:1;transform:scale(1) rotate(0)}

@media (prefers-reduced-motion: reduce){
  .switch-track,.switch-knob,.switch-ico{transition:none !important}
}

.theme-btn{margin-left:8px;background:var(--btn);border:1px solid var(--line);color:var(--text);
  border-radius:10px;min-width:44px;min-height:44px;font-size:16px;cursor:pointer;line-height:1}
.theme-btn:hover{background:var(--hover)}

/* template chooser: pick by sight, not by name */
.tpl-grid{display:grid;grid-template-columns:1fr;gap:18px;margin-top:16px}
@media (min-width:720px){ .tpl-grid{grid-template-columns:repeat(2,1fr)} }
@media (min-width:1200px){ .tpl-grid{grid-template-columns:repeat(3,1fr)} }
.tpl-card{display:block;background:var(--panel);border:1px solid var(--line);border-radius:var(--radius);
  overflow:hidden;transition:border-color .15s,transform .15s}
.tpl-card:hover,.tpl-card:focus{border-color:var(--accent);transform:translateY(-2px);outline:none}
.tpl-shot{position:relative;aspect-ratio:16/9;background:#000;border-bottom:1px solid var(--line)}
.tpl-frame{position:absolute;inset:0;width:100%;height:100%;border:0;pointer-events:none}
/* The baked picture a card rests on, and the preview that covers it once one is asked for. The
   picture stays in place underneath rather than being removed: the preview is a document that
   takes a moment to draw, and a black box in the meantime is worse than the picture it replaces. */
.tpl-still{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;border:0}
.tpl-shot.has-still .tpl-frame{opacity:0;transition:opacity .12s linear}
.tpl-shot.has-still.is-playing .tpl-frame{opacity:1}
/* The clip, where one was baked. It sits over the picture and carries it as its poster, so the
   swap is invisible: the same frame, from a file that is already decoded. */
.tpl-clip{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;border:0;
  opacity:0;transition:opacity .12s linear;pointer-events:none}
.tpl-shot.has-still.is-playing .tpl-clip{opacity:1}
/* Touch only: a pointer device has hover, and asking for a press as well would be a second way
   to do what moving the mouse already does.

   NAMED `tpl-motion`, AND SCOPED TO THE TILE. It was `.tpl-play`, which the template management
   page has used for its own Play button since before these tiles existed — a real button, with a
   triangle and a word. These are global rules in a stylesheet that page also loads, so the rule
   below hid the webmaster's button on every desktop. Two meanings for one class name in one
   stylesheet is the fault; a longer name and a parent selector is the fix. */
.tpl-shot .tpl-motion{position:absolute;right:8px;bottom:8px;z-index:2;min-height:36px;
  padding:7px 14px;border:0;border-radius:999px;background:rgba(0,0,0,.62);color:#fff;font:inherit;
  font-size:12.5px;font-weight:700;cursor:pointer}
@media (hover: hover) and (pointer: fine){.tpl-shot .tpl-motion{display:none}}
/* Somebody who has asked for less motion gets the picture and a control to ask anyway, on every
   device — the hover that would otherwise start one unbidden is switched off. */
@media (prefers-reduced-motion: reduce){
  .tpl-shot .tpl-motion{display:block}
  .tpl-shot.has-still .tpl-frame{transition:none}
}
.tpl-meta{padding:12px 14px}

/* ---- a template's picture in a LIST ------------------------------------------
   Two pages draw one: the template management page and the restrictions page. It lived
   in the management page's own <style> until the second one wanted it, which is the
   point at which a page-local rule becomes a shared one rather than a copied one.
   The tile classes it sits with — .tpl-shot, .tpl-still, .tpl-clip, .tpl-motion — have
   been here all along. */
  /* The baked picture, at the size a list can carry: the same picture the chooser rests its
     cards on, so a webmaster deciding what to retire sees what a manager choosing sees.

     Beside the name rather than in a column of its own, because the row is already a four-column
     grid at width and a fifth column would have to be threaded through every breakpoint. */
  .tpl-rowhead { display: flex; gap: 12px; align-items: flex-start; min-width: 0; }
  .tpl-thumb {
    position: relative; flex: 0 0 auto; width: 132px; aspect-ratio: 16 / 9;
    border-radius: 6px; overflow: hidden; background: #0b0b0f;
    border: 1px solid var(--line);
  }
  /* A row with nothing baked keeps the space rather than reflowing the list around it: the
     names stay in one column whether or not a picture exists, and "no picture yet" is a state
     worth being able to see at a glance down the page. */
  .tpl-thumb.is-empty {
    display: flex; align-items: center; justify-content: center;
    background: var(--btn); color: var(--muted); font-size: 11px; text-align: center;
    padding: 4px;
  }
  @media (max-width: 720px) { .tpl-thumb { width: 96px; } }

.tpl-name{font-weight:700;font-size:15px;margin-bottom:6px}
.tpl-fields{display:flex;flex-wrap:wrap;gap:6px}
.chip{font-size:11.5px;padding:3px 9px;border-radius:999px;background:var(--btn);color:var(--muted)}
.tpl-use{display:block;padding:10px 14px;border-top:1px solid var(--line);color:var(--accent-ink);
  font-weight:700;font-size:13px;min-height:44px;line-height:24px}

/* category filter + label on the template cards */
.tpl-filters{display:flex;flex-wrap:wrap;gap:8px;margin:14px 0 4px}
.chip-btn{min-height:40px;padding:8px 16px;border-radius:999px;border:1px solid var(--line);
  background:var(--btn);color:var(--muted);font-size:13px;cursor:pointer}
.chip-btn:hover{color:var(--text)}
.chip-btn.is-on{background:var(--accent);border-color:var(--accent);color:var(--on-accent);font-weight:700}
/* The count follows the label. .muted sets its own colour, so on a selected tab the number
   stayed grey against the accent while the text beside it turned white — which reads as the
   number belonging to something else. */
.chip-btn.is-on .muted{color:currentColor;opacity:.75}

/* The restaurant picker on the user editor.

   Scoped under .chips rather than restyling .chip, which is already the small grey pill listing
   a template's fields. These are the same shape but a different job: each one is something a
   person put there and can take away again, so they are readable size and carry a cross. */
.chips{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:8px}
.chips .chip{display:inline-flex;align-items:center;gap:6px;font-size:13px;padding:6px 8px 6px 12px;
  background:var(--btn);color:var(--text);border:1px solid var(--line)}
.chips .chip b{font-weight:700}
/* Locked ones read as a statement, not a control — no cross, and dimmed so the eye goes to the
   set that can actually be changed. */
.chips .chip.locked{color:var(--muted);border-style:dashed;padding-right:12px}
.chip-x{border:0;background:none;color:var(--muted);cursor:pointer;font-size:16px;line-height:1;
  padding:2px 4px;border-radius:6px}
.chip-x:hover{color:var(--text);background:var(--panel2)}

/* Search results. Capped in height and scrolled: fifty rows would otherwise push the Save button
   off the bottom of the page every time somebody typed a digit. */
.store-results{max-height:240px;overflow-y:auto;border:1px solid var(--line);
  border-radius:var(--r-control);margin-top:8px;background:var(--panel2)}
.store-row{display:block;width:100%;text-align:left;border:0;background:none;color:var(--text);
  font:inherit;font-size:14px;padding:10px 12px;min-height:44px;cursor:pointer;
  border-bottom:1px solid var(--line)}
.store-row:last-child{border-bottom:0}
.store-row:hover{background:var(--btn)}
/* Already chosen: shown rather than removed from the list, so a search for "89" does not appear
   to be missing the one you just picked. */
.store-row.is-on{color:var(--muted);cursor:default}
.store-row.is-on::after{content:" — already assigned";font-size:12px}
.store-row.is-on:hover{background:none}

/* Status tabs. Underline for the selection, a badge for the count.

   Not .chip-btn: a chip is something you toggle, a tab is somewhere you are. Making them look
   the same is why these read as a row of buttons. */
/* A list that scrolls between a pinned header and a pinned pager.

   The point is that the controls stay reachable. On a hundred rows, a pager at the bottom of
   the document means scrolling to the end to change page and back to the top to change filter,
   which is most of a screen's worth of scrolling to do two things.

   Sticky rather than fixed: fixed would take the bars out of the flow and they would sit over
   the page at whatever width the viewport happens to be, including on top of the navigation. */
.list-head{position:sticky;top:0;z-index:3;
  /* Opaque, or a hundred rows scroll visibly through the tabs. The page background rather
     than the panel, because this sits on the page. */
  background:var(--bg);
  padding-top:4px;
  /* A hairline that only appears once something has scrolled under it — a permanent one reads
     as a divider even when there is nothing above to divide. */
  box-shadow:0 1px 0 var(--line)}

.list-foot{position:sticky;bottom:0;z-index:3;background:var(--bg);
  padding:12px 0 8px;margin-top:0;
  box-shadow:0 -1px 0 var(--line)}

/* Room for the pager. Without this the last post sits underneath it, and the row somebody
   most recently touched is the one they cannot read. */
.list-scroll{padding-bottom:6px}


/* A soft edge where rows pass under the bars.

   Without it a row is sliced clean in half at the boundary, which reads as a rendering fault
   rather than as something scrolling. The fade says "there is more up there". */
.list-head:after{content:"";position:absolute;left:0;right:0;top:100%;height:14px;
  background:linear-gradient(var(--bg),transparent);pointer-events:none}
.list-foot:before{content:"";position:absolute;left:0;right:0;bottom:100%;height:14px;
  background:linear-gradient(transparent,var(--bg));pointer-events:none}

/* A search box with its glyph inside, at the trailing edge.

   Right rather than left, deliberately: on the left it sits where the caret goes and the first
   letter typed appears to collide with it. On the right it stays clear and still labels the
   field. */
.searchbox{position:relative;display:flex;flex:1;min-width:180px}
.searchbox input{width:100%;padding-right:34px}
.searchbox .ico{position:absolute;right:10px;top:50%;transform:translateY(-50%);
  pointer-events:none;color:var(--muted);display:flex}

.tabs{display:flex;flex-wrap:wrap;align-items:center;gap:4px;
  border-bottom:1px solid var(--line);margin-bottom:12px}

.tab{background:none;border:0;color:var(--muted);font:inherit;cursor:pointer;
  padding:9px 13px;border-bottom:2px solid transparent;text-decoration:none;
  display:inline-flex;align-items:center;gap:7px}
.tab:hover{color:var(--text)}
.tab.is-on{color:var(--text);border-bottom-color:var(--accent);font-weight:700}

/* The badge sits against the page rather than a panel, so it borrows the line colour —
   --btn is close to the background here and would nearly disappear. */
.tab .n{background:var(--line);border-radius:var(--r-pill);padding:1px 8px;font-size:12.5px;
  font-weight:400;font-variant-numeric:tabular-nums;min-width:10px;text-align:center}
.tab.is-on .n{background:var(--accent);color:var(--on-accent)}

/* A zero is real information and never the thing you want. Fading it means the row's weight
   follows where the work actually is. */
.tab .n.z{opacity:.4}
.tpl-cat{font-size:11px;letter-spacing:.6px;text-transform:uppercase;color:var(--accent-ink);
  font-weight:700;margin-bottom:3px}

/* running order */
.running{list-style:none;margin:14px 0 0;padding:0;counter-reset:pos}
.run-item{display:flex;align-items:center;gap:10px;flex-wrap:wrap;background:var(--panel);
  border:1px solid var(--line);border-radius:10px;padding:10px 12px;margin-bottom:8px}
.run-item.dragging{opacity:.5;border-color:var(--accent)}
.run-item .grip{cursor:grab;color:var(--muted);font-size:16px;line-height:1}
.run-item .pos{min-width:26px;height:26px;border-radius:999px;background:var(--accent);color:var(--on-accent);
  font-size:12px;font-weight:700;display:inline-flex;align-items:center;justify-content:center}
.run-title{flex:1;min-width:180px;display:flex;flex-direction:column}
.dwell{display:flex;align-items:center;gap:6px}

/* A faded clock while the box is empty, and the number it will use beside it.

   The placeholder used to be the word "screen", which is a word in a box that takes figures —
   and at the width this column allows it was cut to "scree". Widening it stopped the clipping
   and left the odd-looking part. The clock says "this is a time" without spending any width on
   saying it, and the placeholder goes back to being a number: the one that will actually be
   used if nobody types anything.

   80px, down from the 96 the word needed. Room for three digits and the icon.

   Drawn as a data URI rather than another file: it is 200 bytes, and a request per row on a
   running order of thirty posts is thirty requests for one small mark. A fixed muted grey, not
   currentColor — a background-image cannot inherit it. That is a real limitation and the reason
   the brand mark is masked instead; here the box background barely moves between themes, so a
   single grey reads correctly in both.

   SELECTED ON [type=number], and that is not decoration. The base control rule is
   `input:not([type="checkbox"]):not([type="radio"])` — :not() carries the specificity of its
   argument, so that selector is (0,2,1) while a plain `.dwell input` is only (0,1,1). It also
   sets `background` as a SHORTHAND, which resets background-image to none. So the first version
   of this drew nothing at all: a rule 350 lines earlier quietly won. The attribute brings this
   to (0,2,1) as well, and being later in the file it takes the tie. */
.dwell input[type="number"]{width:80px;min-height:40px;padding-left:30px;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%238a90a0' stroke-width='1.5' stroke-linecap='round'%3E%3Ccircle cx='8' cy='8' r='6'/%3E%3Cpath d='M8 4.6V8l2.3 1.6'/%3E%3C/svg%3E");
  background-repeat:no-repeat;background-position:9px center;background-size:15px 15px}

/* Gone the instant anything is typed. A clock still sitting behind a real number is worse than
   no clock — it reads as a second value, or as a control that has stuck. */
.dwell input[type="number"]:not(:placeholder-shown){background-image:none;padding-left:12px}

/* Seconds that belong to a post shared with restaurants this person does not manage. A disabled
   input otherwise looks exactly like an editable one here, and a box that silently refuses to
   change reads as a fault — so it is faded, dashed, and labelled "shared" beside it. */
.dwell input:disabled, .hold-video input:disabled{opacity:.55;cursor:not-allowed;border-style:dashed}
.dwell .shared{font-size:11px;letter-spacing:.04em;text-transform:uppercase;color:var(--muted)}
/* Where a post is playing, under its title in the running order.

   Closed it is one line that never widens the row: min-width:0 is the part that matters, because
   a flex item's default min-width is auto and an overflowing child would otherwise push the row
   out instead of being clipped. Open it wraps into chips and takes the height it needs. */
.run-title .screens{font-size:12px;max-width:100%}
.run-title .screens-only{font-size:12px}
.screens>summary{cursor:pointer;display:flex;gap:6px;align-items:baseline;color:var(--muted);
  max-width:100%;list-style:none}
.screens>summary::-webkit-details-marker{display:none}
.screens>summary:hover{color:var(--text)}
.screens-count{white-space:nowrap;font-weight:600}
/* The truncated half. Cut with an ellipsis rather than wrapped, so a post on forty screens is
   the same height in the list as a post on two. */
.screens-line{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* The triangle, drawn rather than borrowed: ::marker is not stylable everywhere and the default
   glyph sits on the text baseline at whatever size the font decides. */
.screens-count::before{content:"\25b8";display:inline-block;margin-right:5px;
  transition:transform .12s ease}
.screens[open] .screens-count::before{transform:rotate(90deg)}
/* Nothing left to promise once it is open — the chips below say it in full. */
.screens[open] .screens-line{display:none}
.screens-all{display:flex;flex-wrap:wrap;gap:4px;margin-top:5px}
.run-actions{display:flex;gap:6px;flex-wrap:wrap}
/* Status surfaces — a flash, an inline error, a notice, a chip.

   All four now read the --ok/--warn/--bad pairs, so they follow the scheme and the brand
   instead of each carrying its own colours. .flash had fixed dark values, which meant a
   success message in light mode was a dark green box on a white card; .err and .notice were
   used in the markup and styled nowhere at all; .pill was bare text, so a screen that was not
   responding looked exactly like one that was. */
/* Controls sitting side by side. Below the 720 breakpoint a row is stacked rather than a grid,
   so the actions need a rule of their own out here as well as inside it. */
/* A button carrying only an icon. Square rather than a wide half-empty pill, and the glyph
   centred rather than sitting where the text baseline would have put it. */
/* 40px, not 32. It sits in a row beside Edit and Delete, which are full-height buttons, and a
   32px square next to them read as a smaller, lesser control as well as giving the mark less
   room than it needed. This also clears the 40px minimum a finger wants. */
.btn.icon-only{padding:0;width:40px;min-width:40px;display:inline-flex;
  align-items:center;justify-content:center}
.btn.icon-only svg{display:block}

.inline{display:flex;gap:8px;align-items:center;flex-wrap:wrap}

/* Emitted on nine pages and defined on none of them — the same fault as .btn.sm and .tab.is-on
   at the foot of this file, and the third time it has been found by looking at something else.

   Only `.lib-add .row` was ever written, so a bare `class="row"` was a plain block: the Claim
   form on Screens stacked its three fields and its button one above the other down the card,
   and the filter bar above the table did the same. Every call site gives it away — all of them
   set `gap`, `flex-wrap`, `justify-content` or `align-items` inline, and not one of those
   properties does anything to a block. The markup has always expected a flex row.

   `align-items:center` because these labels are inline — text and field on one line — where
   .lib-add's are display:block with the text above, which is why that one keeps its own
   flex-end and, being more specific, still wins. */
.row{display:flex;gap:10px;align-items:center;flex-wrap:wrap}

/* A field inside a row is a stacked pair — caption above control — which is what these labels
   already looked like, because the base input rule is width:100% and pushed itself onto its own
   line. Said properly here so the pair can also be made to shrink.

   min-width:0 is the part that matters. A flex item will not go narrower than its content's
   minimum, an <input> carries a wide intrinsic one, and the default min-width:auto honours it —
   so on a phone the three Claim fields drove the card's right edge off the side of the screen
   and took the whole page's horizontal scroll with them. The basis lets them share a line where
   there is room and wrap where there is not.

   .chk is excluded: a tick box and its caption belong beside each other, not stacked. */
.row>label:not(.chk){display:flex;flex-direction:column;gap:4px;flex:1 1 180px;min-width:0}
.row>label:not(.chk) input,.row>label:not(.chk) select{width:100%}

.row-actions{display:flex;gap:6px;align-items:center;flex-wrap:wrap}
/* A form wrapping a single button should take no space of its own. */
.row-actions form{display:inline-flex;margin:0}

.flash{padding:10px 14px;border-radius:var(--r-control);font-size:14px}
.flash.ok{background:var(--ok-bg);border:1px solid var(--ok-ink);color:var(--ok-ink)}
.flash.bad{background:var(--bad-bg);border:1px solid var(--bad-ink);color:var(--bad-ink)}
/* The third state, and the one the pair above had no room for: neither "that worked" nor
   "that failed", but "there is something here you did not ask for". The editor's offer of
   unsaved work this browser kept is the first of them. */
.flash.warn{background:var(--warn-bg);border:1px solid var(--warn-ink);color:var(--warn-ink)}

/* The bar offering that work back. A message with two buttons in it, so the buttons sit on the
   same line as the sentence where there is room and drop below it where there is not — this is
   read on a back-office machine and on a phone in a stockroom. */
.stash-bar{display:flex;gap:12px;align-items:center;flex-wrap:wrap;justify-content:space-between;
  margin-bottom:14px}
.stash-actions{display:flex;gap:8px;flex-shrink:0}

/* "Draft saved 14:32", beside the save buttons. Deliberately quiet: it is reassurance, and
   something that catches the eye every thirty seconds stops being reassuring. It is also
   aria-live, so it is announced when it changes rather than only when somebody looks. */
.save-state{font-size:12.5px;align-self:center;max-width:34ch;line-height:1.35}

/* The timed-out box. The confirm dialog's clothes, with one difference: it is not a question,
   so the wording is a statement and the primary action leaves the page. */
.session-out .confirm-title{font-size:17px}

.err,.notice{padding:10px 14px;border-radius:var(--r-control);font-size:14px;margin-bottom:14px}
.err{background:var(--bad-bg);border:1px solid var(--bad-ink);color:var(--bad-ink)}
.notice{background:var(--panel2);border:1px solid var(--line);color:var(--text)}
.notice.ok{background:var(--ok-bg);border-color:var(--ok-ink);color:var(--ok-ink)}
.notice.bad{background:var(--bad-bg);border-color:var(--bad-ink);color:var(--bad-ink)}

.pill{display:inline-block;font-size:12px;padding:3px 10px;border-radius:var(--r-pill);
  background:var(--btn);color:var(--muted)}
.pill.ok{background:var(--ok-bg);color:var(--ok-ink)}
.pill.warn{background:var(--warn-bg);color:var(--warn-ink)}
.pill.bad{background:var(--bad-bg);color:var(--bad-ink)}

/* .badge.bad was used in the markup and styled nowhere, so a "Revoked" badge came out the
   same grey as every other. The three admin.css already had keep their meanings. */
.badge.ok{background:var(--ok-bg);color:var(--ok-ink)}
.badge.bad{background:var(--bad-bg);color:var(--bad-ink)}

/* ---- the screens table ---------------------------------------------------
   .grid and .kv were emitted by Pages/Devices/Index.cshtml and defined nowhere, and this file
   carries no `table` rule of any kind — Screens is the only page in the CMS with a table, so
   nothing else ever missed them. The estate list therefore rendered as bare browser default:
   no rule between rows, no header treatment, everything hard against everything else, on the
   one page an operator uses to find a screen that has stopped.

   Border-collapse with a single hairline under each row rather than a boxed grid: eight
   hundred rows of full box borders is a mesh, and what a reader follows across a wide row is
   one horizontal line. */
.grid{width:100%;border-collapse:collapse;font-size:14px}
.grid th{text-align:left;padding:0 12px 8px;border-bottom:1px solid var(--line);
  font-size:11.5px;font-weight:700;letter-spacing:.04em;text-transform:uppercase;
  color:var(--muted);white-space:nowrap}
.grid td{padding:10px 12px;border-bottom:1px solid var(--line);vertical-align:middle}
.grid tbody tr:last-child td{border-bottom:0}
.grid tbody tr:hover{background:var(--panel2)}

/* A screen that has stopped answering is why somebody opened this page, so the row carries the
   state as well as the pill in it. A left edge rather than a tinted row: the tint would fight
   the hover, and at a glance down the table an edge is what the eye picks up. */
.grid tr.warn td:first-child{box-shadow:inset 3px 0 var(--warn-ink)}

/* "More" opens the per-stick diagnostics. Scoped to the table so the library's own
   <details class="lib-add"> keeps the appearance it was written with. */
.grid details summary{cursor:pointer;color:var(--muted);font-size:13px;padding:2px 0}
.grid details summary:hover{color:var(--text)}
.grid details[open] summary{margin-bottom:6px}

/* Model, Android, App, WebView, Panel, Address, Code — a label column that stays as wide as its
   longest word and a value column that takes the rest. */
.kv{display:grid;grid-template-columns:auto 1fr;gap:4px 14px;margin:6px 0 10px;font-size:13px}
.kv dt{color:var(--muted)}
.kv dd{margin:0}

/* Six columns do not fit a phone, and the answer is not to hide any of them — the store number
   and "last seen" are the two a person is looking for, and they are at opposite ends. The table
   scrolls inside its own box instead, so the page itself never does: a card whose right edge is
   off the side of the screen looks broken in a way a scrolling table does not.

   A wrapper rather than `overflow` on the table itself. Setting `display:block` on a <table> to
   make it scrollable does contain the table, but it takes the element out of table layout while
   its rows stay in it, and the anonymous table box that results still pushed the document wider
   than the viewport — measured at 430px, where the cards' right edges ended up off-screen.
   Inert above the breakpoint: there is nothing to scroll. */
.grid-wrap{overflow-x:auto}

.inline-form{display:inline-flex;gap:8px;align-items:center;margin:0}
@media (max-width:719px){
  .run-actions{width:100%} .run-actions .btn{flex:1}
}
.hold-video{display:flex;align-items:center;gap:6px;font-size:12.5px;color:var(--muted);
  padding:6px 10px;border:1px solid var(--line);border-radius:8px;min-height:40px}
.hold-video input{min-width:18px;min-height:18px}
.auth-card{max-width:460px;margin:32px auto;background:var(--panel);border:1px solid var(--line);
  border-radius:var(--radius);padding:24px}
.auth-card h1{font-size:20px;margin:0 0 8px}


/* audit panel — Users page, admin and above */
.audit{list-style:none;margin:0;padding:0;display:flex;flex-direction:column}
.audit-row{display:grid;grid-template-columns:auto 1fr;gap:12px;align-items:baseline;
  padding:7px 0;border-top:1px solid var(--line);font-size:13.5px}
.audit-row:first-child{border-top:0}
.audit-row time{color:var(--muted);font-size:12.5px;white-space:nowrap;
  font-variant-numeric:tabular-nums}
/* a refused attempt has to be findable by eye in a list of fifty */
.audit-row.refused{background:rgba(225,29,85,.10);margin:0 -8px;
  padding-left:8px;padding-right:8px;border-radius:6px}
@media (max-width:719px){
  .audit-row{grid-template-columns:1fr;gap:2px}
}


/* ===== the store library =====

   Two places use these: the picker under a library slot in the post editor, and the
   webmaster's curator page. One set of rules, because they show the same artwork and
   somebody comparing "what the curator sees" with "what an editor sees" should be
   comparing the artwork rather than two renderings of it. */

.lib-panel{margin-top:10px;padding:12px;background:var(--panel2);border:1px solid var(--line);
  border-radius:10px}
.lib-head{display:flex;gap:12px;align-items:flex-start;justify-content:space-between;
  flex-wrap:wrap;margin-bottom:10px}
.lib-sub{font-size:12.5px;margin-top:2px}
.lib-note{font-size:12.5px;margin:0 0 8px}
.lib-folders{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:12px}
/* The folders inside one folder: indented under the row above, and smaller, so two rows read as
   a level rather than as sixteen folders of equal standing. */
.lib-subfolders,.lib-folders-child{margin-top:-4px;margin-left:14px}
.lib-subfolders .chip-btn,.lib-filter-child{min-height:32px;padding:5px 12px;font-size:12.5px}
.lib-filter-child{margin-left:10px}
/* The move control sits beside a folder's chip, not inside one: a select inside a .chip inherits
   the pill's padding and radius and comes out as a blob with a control in it. */
/* What a bulk import did, one line per piece. Scrolls rather than pushing the grid off the
   screen: two hundred lines is a report, not a page. */
.bulk-list{list-style:none;margin:12px 0 0;padding:0;max-height:16rem;overflow-y:auto;font-size:13px}
.bulk-list li{padding:3px 0;border-bottom:1px solid rgba(0,0,0,.06)}
.bulk-added strong{font-weight:600}
.bulk-skipped strong{font-weight:600;opacity:.75}
.bulk-failed strong{font-weight:700;color:#b3111f}

.lib-move{display:inline-flex;align-items:center;gap:6px}
.lib-move label{font-size:12px}
.lib-move select{max-width:10rem;min-height:32px;padding:4px 8px;font-size:12.5px}

/* The picker's search. .searchbox is sized to sit in a toolbar row, where flex:1 makes it share
   the space; here it is on its own in a narrow column, so it takes the width and nothing else. */
.lib-search{margin-bottom:10px;width:100%}
.lib-from{font-size:12.5px}

/* auto-fill rather than a fixed column count: this grid appears both full-width on the
   curator page and in the narrow left column of the editor, and one rule should suit both. */
.lib-grid{display:grid;gap:10px;grid-template-columns:repeat(auto-fill,minmax(104px,1fr))}

.lib-item{display:flex;flex-direction:column;gap:4px;padding:8px;cursor:pointer;text-align:center;
  background:var(--panel);border:1px solid var(--line);border-radius:10px;color:inherit;
  font:inherit;transition:border-color .12s,transform .12s}
.lib-item:hover,.lib-item:focus{border-color:var(--accent);transform:translateY(-1px);outline:none}

/* A square well with the artwork centred and contained, not cropped.

   object-fit:cover is right for a photograph in a slot and wrong here: a promotional flash
   is a shape, and cropping its corners off makes two different stickers look alike — which
   is the one thing a picker must not do. */
.lib-art{display:flex;align-items:center;justify-content:center;aspect-ratio:1;
  background:#0d1015;border-radius:8px;overflow:hidden}
.lib-art img,.lib-art video{max-width:100%;max-height:100%;object-fit:contain}
.lib-art .kt-lottie{width:100%;height:100%}

.lib-name{font-size:12.5px;font-weight:600;line-height:1.25;
  overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}
.lib-folder{font-size:11px;color:var(--muted)}

/* ---- the curator page ---- */

.lib-curate .lib-item{cursor:default;text-align:left}
.lib-curate .lib-item:hover{transform:none;border-color:var(--line)}

/* Withdrawn artwork stays in the curator's grid, dimmed, because "it is not offered" and
   "it is not there" are different answers and only one of them tells a curator to stop
   looking for it. Dimming is paired with the word, never on its own — a greyed thumbnail on
   its own reads as a loading state. */
.lib-item.is-withdrawn .lib-art{opacity:.4}
.lib-item.is-withdrawn .lib-name{color:var(--muted)}

.lib-actions{display:flex;flex-wrap:wrap;gap:6px;margin-top:2px}
.lib-usage{font-size:11.5px;color:var(--muted);font-variant-numeric:tabular-nums}

/* The thumbnail is a button on the curator page — it opens the piece full size. Stripped back to
   the well it already was: a button brings its own border, padding and centred text, and the
   .lib-art rule above assumes none of them. */
.lib-curate .lib-art{border:0;padding:0;width:100%;cursor:zoom-in;font:inherit;color:inherit}
.lib-curate .lib-art:focus-visible{outline:2px solid var(--accent);outline-offset:2px}

/* The folder filter. .chip-btn is the toggle chip the template categories already use, so the
   two filters on this system look and behave alike; .lib-folders gives it the row. */
.lib-filters{margin:0 0 12px}
/* The count follows the label, and .muted sets its own colour — on the chosen chip it would
   otherwise stay grey against the accent, reading as a number belonging to something else.
   Same fix as .chip-btn.is-on .muted, restated because this row is nested one deeper. */
.lib-filters .chip-btn.is-on .muted{color:currentColor;opacity:.75}

/* One piece, full size. Same shape as the running-order preview: the thing fills the space and
   the controls sit on a bar underneath it. */
.lib-view{position:fixed;inset:0;z-index:70;background:rgba(6,8,11,.94);
  display:flex;flex-direction:column}
.lib-view-art{flex:1;min-height:0;display:flex;align-items:center;justify-content:center;
  padding:24px}
/* Contained, never cropped, and never enlarged past its own pixels — a 200px flash blown up to
   fill a 4K monitor tells a curator nothing true about how it will look on a screen. */
.lib-view-art img,.lib-view-art video{max-width:100%;max-height:100%;
  width:auto;height:auto;object-fit:contain}
.lib-view-art .kt-lottie{width:min(100%,720px);height:min(100%,720px)}
.lib-view-bar{display:flex;align-items:center;gap:12px;flex-wrap:wrap;
  padding:10px 14px;background:var(--panel);border-top:1px solid var(--line);font-size:13px}
.lib-view-name{font-weight:700}
/* Pushes the close button to the far end, so it is in the same place whatever the name's length. */
.lib-view-bar .muted{margin-right:auto}
/* A piece on screen right now is the one where withdrawing needs a second thought. */
.lib-usage.is-live{color:var(--accent-ink);font-weight:700}

.lib-add{background:var(--panel);border:1px solid var(--line);border-radius:var(--radius);
  padding:16px;margin-bottom:20px}
.lib-add .row{display:flex;flex-wrap:wrap;gap:10px;align-items:flex-end}
.lib-add label{display:block;font-size:12.5px;color:var(--muted);margin-bottom:4px}
.lib-drop{margin-top:12px}
.lib-form{display:flex;flex-wrap:wrap;gap:6px;align-items:center;margin-top:6px}
.lib-form input,.lib-form select{min-width:0;flex:1 1 120px}
.muted.bad{color:#ff8fae}

/* Emitted but never defined — found while adding the library.

   Every caller in Pages/ writes class="btn sm" and has done since those pages were written;
   nothing here ever matched, so all of them render full size. No error, no warning, and the
   only symptom is a row of buttons that looks heavier than it was meant to. Exactly the shape
   of the .tab is-on fault verify_cms_toolkit.py now guards, which is why it is fixed here
   rather than left as it was found. Same for .empty, emitted on every "nothing here yet" list. */
.btn.sm{min-height:32px;padding:5px 12px;font-size:13px;border-radius:8px}
.btn.sm.danger{border-color:#7a2233;color:#ff8fae}
.empty{color:var(--muted);background:var(--panel2);border:1px dashed var(--line);
  border-radius:10px;padding:18px;text-align:center;margin:16px 0}

/* ---- the status page ------------------------------------------------------
   404, 403, 400, 429 and 500 all land on one page, so the layout has to carry the
   difference visually rather than only in the words. Left as three inline `style=`
   attributes when it was first written as a backstop for the sign-in 400; moved here
   because it is a page people actually see.

   Centred in what is left of the viewport rather than pinned near the top: floating a
   small card on a full-height gold field left two-thirds of the screen empty and read
   as unfinished. `svh` so a phone's collapsing address bar does not make it jump, with
   `vh` first for anything that does not know the unit. */
.status{min-height:calc(100vh - 140px);min-height:calc(100svh - 140px);
  display:flex;align-items:center;justify-content:center;padding:24px 16px}
.status .card{max-width:30rem;width:100%;padding:32px 28px;text-align:center}
.status .card:hover{border-color:var(--line)}   /* not a control; nothing to hover */

/* The mark. Same screen-on-a-stand silhouette as the favicon, so an error page is
   recognisably part of the same product, with the symbol inside it carrying the
   meaning — a 403 must not look like a 404 at a glance. */
.status-mark{width:88px;height:88px;margin:0 auto 20px;display:block}
.status-mark .screen{fill:none;stroke:var(--edge);stroke-width:5;stroke-linejoin:round}
.status-mark .glyph{fill:none;stroke:var(--accent);stroke-width:6;
  stroke-linecap:round;stroke-linejoin:round}
.status-mark .stand{fill:var(--edge)}

.status h1{margin:0 0 10px;font-size:1.6rem;line-height:1.2}
.status p{margin:0 0 22px;color:var(--page-muted)}

/* Two ways out, not one. A signed-out visitor who lands here otherwise has a single
   button and no navigation bar to fall back on — the people least able to help
   themselves got the least help. */
.status-actions{display:flex;gap:12px;justify-content:center;flex-wrap:wrap}
.status-actions .quiet{background:transparent;border-color:transparent;
  text-decoration:underline;color:var(--page-muted)}
.status-actions .quiet:hover{color:var(--page-text);border-color:transparent}
