/* =========================================================
   SaaS Core — light theme design system
   Palette: indigo-violet brand on a cool paper canvas
   Type: Plus Jakarta Sans (display) + Inter (UI/body)
   ========================================================= */

:root{
  --bg:#F4F6F9;
  --surface:#FFFFFF;
  --surface-2:#FAFBFD;
  --surface-3:#F1F3F8;
  --border:#E3E7EE;
  --border-strong:#D3D9E4;

  --ink:#101828;
  --ink-2:#344054;
  --muted:#667085;
  --faint:#98A2B3;

  --brand:#5A4FE0;
  --brand-600:#4A3FD0;
  --brand-700:#3D34B4;
  --brand-tint:#EFEEFC;
  --brand-tint-2:#E2E0F9;

  --ok:#0E9F6E;
  --warn:#B54708;
  --danger:#D92D20;
  --info:#175CD3;

  /* The values that render. Until now this line said 12px / 16px / 22px and
     was silently replaced further down the same block by 8px / 14px / 18px, so
     anybody reading here believed a radius they were not getting. The second
     set is removed; these are its values. */
  --radius-xs:4px; --radius-sm:8px; --radius:8px;
  --radius-md:10px; --radius-lg:14px; --radius-xl:18px;
  --radius-2xl:22px; --radius-pill:999px;
  --shadow-xs:0 1px 2px rgba(16,24,40,.05);
  --shadow-sm:0 2px 8px rgba(16,24,40,.06);
  --shadow-md:0 10px 28px rgba(16,24,40,.10);
  --shadow-lg:0 24px 60px rgba(16,24,40,.16);

  --sidebar-w:262px;
  /* Aliases. These names are used across the stylesheets but were never
     defined, so every rule naming one silently did nothing — a transparent
     background where a dark one was meant, and so on. Defined here once rather
     than renamed in thirty-odd places. */
  --text:var(--ink);
  --ok-tint:#ECFDF3;
  --warn-tint:#FFFAEB;
  --danger-tint:rgba(240,68,56,.12);
  --info-tint:#EFF8FF;
  --surface-muted:var(--surface-2);

  /* Text shades of the status colours.
   *
   * A fill colour and a text colour are different jobs at the same hue, and
   * this file only had the fill. Measured: --danger on the page background is
   * 4.46:1, under AA, and a validation message is the one piece of text
   * somebody must be able to read.
   *
   * Against --bg (#F4F6F9): danger 6.07:1, warn 6.95:1, ok 7.08:1, info
   * 7.56:1. The dark block gives these the plain status colours, because these
   * shades measure under 3:1 on a dark page.
   */
  /* Muted text that can actually be read.
   *
   * --faint (#98A2B3) measures 2.32 to 2.58:1 on every background this project
   * has — it never passes AA anywhere. It was the colour of .hint, the helper
   * line under every form field, and that single rule accounted for 430 of the
   * contrast failures found across 92 pages.
   *
   * --muted (#667085) is 4.48:1 on --surface-3, which is 0.02 short. So this is
   * --muted two steps darker along its own hue: 4.61 to 5.12:1 on all four
   * backgrounds, and indistinguishable from it by eye.
   *
   * --faint keeps its other uses: icons, placeholders, a deliberately greyed
   * inherited value. Those are not text somebody has to read.
   */
  --muted-text:#646E83;

  --danger-text:#B42318;
  --warn-text:#93370D;
  --ok-text:#05603A;
  --info-text:#1849A9;
  --topbar-h:64px;
  /* Weights.
   *
   * 400, 500, 600 and 700 — the four the fonts are actually loaded with:
   *
   *   Inter:wght@400;500;600;700
   *   Plus+Jakarta+Sans:wght@600;700;800
   *
   * The stylesheets used to ask for 650 in 111 rules and 550 in 3. Measured
   * with four static faces declared at exactly those weights: a 650 request
   * resolves to the 700 face and a 550 request to the 600 face. So the CSS was
   * asking for a weight the font cannot draw, and the distinction between 600
   * and 650 did not exist on screen.
   *
   * They are snapped to what they already rendered as, which changes nothing
   * visually and stops 114 rules claiming something they do not do. If the
   * variable font is ever loaded instead, 650 becomes a real weight and this
   * is the decision to revisit.
   */
  --ff-display:"Plus Jakarta Sans",ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;
  --ff-body:"Inter",ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;
  --ff-mono:ui-monospace,SFMono-Regular,"SF Mono",Menlo,monospace;
}

*,*::before,*::after{box-sizing:border-box}
html{-webkit-text-size-adjust:100%}
body{
  margin:0;background:var(--bg);color:var(--ink);
  font-family:var(--ff-body);font-size:15px;line-height:1.55;
  -webkit-font-smoothing:antialiased;
}
h1,h2,h3,h4,h5{font-family:var(--ff-display);margin:0;letter-spacing:-.015em;color:var(--ink)}
h1{font-size:24px;font-weight:700}
h2{font-size:19px;font-weight:700}
h3{font-size:16px;font-weight:700}
p{margin:0}
a{color:var(--brand);text-decoration:none}
a:hover{color:var(--brand-700)}
img{max-width:100%;display:block}
hr{border:0;border-top:1px solid var(--border);margin:20px 0}
:focus-visible{outline:2px solid var(--brand);outline-offset:2px;border-radius:6px}
::selection{background:var(--brand-tint-2)}

.muted{color:var(--muted-text)}
.faint{color:var(--muted-text)}
.small{font-size:13px}
.tiny{font-size:12px}
.strong{font-weight:600}
.mono{font-family:var(--ff-mono)}
.center{text-align:center}
.right{text-align:right}
.nowrap{white-space:nowrap}
.hide{display:none !important}
.stack{display:flex;flex-direction:column;gap:14px}
.row{display:flex;align-items:center;gap:10px}
.row-between{display:flex;align-items:center;justify-content:space-between;gap:12px}
.grow{flex:1 1 auto;min-width:0}
.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.mt-0{margin-top:0}.mt-1{margin-top:6px}.mt-2{margin-top:12px}.mt-3{margin-top:20px}.mt-4{margin-top:28px}
.mb-2{margin-bottom:12px}.mb-3{margin-bottom:20px}

/* ---------- App shell ---------- */
.app{display:flex;min-height:100vh}
.sidebar{
  width:var(--sidebar-w);flex:0 0 var(--sidebar-w);background:var(--surface);
  border-right:1px solid var(--border);display:flex;flex-direction:column;
  position:fixed;inset:0 auto 0 0;z-index:60;
}
.sidebar-brand{display:flex;align-items:center;gap:12px;padding:16px 16px 14px}
.brand-mark{
  width:38px;height:38px;border-radius:11px;display:grid;place-items:center;flex:0 0 38px;
  background:linear-gradient(140deg,var(--brand) 0%,var(--brand-700) 100%);
  color:#fff;font-family:var(--ff-display);font-weight:800;font-size:16px;
  box-shadow:0 6px 14px rgba(90,79,224,.28);overflow:hidden;
}
.brand-mark img{width:100%;height:100%;object-fit:cover}
.brand-text{min-width:0}
.brand-name{font-family:var(--ff-display);font-weight:800;font-size:15px;letter-spacing:-.01em}
.brand-sub{font-size:11.5px;color:var(--muted-text);letter-spacing:.02em}
.nav{padding:8px 12px 16px;overflow-y:auto;overscroll-behavior:contain;flex:1;min-height:0}
.nav-label{
  font-size:11px;font-weight:700;letter-spacing:.09em;text-transform:uppercase;
  color:var(--muted-text);padding:16px 10px 8px;
}
.nav-item{
  position:relative;display:flex;align-items:center;gap:12px;padding:8px 12px;border-radius:10px;
  color:var(--ink-2);font-weight:500;font-size:14.5px;margin-bottom:2px;transition:background .15s,color .15s;
}
.nav-item svg{width:19px;height:19px;flex:0 0 19px;stroke-width:1.7;color:var(--faint);transition:color .15s}
.nav-item:hover{background:var(--surface-3);color:var(--ink)}
.nav-item.active{background:var(--brand-tint);color:var(--brand-700);font-weight:600}
.nav-item.active svg{color:var(--brand)}
.nav-item.active::before{
  content:"";position:absolute;left:-12px;top:50%;transform:translateY(-50%);
  width:3px;height:22px;border-radius:0 3px 3px 0;
  background:linear-gradient(180deg,var(--brand),var(--brand-700));
}
.nav-badge{margin-left:auto;font-size:11px;font-weight:700;background:var(--surface-3);color:var(--muted-text);padding:2px 8px;border-radius:999px}
.sidebar-foot{padding:12px;border-top:1px solid var(--border)}
.side-user{display:flex;align-items:center;gap:10px;padding:8px;border-radius:10px}
.side-user:hover{background:var(--surface-3)}

.main{flex:1;min-width:0;margin-left:var(--sidebar-w);display:flex;flex-direction:column}
.topbar{
  height:var(--topbar-h);position:sticky;top:0;z-index:40;
  /* A token, not rgba(255,255,255,.85): hardcoded white stayed white in dark
     mode, leaving a bright band across the top of a dark page. */
  background:color-mix(in srgb, var(--surface) 85%, transparent);
  backdrop-filter:saturate(180%) blur(10px);border-bottom:1px solid var(--border);
  display:flex;align-items:center;gap:12px;padding:0 20px;
}

/* Everything from the theme toggle rightwards is pushed to the corner.
 *
 * The search has flex:1 and was doing this by accident. When it is not there —
 * an account without the users permission — nothing pushed, and the avatar and
 * its menu drifted into the middle of the bar. */
.topbar .tb-theme{margin-left:auto}
.page{padding:24px 20px 56px;max-width:1360px;width:100%;margin:0 auto}

/* ---------- how far a line of text runs ----------
   A paragraph across the full width measured around two hundred characters a
   line here. Typographers put the comfortable range at 65 to 75, and the reason
   is mechanical rather than aesthetic: past that the eye cannot find the start
   of the next line and re-reads or skips one.

   Applied to prose only. A table, a card grid or a form uses the width it is
   given — the limit is about reading sentences, not about narrowing the page. */
/* Running text gets a readable line length. Measured at 1440px, these three
   description lines ran to about 140 characters, which is roughly twice what
   is comfortable — they were simply missing from this list. */
.page-sub,.hint,.empty p,.small,.card-head p,.field .hint,
.svc-row-d,.fld-sub,.map-d{max-width:70ch}

/* Centred blocks keep their own centring; capping them at 70ch and leaving them
   left-aligned would look like a mistake. */
.empty p{margin-left:auto;margin-right:auto}
.page-head{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;flex-wrap:wrap;margin-bottom:20px}
.page-title{font-size:23px;font-weight:700;font-family:var(--ff-display)}
.page-sub{color:var(--muted-text);font-size:14px;margin-top:4px}
.burger{display:none}
.scrim{
  position:fixed;inset:0;background:rgba(16,24,40,.45);z-index:55;opacity:0;visibility:hidden;
  transition:opacity .22s;backdrop-filter:blur(2px);
}
.scrim.show{opacity:1;visibility:visible}

@media (max-width:1024px){
  .sidebar{transform:translateX(-100%);transition:transform .26s cubic-bezier(.4,0,.2,1);box-shadow:var(--shadow-lg)}
  .sidebar.open{transform:none}
  .main{margin-left:0}
  .burger{display:inline-flex}
  .page{padding:16px 14px 72px}
}

/* ---------- Cards ---------- */
.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-lg);box-shadow:var(--shadow-xs);min-width:0}
.card-head{padding:16px;border-bottom:1px solid var(--border);display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap}
.card-body{padding:16px}

/* A card is a bare container: its children bring their own edges. .card-head
   and .card-body do; a paragraph dropped straight in does not, and ends up one
   pixel from the border with its text against the corner.
   Rather than adding padding to .card — which would double up on the two that
   already inset themselves, and push a full-bleed table away from its own
   border — the loose children are given the same inset the head uses.
   Tables stay flush on purpose: their cells carry the padding, so the text
   still lines up with everything above it. */
.card > .hint,
.card > p,
.card > .empty,
.card > .field-error,
.card > .small{padding-left:16px;padding-right:16px}
.card > .hint:first-child,
.card > p:first-child{padding-top:16px}
.card > .hint:last-child,
.card > p:last-child{padding-bottom:16px}
.card-foot{padding:14px 16px;border-top:1px solid var(--border);background:var(--surface-2);border-radius:0 0 var(--radius-lg) var(--radius-lg)}
.grid{display:grid;gap:16px}
.grid-4{grid-template-columns:repeat(4,1fr)}
.grid-3{grid-template-columns:repeat(3,1fr)}
.grid-2{grid-template-columns:repeat(2,1fr)}
@media (max-width:1100px){.grid-4{grid-template-columns:repeat(2,1fr)}}
@media (max-width:820px){.grid-3,.grid-2{grid-template-columns:1fr}}
@media (max-width:560px){.grid-4{grid-template-columns:1fr}}

.stat{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-lg);padding:16px;box-shadow:var(--shadow-xs)}
.stat-icon{width:38px;height:38px;border-radius:11px;display:grid;place-items:center;background:var(--brand-tint);color:var(--brand);margin-bottom:14px}
.stat-icon svg{width:19px;height:19px}
.stat-value{font-family:var(--ff-display);font-size:27px;font-weight:800;letter-spacing:-.02em;line-height:1.1}
.stat-label{color:var(--muted-text);font-size:13.5px;margin-top:4px}

/* ---------- Buttons ---------- */
.btn{
  display:inline-flex;align-items:center;justify-content:center;gap:8px;
  height:40px;padding:0 16px;border-radius:10px;border:1px solid transparent;
  font-family:var(--ff-body);font-size:14.5px;font-weight:600;cursor:pointer;
  background:var(--surface-3);color:var(--ink-2);transition:background .15s,border-color .15s,box-shadow .15s,transform .06s;
  white-space:nowrap;text-decoration:none;
}
.btn:hover{background:var(--border)}
.btn:active{transform:translateY(1px)}
.btn:disabled,.btn.is-loading{opacity:.62;pointer-events:none}
.btn svg{width:17px;height:17px;flex:0 0 17px}
.btn-primary{background:var(--brand);color:#fff;box-shadow:0 2px 8px rgba(90,79,224,.28)}
.btn-primary:hover{background:var(--brand-600);color:#fff}
.btn-outline{background:var(--surface);border-color:var(--border-strong);color:var(--ink-2)}
.btn-outline:hover{background:var(--surface-2);border-color:var(--muted-text)}
.btn-danger{background:var(--danger);color:#fff}
.btn-danger:hover{background:#B42318;color:#fff}
.btn-ghost{background:transparent;color:var(--ink-2)}
.btn-ghost:hover{background:var(--surface-3)}
.btn-sm{height:34px;padding:0 12px;font-size:13.5px;border-radius:9px}
.btn-block{width:100%}
.btn-icon{width:38px;height:38px;padding:0;border-radius:10px;background:transparent;color:var(--muted)}
.btn-icon:hover{background:var(--surface-3);color:var(--ink)}
.btn-icon svg{width:19px;height:19px}
.btn .spinner{width:15px;height:15px;border:2px solid rgba(255,255,255,.45);border-top-color:#fff;border-radius:50%;animation:spin .7s linear infinite;display:none}
.btn.is-loading .spinner{display:inline-block}
@keyframes spin{to{transform:rotate(360deg)}}

/* ---------- Forms ---------- */
.field{display:flex;flex-direction:column;gap:6px}
.label{font-size:13.5px;font-weight:600;color:var(--ink-2)}
.label .req{color:var(--danger-text)}
.hint{font-size:12.5px;color:var(--muted-text)}
.input,.select,.textarea{
  width:100%;height:42px;padding:0 12px;border:1px solid var(--border-strong);border-radius:10px;
  background:var(--surface);color:var(--ink);font-family:var(--ff-body);font-size:14.5px;
  transition:border-color .15s,box-shadow .15s;
}
/* A textarea has to escape the single-line height above, whichever class it
   carries. The element selector is here as well as the class because most of
   the project writes <textarea class="input">, and every one of those was
   rendering forty-two pixels tall with its rows attribute ignored. */
.textarea,textarea.input{height:auto;padding:12px;resize:vertical;
  line-height:1.6;min-height:120px}
.select{appearance:none;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='none' stroke='%23667085' stroke-width='1.8' stroke-linecap='round'%3E%3Cpath d='M4 6l4 4 4-4'/%3E%3C/svg%3E");
  background-repeat:no-repeat;background-position:right 12px center;padding-right:36px}
.input:focus,.select:focus,.textarea:focus{outline:none;border-color:var(--brand);box-shadow:0 0 0 3.5px var(--brand-tint)}
.input::placeholder,.textarea::placeholder{color:var(--faint)}
.input:disabled,.select:disabled{background:var(--surface-3);color:var(--muted-text);cursor:not-allowed}
.input.error,.select.error{border-color:var(--danger-text)}
/* --danger-text, not --danger. Measured on the page background, --danger is
   4.46:1 — 0.04 under AA. The token is tuned as a fill on white. */
.field-error{font-size:12.5px;color:var(--danger-text);font-weight:500}
.input-group{position:relative;display:flex;align-items:center}
.input-group .input{padding-left:40px}
.input-group .ig-icon{position:absolute;left:13px;width:18px;height:18px;color:var(--faint);pointer-events:none}
.input-group .ig-action{position:absolute;right:6px;width:32px;height:32px;border:0;background:transparent;color:var(--muted-text);border-radius:8px;cursor:pointer;display:grid;place-items:center}
.input-group .ig-action:hover{background:var(--surface-3);color:var(--ink-2)}
.input-group .ig-action svg{width:18px;height:18px}
.form-grid{display:grid;grid-template-columns:1fr 1fr;gap:16px}
@media (max-width:640px){.form-grid{grid-template-columns:1fr}}
.col-span{grid-column:1/-1}

.check{display:flex;align-items:center;gap:8px;cursor:pointer;font-size:14px;color:var(--ink-2);user-select:none}
.check input{width:17px;height:17px;accent-color:var(--brand);cursor:pointer;margin:0}
.switch{position:relative;display:inline-flex;align-items:center;gap:10px;cursor:pointer;user-select:none}
.switch input{position:absolute;opacity:0;width:0;height:0}
.switch .track{width:42px;height:24px;border-radius:999px;background:var(--border-strong);transition:background .18s;flex:0 0 42px;position:relative}
.switch .track::after{content:"";position:absolute;top:3px;left:3px;width:18px;height:18px;border-radius:50%;background:#fff;box-shadow:var(--shadow-xs);transition:transform .18s}
.switch input:checked + .track{background:var(--brand)}
.switch input:checked + .track::after{transform:translateX(18px)}
.switch input:focus-visible + .track{box-shadow:0 0 0 3.5px var(--brand-tint)}

.color-field{display:flex;align-items:center;gap:10px}
.color-field input[type=color]{width:46px;height:42px;padding:4px;border:1px solid var(--border-strong);border-radius:10px;background:var(--surface);cursor:pointer}

/* A list of radio options, each with a title and a line of explanation.
 *
 * This is review-replies.css's .rr-mode-opt, which was the only place in the
 * project with this shape. Rather than write a second one for the landing-page
 * modes, the same rules are here under a generic name; the reply editor keeps
 * its own selector so nothing about that screen changes.
 */
.radio-rows{display:grid;gap:8px}
.radio-row{
  display:flex;align-items:flex-start;gap:12px;padding:12px;
  border:1px solid var(--border);border-radius:var(--radius-sm);
  cursor:pointer;transition:border-color .12s,background .12s;
}
.radio-row:hover{border-color:var(--border-strong)}
.radio-row input{margin-top:2px;flex:0 0 auto}
.radio-row > span{display:grid;gap:2px;min-width:0}
.radio-row strong{font-size:13px;font-weight:700;color:var(--ink)}
.radio-row .hint{font-size:12px;color:var(--muted-text);line-height:1.5}
.radio-row:has(input:checked){border-color:var(--brand);background:var(--brand-tint)}
/* On the chosen row the background becomes --brand-tint, where --muted-text is
   4.46:1 — just under AA. This is my own card, and the same 0.02-class
   shortfall that --muted-text was created to fix, one surface further in. */
.radio-row:has(input:checked) .hint{color:var(--ink-2)}

/* ---------- Chips & badges ---------- */
.chip{display:inline-flex;align-items:center;gap:6px;height:24px;padding:0 8px;border-radius:999px;font-size:12.5px;font-weight:600;background:var(--surface-3);color:var(--ink-2)}
.chip .dot{width:6px;height:6px;border-radius:50%;background:currentColor}
.chip-active{background:var(--ok-tint);color:var(--ok-text)}
.chip-inactive{background:var(--surface-3);color:var(--muted-text)}
.chip-expired{background:var(--warn-tint);color:var(--warn-text)}
.chip-role{background:var(--info-tint);color:var(--info-text)}
.chip-super{background:var(--brand-tint);color:var(--brand-700)}
.chip-danger{background:var(--danger-tint);color:var(--danger-text)}

/* A count beside a heading: "Scenes 12", "Library 40".
 *
 * Used on seven screens and defined on none of them, so the number sat
 * against the heading as plain text and read as a typo. Quieter than a
 * chip on purpose — it is a count, not a state, and giving it a colour
 * would make every heading look like it was reporting a problem. */
.pill{
  display:inline-flex;align-items:center;justify-content:center;
  min-width:22px;height:22px;padding:0 8px;margin-left:6px;
  border-radius:999px;background:var(--surface-3);color:var(--muted-text);
  font-size:12px;font-weight:600;font-variant-numeric:tabular-nums;
  vertical-align:middle;
}

.avatar{
  width:38px;height:38px;border-radius:11px;display:grid;place-items:center;flex:0 0 38px;
  color:#fff;font-weight:700;font-size:13.5px;font-family:var(--ff-display);overflow:hidden;
}
.avatar img{width:100%;height:100%;object-fit:cover}
.avatar-lg{width:76px;height:76px;border-radius:20px;font-size:26px;flex-basis:76px}
.avatar-sm{width:30px;height:30px;border-radius:9px;font-size:11.5px;flex-basis:30px}

/* ---------- Table ---------- */
.table-tools{display:flex;align-items:center;gap:10px;flex-wrap:wrap;padding:14px 16px;border-bottom:1px solid var(--border)}
.search{position:relative;flex:1 1 260px;max-width:340px}
.search svg{position:absolute;left:12px;top:50%;transform:translateY(-50%);width:17px;height:17px;color:var(--faint)}
.search .input{padding-left:37px;height:40px}
/* min-width:0 is what lets this actually scroll. A grid or flex child defaults
   to min-width:auto, so it refuses to shrink below its table's natural width and
   pushes the whole page sideways instead of scrolling inside itself. */
.table-wrap{overflow-x:auto;-webkit-overflow-scrolling:touch;min-width:0;max-width:100%}
table.data{width:100%;border-collapse:separate;border-spacing:0;font-size:14.5px}
table.data th{
  /* --muted, not --faint. Measured on --surface-2: --faint is 2.49:1 in light
     and 3.64:1 in dark, both under AA, and this is the only label a column
     has. --muted is 4.80:1 and 6.23:1. --faint keeps its other uses. */
  text-align:left;font-size:11.5px;font-weight:700;letter-spacing:.07em;text-transform:uppercase;color:var(--muted-text);
  padding:12px 16px;background:var(--surface-2);border-bottom:1px solid var(--border);white-space:nowrap;
}
table.data td{padding:12px 16px;border-bottom:1px solid var(--border);vertical-align:middle;color:var(--ink-2)}
table.data tbody tr{transition:background .12s}
table.data tbody tr:hover{background:var(--surface-2)}
table.data tbody tr:last-child td{border-bottom:0}
.cell-user{display:flex;align-items:center;gap:12px;min-width:0}
.cell-name{font-weight:600;color:var(--ink)}
.cell-mail{font-size:13px;color:var(--muted-text)}
.col-actions{width:56px;text-align:right}
.pagination{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:14px 16px;border-top:1px solid var(--border);flex-wrap:wrap}
.pager{display:flex;gap:6px}
.pager button{min-width:34px;height:34px;border-radius:9px;border:1px solid var(--border);background:var(--surface);color:var(--ink-2);font-weight:600;font-size:13.5px;cursor:pointer}
.pager button:hover:not(:disabled){background:var(--surface-3)}
.pager button.active{background:var(--brand);border-color:var(--brand);color:#fff}
.pager button:disabled{opacity:.45;cursor:not-allowed}

.empty{padding:48px 20px;text-align:center}
.empty-art{width:56px;height:56px;border-radius:16px;background:var(--brand-tint);color:var(--brand);display:grid;place-items:center;margin:0 auto 14px}
.empty-art svg{width:26px;height:26px}
.empty h3{margin-bottom:4px}

/* skeleton rows */
.sk{height:12px;border-radius:6px;background:linear-gradient(90deg,var(--surface-3) 25%,#EAEDF3 37%,var(--surface-3) 63%);background-size:400% 100%;animation:shimmer 1.3s ease infinite}
@keyframes shimmer{0%{background-position:100% 50%}100%{background-position:0 50%}}

/* ---------- Dropdown (3-dot menu) ---------- */
.dropdown{position:relative;display:inline-block}
.dropdown-menu{
  position:fixed;min-width:206px;background:var(--surface);border:1px solid var(--border);
  border-radius:var(--radius);box-shadow:var(--shadow-md);padding:6px;z-index:120;
  opacity:0;visibility:hidden;transform:translateY(-6px) scale(.98);transform-origin:top right;
  transition:opacity .14s,transform .14s,visibility .14s;
  /* visibility:hidden still occupies its position, and a menu parked off the
     right edge widens the document on a narrow screen — every page then scrolls
     sideways for no visible reason. Pinning it to the origin while closed keeps
     the fade but takes it out of the page's width. */
  left:0;top:0;max-width:100vw;
}
.dropdown-menu.open{opacity:1;visibility:visible;transform:none;max-width:calc(100vw - 16px)}
.dropdown-menu button,
.dropdown-menu a{
  display:flex;align-items:center;gap:10px;width:100%;padding:8px 10px;border:0;background:transparent;
  border-radius:8px;font-family:var(--ff-body);font-size:14px;color:var(--ink-2);cursor:pointer;text-align:left;
}
/* Anchors carry the same look as buttons here. A menu whose links render as
   blue underlined text next to styled buttons reads as two menus. */
.dropdown-menu a{text-decoration:none}
.dropdown-menu button:hover,
.dropdown-menu a:hover{background:var(--surface-3);color:var(--ink)}
.dropdown-menu button svg,
.dropdown-menu a svg{width:17px;height:17px;color:var(--faint);flex:0 0 17px}
.dropdown-menu button:hover svg,
.dropdown-menu a:hover svg{color:var(--ink-2)}
.dropdown-menu button.danger,
.dropdown-menu a.danger{color:var(--danger-text)}
.dropdown-menu button.danger svg,
.dropdown-menu a.danger svg{color:var(--danger)}
.dropdown-menu button.danger:hover,
.dropdown-menu a.danger:hover{background:var(--danger-tint)}
.dropdown-sep{height:1px;background:var(--border);margin:4px}

/* ---------- Drawer (slide-over) ---------- */
.drawer{
  position:fixed;top:0;right:0;bottom:0;width:min(520px,100%);background:var(--surface);z-index:130;
  display:flex;flex-direction:column;box-shadow:var(--shadow-lg);
  transform:translateX(100%);transition:transform .28s cubic-bezier(.32,.72,0,1);
}
.drawer.open{transform:none}
.drawer-head{padding:16px 20px;border-bottom:1px solid var(--border);display:flex;align-items:flex-start;justify-content:space-between;gap:12px}
/* min-height:0 is what makes the scroll work: a flex child defaults to
   min-height:auto, so it refuses to shrink below its content and the
   overflow rule never takes effect. Without it a long form is simply cut
   off and the only way to read it is to zoom out. */
.drawer-body{padding:20px;overflow-y:auto;overscroll-behavior:contain;flex:1;min-height:0}
/* Several drawers wrap head, body and footer in a form. A plain block in
   the middle breaks the flex chain, so the body sizes itself against the
   form's automatic height instead of the drawer's fixed one and grows past
   the screen. Passing the chain through the form is what makes those
   drawers scroll. */
.drawer > form{display:flex;flex-direction:column;flex:1;min-height:0}
.drawer-foot{padding:14px 20px;border-top:1px solid var(--border);display:flex;gap:10px;justify-content:flex-end;background:var(--surface-2)}
@media (max-width:560px){
  .drawer{width:100%;top:auto;height:94vh;border-radius:var(--radius-xl) var(--radius-xl) 0 0;transform:translateY(100%)}
  .drawer.open{transform:none}
  .drawer-foot .btn{flex:1}
}

/* ---------- Modal / confirm ---------- */
.modal{
  position:fixed;left:50%;top:50%;width:min(430px,calc(100% - 32px));background:var(--surface);
  border-radius:var(--radius-xl);box-shadow:var(--shadow-lg);z-index:140;padding:24px;
  transform:translate(-50%,-46%) scale(.96);opacity:0;visibility:hidden;
  transition:opacity .18s,transform .18s,visibility .18s;
}
.modal.open{opacity:1;visibility:visible;transform:translate(-50%,-50%) scale(1)}
/* A modal has no fixed height, so a tall one used to run past both edges of the
   screen with no way to reach the buttons. */
.modal{max-height:calc(100dvh - 40px);overflow-y:auto;overscroll-behavior:contain}
.modal-icon{width:46px;height:46px;border-radius:14px;display:grid;place-items:center;margin-bottom:14px}
.modal-icon svg{width:23px;height:23px}
.modal-icon.danger{background:var(--danger-tint);color:var(--danger-text)}
.modal-icon.warn{background:var(--warn-tint);color:var(--warn-text)}
.modal-icon.brand{background:var(--brand-tint);color:var(--brand)}
.modal h3{margin-bottom:6px;font-size:17.5px}
.modal-actions{display:flex;gap:10px;justify-content:flex-end;margin-top:24px}

/* The head and foot of a markup modal.
 *
 * Nine modals across bookings, chatbots, reviews, tours and sites use these
 * class names, and there was no generic rule for either — only per-id rules
 * for three poster modals and one in the site builder. So everywhere else the
 * close button dropped below the title instead of sitting beside it, and the
 * footer buttons were left-aligned with a 5px gap.
 *
 * Written to match .drawer-head and .drawer-foot, which had these rules all
 * along — so a modal and a drawer now read as the same kind of thing.
 */
.modal-head{
  display:flex;align-items:flex-start;justify-content:space-between;gap:12px;
  margin:0 0 16px;
}
.modal-head h2{margin:0;font-size:16px;font-weight:700;min-width:0}
/* The close control keeps its size rather than being squeezed by a long
   title. */
.modal-head > .icon-btn,
.modal-head > button[aria-label="Close"]{flex:0 0 auto;margin:-4px -4px 0 0}

.modal-body{min-width:0}
/* Fields inside a modal, spaced like the ones inside a dialog. */
.modal-body .field + .field{margin-top:16px}
.modal-body .field{margin-bottom:0}

.modal-foot{
  display:flex;align-items:center;justify-content:flex-end;gap:10px;
  margin-top:24px;
}
/* On a narrow screen they stack, widest action last so the thumb lands on it.
   The same rule .modal-actions already had. */
@media (max-width:480px){
  .modal-foot{flex-direction:column-reverse}
  .modal-foot .btn{width:100%}
}
@media (max-width:480px){.modal-actions{flex-direction:column-reverse}.modal-actions .btn{width:100%}}

/* ---------- Toasts ---------- */
.toasts{position:fixed;right:16px;bottom:16px;z-index:200;display:flex;flex-direction:column;gap:10px;max-width:min(380px,calc(100% - 32px))}
.toast{
  display:flex;gap:12px;align-items:flex-start;background:var(--surface);border:1px solid var(--border);
  border-left:3px solid var(--brand);border-radius:var(--radius);padding:12px 14px;box-shadow:var(--shadow-md);
  animation:toastIn .22s cubic-bezier(.32,.72,0,1);
}
.toast.success{border-left-color:var(--ok-text)}
.toast.error{border-left-color:var(--danger-text)}
.toast.warn{border-left-color:var(--warn-text)}
.toast svg{width:19px;height:19px;flex:0 0 19px;margin-top:1px}
.toast.success svg{color:var(--ok)} .toast.error svg{color:var(--danger)}
.toast.warn svg{color:var(--warn)} .toast.info svg{color:var(--brand)}
.toast-msg{font-size:14px;color:var(--ink-2)}
.toast-x{margin-left:auto;border:0;background:transparent;color:var(--muted-text);cursor:pointer;padding:0 2px;font-size:16px;line-height:1}
@keyframes toastIn{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:none}}
@media (max-width:560px){.toasts{left:16px;right:16px;bottom:16px;max-width:none}}

/* ---------- Alerts ---------- */
.alert{display:flex;gap:10px;align-items:flex-start;padding:12px 14px;border-radius:var(--radius);font-size:14px;border:1px solid transparent}
.alert svg{width:18px;height:18px;flex:0 0 18px;margin-top:1px}
/* Alerts take their text and border from the tokens too.
 *
 * The background was already a token and switched with the theme; the text and
 * border were hardcoded light-mode colours and did not. So in dark mode an
 * error alert was a dark red panel carrying #912018 text — measured 1.87:1 —
 * and a warning was 1.81:1. An alert is the one thing on a page that must be
 * read, and these were the least readable things on it.
 *
 * The -text tokens already carry a light and a dark value each, so this fixes
 * both modes from one rule. The borders use the tints' own colour rather than
 * a separate hardcoded shade. */
.alert-error{background:var(--danger-tint);color:var(--danger-text);border-color:var(--danger)}
.alert-success{background:var(--ok-tint);color:var(--ok-text);border-color:var(--ok)}
.alert-warn{background:var(--warn-tint);color:var(--warn-text);border-color:var(--warn)}
.alert-info{background:var(--info-tint);color:var(--info-text);border-color:var(--info)}

/* ---------- Tabs ---------- */
.tabs{display:flex;gap:4px;border-bottom:1px solid var(--border);overflow-x:auto;margin-bottom:20px}
.tab{
  padding:10px 14px;border:0;background:transparent;font-family:var(--ff-body);font-size:14.5px;font-weight:600;
  color:var(--muted-text);cursor:pointer;border-bottom:2px solid transparent;margin-bottom:-1px;white-space:nowrap;
}
.tab:hover{color:var(--ink-2)}
.tab.active{color:var(--brand-700);border-bottom-color:var(--brand)}

/* ---------- Permission matrix ---------- */
.perm-row{border:1px solid var(--border);border-radius:var(--radius);padding:14px;margin-bottom:10px;background:var(--surface-2)}
.perm-head{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:10px}
.perm-name{font-weight:600;font-size:14.5px}
.perm-actions{display:flex;flex-wrap:wrap;gap:8px 16px}
.perm-actions .check{font-size:13.5px}

/* ---------- Auth pages ---------- */
.auth{min-height:100vh;display:grid;grid-template-columns:1.05fr .95fr}
.auth-aside{
  position:relative;overflow:hidden;padding:48px;display:flex;flex-direction:column;justify-content:space-between;
  background:linear-gradient(155deg,#2A2470 0%,var(--brand-700) 45%,var(--brand) 100%);color:#fff;
}
.auth-aside::after{
  content:"";position:absolute;inset:0;opacity:.5;pointer-events:none;
  background:
    radial-gradient(700px 340px at 82% 8%,rgba(255,255,255,.20),transparent 60%),
    radial-gradient(520px 320px at 8% 92%,rgba(122,90,248,.45),transparent 62%);
}
.auth-aside > *{position:relative;z-index:1}
.auth-quote{font-family:var(--ff-display);font-size:31px;line-height:1.28;font-weight:700;letter-spacing:-.02em;max-width:15ch}
.auth-quote em{font-style:normal;color:#C7C2FF}
.auth-points{display:flex;flex-direction:column;gap:12px;margin-top:24px;max-width:42ch}
.auth-point{display:flex;gap:12px;align-items:flex-start;font-size:14.5px;color:rgba(255,255,255,.86)}
.auth-point svg{width:19px;height:19px;flex:0 0 19px;margin-top:2px;color:#B9B2FF}
.auth-brand{display:flex;align-items:center;gap:12px;font-family:var(--ff-display);font-weight:800;font-size:16px}
.auth-brand .brand-mark{background:rgba(255,255,255,.16);box-shadow:none;backdrop-filter:blur(6px)}
.auth-main{display:flex;align-items:center;justify-content:center;padding:40px 24px;background:var(--surface)}
.auth-card{width:100%;max-width:392px}
.auth-card h1{font-size:26px;margin-bottom:8px}
.auth-card .sub{color:var(--muted-text);margin-bottom:24px;font-size:14.5px}
.auth-foot{margin-top:24px;text-align:center;font-size:14px;color:var(--muted-text)}
.auth-legal{margin-top:28px;font-size:12.5px;color:var(--muted-text);text-align:center}
@media (max-width:900px){
  .auth{grid-template-columns:1fr}
  .auth-aside{display:none}
  .auth-main{background:var(--bg);align-items:flex-start;padding-top:40px}
  .auth-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-xl);padding:24px;box-shadow:var(--shadow-sm)}
}
.pw-meter{height:5px;border-radius:999px;background:var(--surface-3);overflow:hidden;margin-top:8px}
.pw-meter span{display:block;height:100%;width:0;background:var(--danger);transition:width .25s,background .25s}

/* ---------- Installer wizard ---------- */
.wiz{min-height:100vh;display:flex;align-items:flex-start;justify-content:center;padding:40px 16px 64px}
.wiz-box{width:100%;max-width:680px}
.wiz-head{text-align:center;margin-bottom:24px}
.wiz-head .brand-mark{margin:0 auto 14px;width:46px;height:46px;border-radius:14px;font-size:19px}
.wiz-steps{display:flex;align-items:center;gap:6px;margin-bottom:24px}
.wiz-step{flex:1;display:flex;flex-direction:column;gap:8px}
.wiz-step .bar{height:5px;border-radius:999px;background:var(--border)}
.wiz-step.done .bar{background:var(--ok)}
.wiz-step.current .bar{background:var(--brand)}
.wiz-step .cap{font-size:11.5px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:var(--muted-text)}
.wiz-step.current .cap{color:var(--brand-700)}
.wiz-step.done .cap{color:var(--ok-text)}
.req-list{display:flex;flex-direction:column;gap:2px}
.req-item{display:flex;align-items:center;gap:12px;padding:12px 4px;border-bottom:1px solid var(--border);font-size:14.5px}
.req-item:last-child{border-bottom:0}
.req-item .state{margin-left:auto;font-weight:600;font-size:13px}
.req-ok{color:var(--ok-text)} .req-bad{color:var(--danger-text)} .req-warn{color:var(--warn-text)}
.req-item .ic{width:20px;height:20px;border-radius:50%;display:grid;place-items:center;flex:0 0 20px}
.req-item .ic svg{width:13px;height:13px}
.ic-ok{background:var(--ok-tint);color:var(--ok-text)} .ic-bad{background:var(--danger-tint);color:var(--danger-text)}
.ic-warn{background:var(--warn-tint);color:var(--warn-text)}

@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{animation-duration:.001ms !important;transition-duration:.001ms !important}
}
@media print{.sidebar,.topbar,.btn,.dropdown{display:none !important}.main{margin:0}}

/* Limits block inside the permissions drawer */
.limit-box{margin-top:16px;padding:16px;border:1px solid var(--border);border-radius:12px;background:var(--surface-2)}
.limit-box .pay-title{margin:0 0 2px;font-size:14px;font-weight:700}

/* Impersonation banner: deliberately loud, and the way back is always one tap. */
.imp-bar{position:sticky;top:0;z-index:60;display:flex;align-items:center;gap:12px;flex-wrap:wrap;
  padding:8px 16px;background:#B42318;color:#fff;font-size:13px}
.imp-text{flex:1 1 auto;min-width:0}
.imp-text strong{font-weight:700}
.imp-form{margin:0;flex:0 0 auto}
.imp-back{display:inline-flex;align-items:center;gap:6px;padding:6px 12px;border-radius:8px;border:0;
  background:#fff;color:#B42318;font:inherit;font-size:12.5px;font-weight:700;cursor:pointer}
.imp-back svg{width:15px;height:15px}
.imp-back:hover{background:#FEF3F2}

/* Built-in brand mark picker */
.brand-icons{display:flex;flex-wrap:wrap;gap:8px}
.brand-icon{position:relative;cursor:pointer}
.brand-icon input{position:absolute;opacity:0;width:0;height:0}
.brand-icon-mark{display:grid;place-items:center;width:42px;height:42px;border-radius:11px;
  border:1.5px solid var(--border);background:var(--surface);color:var(--muted);font-weight:700}
.brand-icon-mark svg{width:20px;height:20px}
.brand-icon input:checked + .brand-icon-mark{border-color:var(--brand);color:var(--brand);
  background:var(--brand-tint,rgba(90,79,224,.12))}
.brand-icon input:focus-visible + .brand-icon-mark{outline:2px solid var(--brand);outline-offset:2px}

/* ---- your own allowance, shown to anyone who has one ---- */
.allowance{display:flex;gap:10px;flex-wrap:wrap;margin-bottom:14px}
.allowance-item{display:flex;flex-direction:column;gap:4px;min-width:150px;padding:10px 12px;
  border:1px solid var(--border);border-radius:10px;background:var(--surface)}
.allowance-label{font-size:11px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;color:var(--muted-text)}
.allowance-value{font-size:15px;font-weight:600}
.allowance-bar{display:block;height:4px;border-radius:2px;background:rgba(127,127,127,.18);overflow:hidden}
.allowance-bar>span{display:block;height:100%;border-radius:2px;background:var(--brand)}

/* ---- what this account can do ---- */
.access-list{display:flex;flex-direction:column;gap:8px}
.access-row{display:flex;align-items:center;gap:12px;flex-wrap:wrap;padding:8px 12px;
  border:1px solid var(--border);border-radius:9px;background:var(--surface)}
.access-mod{flex:1 1 auto;font-size:13.5px;font-weight:600;min-width:110px}
.access-acts{display:flex;gap:4px;flex-wrap:wrap}
.access-chip{padding:4px 8px;border-radius:20px;font-size:11px;font-weight:600;
  background:var(--brand-tint,rgba(90,79,224,.12));color:var(--brand)}

/* ---- standee template library ---- */
.asset-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(190px,1fr));gap:14px}
.asset-card{display:flex;flex-direction:column;border:1px solid var(--border);border-radius:12px;
  overflow:hidden;background:var(--surface)}
.asset-card.asset-off{opacity:.55}
.asset-thumb{aspect-ratio:2/3;background:var(--surface-3);display:flex;align-items:center;justify-content:center}
.asset-thumb img{width:100%;height:100%;object-fit:cover}
.asset-meta{display:flex;flex-direction:column;gap:2px;padding:8px 12px 4px}
.asset-name{font-size:13px;font-weight:600;line-height:1.3;word-break:break-word}
.asset-dim{font-size:11px;color:var(--muted-text)}
.asset-actions{display:flex;gap:6px;padding:8px 12px 12px}
.asset-actions .btn{flex:1 1 auto}

/* layout editor */
.drawer-wide{width:min(1040px,96vw);max-width:96vw}
.layout-editor{display:grid;grid-template-columns:minmax(0,1.35fr) minmax(240px,1fr);gap:16px;align-items:start}
@media (max-width:840px){.layout-editor{grid-template-columns:1fr}}
.layout-stage{position:relative;width:100%;max-height:62vh;background:var(--surface-3);
  border:1px solid var(--border);border-radius:10px;overflow:hidden;touch-action:none}
.layout-stage img{width:100%;height:100%;object-fit:contain;display:block;pointer-events:none;user-select:none}
/* The box shows what will print: the real code, the real wording, at the real
   size. An empty outline could not answer "will this fit?" or "is the code big
   enough to scan?", which is the whole reason for laying anything out. */
/* Outline, not border: a border would sit inside the box and make the code look
   bigger than it prints. An outline is drawn outside the box and takes no space,
   so what you see is exactly the printed size. */
.lay-box{position:absolute;outline:1px dashed rgba(90,79,224,.7);outline-offset:1px;
  border:0;border-radius:2px;background:none;cursor:move;touch-action:none}
.lay-box.lay-active{outline:2px solid rgba(90,79,224,.95);outline-offset:2px}
/* The QR box in particular carries no tint, so its contrast on the artwork is
   the contrast it will actually print at. */
#lay-qr{background:none}
.lay-box.lay-off{opacity:.28;border-style:dotted}
.lay-tag{position:absolute;top:-9px;left:6px;padding:1px 6px;border-radius:5px;font-size:10px;
  font-weight:600;background:var(--brand);color:#fff;white-space:nowrap;pointer-events:none}
.lay-handle{position:absolute;right:-8px;bottom:-8px;width:15px;height:15px;border-radius:50%;
  background:var(--brand);border:2px solid #fff;cursor:nwse-resize;
  box-shadow:0 1px 4px rgba(0,0,0,.35);z-index:4}

/* Alignment guides. They appear only while a block is being moved, and only
   when it is actually lined up, so they confirm rather than clutter. */
.lay-guide{position:absolute;background:#FF3D71;opacity:0;pointer-events:none;z-index:6;
  transition:opacity .09s}
.lay-guide.on{opacity:.9}
.lay-guide-v{top:0;bottom:0;width:1px;margin-left:-.5px}
.lay-guide-h{left:0;right:0;height:1px;margin-top:-.5px}
/* A readable size read-out while dragging: the printed pixel size, not the
   on-screen one, because that is the number that decides whether it scans. */
.lay-readout{position:absolute;padding:4px 8px;border-radius:6px;background:rgba(17,24,39,.9);
  color:#fff;font-size:10.5px;font-weight:600;white-space:nowrap;pointer-events:none;
  z-index:7;opacity:0;transition:opacity .12s}
.lay-readout.on{opacity:1}
.layout-side{display:flex;flex-direction:column;gap:12px}

/* ---------- Tutorials ---------- */
.tut-bar{display:flex;gap:12px;align-items:center;justify-content:space-between;flex-wrap:wrap;margin-bottom:16px}
.tut-filters{display:flex;gap:8px;flex:1 1 260px;min-width:0}
.tut-search{flex:1 1 auto;min-width:0}
.tut-views{display:flex;gap:4px;padding:4px;border:1px solid var(--border);border-radius:10px;background:var(--surface)}
.tut-view{display:inline-flex;align-items:center;gap:6px;padding:6px 10px;border:0;border-radius:7px;
  background:none;color:var(--muted-text);font:inherit;font-size:12.5px;cursor:pointer}
.tut-view:hover{color:var(--text)}
.tut-view.selected{background:var(--brand-tint,rgba(90,79,224,.12));color:var(--brand);font-weight:600}
@media (max-width:620px){.tut-view span{display:none}.tut-view{padding:8px}}

.tut-wrap{display:grid;gap:16px}
.tut{position:relative;display:flex;flex-direction:column;border:1px solid var(--border);
  border-radius:14px;background:var(--surface);overflow:hidden;cursor:pointer;
  transition:border-color .14s,box-shadow .14s,transform .14s}
.tut:hover{border-color:var(--brand);box-shadow:0 8px 26px rgba(16,24,40,.09);transform:translateY(-1px)}
.tut-thumb{position:relative;aspect-ratio:16/9;background:var(--surface-3);overflow:hidden;flex:0 0 auto}
.tut-thumb img{width:100%;height:100%;object-fit:cover}
.tut-thumb-blank{display:grid;place-items:center}
.tut-glyph{color:var(--muted-text);opacity:.5}
.tut-glyph svg{width:34px;height:34px}
.tut-play{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);width:46px;height:46px;
  border-radius:50%;display:grid;place-items:center;background:rgba(0,0,0,.55);color:#fff;
  backdrop-filter:blur(3px);transition:transform .16s,background .16s}
.tut:hover .tut-play{transform:translate(-50%,-50%) scale(1.09);background:var(--brand)}
.tut-dur{position:absolute;right:8px;bottom:8px;padding:2px 8px;border-radius:5px;
  background:rgba(0,0,0,.72);color:#fff;font-size:11px;font-weight:600;font-variant-numeric:tabular-nums}
.tut-meta{display:flex;flex-direction:column;gap:6px;padding:12px 14px 14px;flex:1 1 auto;min-width:0}
.tut-title{margin:0;font-size:14.5px;font-weight:600;line-height:1.35}
.tut-sum{margin:0;font-size:12.5px;color:var(--muted-text);line-height:1.5;
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.tut-tags{display:flex;gap:4px;flex-wrap:wrap;margin-top:auto;padding-top:4px}
.tut-chip{padding:2px 8px;border-radius:20px;font-size:10.5px;font-weight:600;
  background:var(--brand-tint,rgba(90,79,224,.12));color:var(--brand)}
.tut-chip.ghost{background:var(--surface-3);color:var(--muted-text)}
.tut-chip.warn{background:var(--warn-tint);color:var(--warn-text)}
.tut-admin{display:flex;gap:6px;padding:0 14px 12px}
.tut-admin .btn{flex:1 1 auto}

/* --- the four layouts --- */
[data-view="card"]{grid-template-columns:repeat(auto-fill,minmax(270px,1fr))}
[data-view="grid"]{grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:12px}
[data-view="grid"] .tut-sum{display:none}
[data-view="grid"] .tut-title{font-size:13px}
[data-view="grid"] .tut-meta{padding:10px 12px 12px}

[data-view="list"]{grid-template-columns:1fr;gap:10px}
[data-view="list"] .tut{flex-direction:row;align-items:stretch}
[data-view="list"] .tut-thumb{width:190px;flex:0 0 190px;aspect-ratio:16/10}
[data-view="list"] .tut-meta{padding:12px 16px}
[data-view="list"] .tut-admin{align-items:center;padding:12px 14px 12px 0;flex:0 0 auto}
[data-view="list"] .tut-play{width:38px;height:38px}
@media (max-width:560px){
  [data-view="list"] .tut{flex-direction:column}
  [data-view="list"] .tut-thumb{width:100%;flex:0 0 auto;aspect-ratio:16/9}
  [data-view="list"] .tut-admin{padding:0 14px 12px}
}

[data-view="compact"]{grid-template-columns:1fr;gap:0;border:1px solid var(--border);
  border-radius:12px;overflow:hidden;background:var(--surface)}
[data-view="compact"] .tut{flex-direction:row;align-items:center;border:0;border-radius:0;
  border-bottom:1px solid var(--border);gap:12px;padding:0}
[data-view="compact"] .tut:last-child{border-bottom:0}
[data-view="compact"] .tut:hover{transform:none;box-shadow:none;background:var(--surface-2)}
[data-view="compact"] .tut-thumb{width:74px;flex:0 0 74px;aspect-ratio:1;border-radius:0}
[data-view="compact"] .tut-play{width:28px;height:28px}
[data-view="compact"] .tut-play svg{width:14px;height:14px}
[data-view="compact"] .tut-dur{display:none}
[data-view="compact"] .tut-sum{-webkit-line-clamp:1}
[data-view="compact"] .tut-meta{padding:10px 0}
[data-view="compact"] .tut-admin{padding:0 12px;flex:0 0 auto}

/* --- player --- */
.tut-overlay{position:fixed;inset:0;z-index:150;display:grid;place-items:center;padding:20px;
  background:rgba(8,10,18,.78);backdrop-filter:blur(6px);opacity:0;visibility:hidden;
  transition:opacity .2s,visibility .2s}
.tut-overlay.open{opacity:1;visibility:visible}
.tut-stage{width:min(980px,100%);max-height:calc(100dvh - 40px);display:flex;flex-direction:column;
  min-height:0;background:var(--surface);border-radius:16px;overflow:hidden;box-shadow:var(--shadow-lg);
  transform:scale(.97);transition:transform .2s}
.tut-overlay.open .tut-stage{transform:none}
.tut-stage-head{display:flex;align-items:center;justify-content:space-between;gap:12px;
  padding:12px 16px;border-bottom:1px solid var(--border);flex:0 0 auto}
.tut-stage-head h2{margin:0;font-size:15.5px;font-weight:600;line-height:1.35;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.tut-stage-body{flex:1 1 auto;min-height:0;overflow-y:auto;overscroll-behavior:contain;
  display:flex;flex-direction:column;background:#000}
.tut-video,.tut-frame{width:100%;border:0;display:block;background:#000}
.tut-video{max-height:calc(100dvh - 140px)}
.tut-frame{aspect-ratio:16/9}
.tut-frame.tut-doc{aspect-ratio:auto;height:min(76vh,900px)}
.tut-openout{margin:12px auto;flex:0 0 auto}
body.no-scroll{overflow:hidden}

/* ---------- Festival greetings ---------- */
.fx-list{display:flex;flex-direction:column;gap:8px}
.fx-row{display:flex;align-items:center;gap:12px;padding:12px;border:1px solid var(--border);
  border-radius:11px;background:var(--surface);flex-wrap:wrap}
.fx-swatch{width:34px;height:34px;border-radius:9px;flex:0 0 auto}
.fx-meta{display:flex;flex-direction:column;gap:2px;flex:1 1 190px;min-width:0}
.fx-name{font-size:13.5px;font-weight:600}
.fx-when{font-size:11.5px;color:var(--muted-text)}
.fx-warn{font-size:11px;font-weight:600;color:var(--warn-text)}
.fx-chip{padding:4px 8px;border-radius:20px;font-size:11px;font-weight:600;flex:0 0 auto;
  background:var(--surface-3);color:var(--muted-text)}
.fx-chip.warn{background:var(--warn-tint);color:var(--warn-text)}
/* flows.php marks a live flow with .ok and nothing defined it, so a live
   flow and a draft looked identical — the one thing the chip was there to
   tell apart. */
.fx-chip.ok{background:var(--ok-tint);color:var(--ok-text)}
.fx-admin{display:flex;gap:6px;flex:0 0 auto}
@media (max-width:620px){.fx-admin{width:100%}.fx-admin .btn{flex:1}}

/* A bare toggle with no label beside it. Named apart from .switch because that
   one wraps its own label text, and a rule matching every span inside it lands
   on the caption as well as the track — which is what silently broke the Access
   settings layout. */
.toggle{position:relative;display:inline-block;width:40px;height:23px;flex:0 0 auto;cursor:pointer}
.toggle input{position:absolute;opacity:0;width:0;height:0}
.toggle > span{position:absolute;inset:0;border-radius:999px;background:var(--surface-3);
  transition:background .18s}
.toggle > span::after{content:"";position:absolute;top:3px;left:3px;width:17px;height:17px;border-radius:50%;
  background:#fff;box-shadow:0 1px 3px rgba(0,0,0,.25);transition:transform .18s}
.toggle input:checked + span{background:var(--brand)}
.toggle input:checked + span::after{transform:translateX(17px)}
.toggle input:focus-visible + span{box-shadow:0 0 0 3px var(--brand-tint,rgba(90,79,224,.3))}

.snip{display:flex;align-items:center;gap:8px;padding:10px 12px;border:1px solid var(--border);
  border-radius:10px;background:var(--surface-2);flex-wrap:wrap}
.snip code{flex:1 1 260px;min-width:0;font-size:12px;line-height:1.5;word-break:break-all;
  font-family:ui-monospace,SFMono-Regular,Menlo,monospace}

.fx-edit{display:grid;grid-template-columns:minmax(0,1fr) minmax(230px,300px);gap:20px;align-items:start}
@media (max-width:860px){.fx-edit{grid-template-columns:1fr}}
.fx-fields{display:flex;flex-direction:column;gap:12px}
.fx-preview{display:flex;flex-direction:column;gap:8px;position:sticky;top:0}

/* ---------- Plans and billing ---------- */
.plan-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(258px,1fr));gap:16px}
.plan{position:relative;display:flex;flex-direction:column;gap:8px;padding:24px 20px;
  border:1px solid var(--border);border-radius:16px;background:var(--surface)}
.plan-featured{border-color:var(--brand);box-shadow:0 0 0 3px var(--brand-tint,rgba(90,79,224,.12))}
.plan-flag{position:absolute;top:-10px;left:20px;padding:4px 10px;border-radius:20px;font-size:11px;
  font-weight:700;background:var(--brand);color:#fff}
.plan-name{font-size:16px;font-weight:700}
.plan-tag{font-size:12.5px;color:var(--muted-text);line-height:1.45}
.plan-price{display:flex;align-items:baseline;gap:8px;margin:4px 0;flex-wrap:wrap}
.plan-amount{font-size:29px;font-weight:700;letter-spacing:-.02em}
.plan-per{font-size:12px;color:var(--muted-text)}
.plan-features{list-style:none;display:flex;flex-direction:column;gap:8px;margin:4px 0 0;padding:0;flex:1 1 auto}
.plan-features li{display:flex;gap:8px;align-items:flex-start;font-size:12.5px;line-height:1.45}
.plan-features svg{width:15px;height:15px;flex:0 0 auto;margin-top:1px;color:var(--ok)}
.plan-foot{display:flex;flex-direction:column;gap:8px;margin-top:14px}
.plan-foot > .btn{width:100%}
.plan-admin{display:flex;gap:6px}
.plan-admin .btn{flex:1}
.plan-off{font-size:11px;font-weight:600;color:var(--warn-text);text-align:center}

.pay-list{display:flex;flex-direction:column;gap:8px}
.pay-row{display:flex;align-items:center;gap:12px;padding:12px;border:1px solid var(--border);
  border-radius:11px;background:var(--surface);flex-wrap:wrap}
.pay-status{padding:4px 8px;border-radius:20px;font-size:11px;font-weight:700;flex:0 0 auto}
.pay-pending{background:var(--warn-tint);color:var(--warn-text)}
.pay-verified{background:var(--ok-tint);color:var(--ok-text)}
.pay-rejected{background:var(--danger-tint);color:var(--danger-text)}
.pay-meta{display:flex;flex-direction:column;gap:2px;flex:1 1 220px;min-width:0}
.pay-plan{font-size:13.5px;font-weight:600}
.pay-ref{font-size:11.5px;color:var(--muted-text);word-break:break-word}
.pay-note{font-size:11.5px;color:var(--warn-text)}
.pay-proof{flex:0 0 auto;width:54px;height:54px;border-radius:8px;overflow:hidden;border:1px solid var(--border)}
.pay-proof img{width:100%;height:100%;object-fit:cover}
.pay-actions{display:flex;gap:6px;flex:0 0 auto}
@media (max-width:620px){.pay-actions{width:100%}.pay-actions .btn{flex:1}}

.pay-head{display:flex;align-items:baseline;justify-content:space-between;gap:12px;
  padding-bottom:12px;margin-bottom:14px;border-bottom:1px solid var(--border);flex-wrap:wrap}
.pay-head-plan{font-size:15px;font-weight:600}
.pay-head-amount{font-size:23px;font-weight:700}
.pay-qr{display:flex;flex-direction:column;align-items:center;gap:8px;padding:16px;
  border:1px solid var(--border);border-radius:14px;background:var(--surface-2)}
.pay-qr img{width:220px;height:220px;border-radius:10px;background:#fff;padding:8px}
.pay-apps{display:grid;grid-template-columns:repeat(auto-fit,minmax(96px,1fr));gap:8px;margin:12px 0}
.pay-app{display:flex;align-items:center;justify-content:center;padding:10px;border-radius:10px;
  border:1px solid var(--border);background:var(--surface);font-size:12.5px;font-weight:600;
  text-decoration:none;color:inherit;text-transform:capitalize}
.pay-app:hover{border-color:var(--brand);color:var(--brand)}
.pay-vpa{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.pay-vpa code{flex:1 1 auto;min-width:0;padding:8px 10px;border-radius:8px;background:var(--surface-3);
  font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:13px;word-break:break-all}
.sep{border:0;border-top:1px solid var(--border);margin:16px 0}
.pay-gateways{display:flex;flex-direction:column;gap:8px;margin-bottom:16px}
.pay-gw-form{display:block}
.pay-gw{width:100%}

/* ---------- colour field ---------- */
.colour-field{display:flex;align-items:center;gap:8px}
.colour-chip{position:relative;flex:0 0 auto;width:34px;height:34px;border-radius:9px;overflow:hidden;
  border:1px solid var(--border);cursor:pointer;background:var(--surface)}
/* An empty value shows the chequerboard, so "no colour set" reads differently
   from "white". */
.colour-chip.is-empty::after{content:"";position:absolute;inset:0;
  background-image:linear-gradient(45deg,var(--surface-3) 25%,transparent 25%),
    linear-gradient(-45deg,var(--surface-3) 25%,transparent 25%),
    linear-gradient(45deg,transparent 75%,var(--surface-3) 75%),
    linear-gradient(-45deg,transparent 75%,var(--surface-3) 75%);
  background-size:10px 10px;background-position:0 0,0 5px,5px -5px,-5px 0}
.colour-chip input[type="color"]{position:absolute;inset:-4px;width:calc(100% + 8px);
  height:calc(100% + 8px);border:0;padding:0;background:none;cursor:pointer}
.colour-chip input[type="color"]::-webkit-color-swatch-wrapper{padding:0}
.colour-chip input[type="color"]::-webkit-color-swatch{border:0}
.colour-text{flex:1 1 auto;min-width:0;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;
  font-size:13px;text-transform:uppercase}
.colour-clear{flex:0 0 auto;width:28px;height:28px;border:0;border-radius:7px;background:none;
  color:var(--muted-text);font-size:18px;line-height:1;cursor:pointer}
.colour-clear:hover{background:var(--surface-3);color:var(--text)}

/* ---------- standee layout preview contents ---------- */
.lay-qr-img{width:100%;height:100%;object-fit:contain;display:block;pointer-events:none;
  border-radius:2px;background:#fff}
.lay-box.lay-media,.lay-box.lay-text{display:flex;align-items:center;justify-content:center;
  overflow:hidden;padding:2px}
.lay-logo-img{max-width:100%;max-height:100%;object-fit:contain;pointer-events:none}
.lay-logo-img:not([src]),.lay-logo-img[src=""]{display:none}
/* Full opacity. At .75 this measured 3.29:1 against the page — the fade was
   doing nothing a lighter weight would not do, and it made the one word in the
   box hard to read. */
.lay-fallback{font-size:11px;font-weight:800;letter-spacing:.12em;color:var(--brand-700);
  pointer-events:none}
/* Font size is set from the layout by script, so the wrapping you see here is
   the wrapping the print will have. */
.lay-sample{display:block;width:100%;text-align:center;line-height:1.3;pointer-events:none;
  overflow:hidden;word-break:break-word}
.lay-box.lay-off .lay-sample,.lay-box.lay-off .lay-logo-img,.lay-box.lay-off .lay-fallback{opacity:.4}
.lay-missing{position:absolute;inset:auto 12px 12px;margin:0;padding:8px 12px;border-radius:9px;
  background:var(--warn-tint);color:var(--warn-text);font-size:12px;line-height:1.45;z-index:5}
.lay-foot{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.lay-foot-gap{flex:1 1 auto}
.link-btn{border:0;background:none;padding:0;font:inherit;color:var(--brand);
  text-decoration:underline;cursor:pointer}
.range{width:100%;accent-color:var(--brand)}
.num{float:right;font-weight:600;font-variant-numeric:tabular-nums;color:var(--muted-text)}

/* ---------- standee block list ---------- */
.lay-blocks{display:flex;flex-direction:column;gap:4px;border:1px solid var(--border);
  border-radius:10px;padding:4px;background:var(--surface)}
.lay-block{display:flex;align-items:center;gap:8px;padding:2px 4px 2px 2px;border-radius:7px}
.lay-block.on{background:var(--brand-tint,rgba(90,79,224,.12))}
.lay-block-pick{flex:1 1 auto;min-width:0;display:flex;align-items:center;gap:8px;padding:8px;
  border:0;border-radius:6px;background:none;font:inherit;font-size:13px;text-align:left;cursor:pointer;color:inherit}
.lay-block.on .lay-block-pick{font-weight:700;color:var(--brand)}
.lay-block-off .lay-block-name{opacity:.45;text-decoration:line-through}
.lay-block-fixed{flex:0 0 auto;padding-right:8px;font-size:10.5px;color:var(--muted-text)}
.toggle-sm{width:34px;height:20px}
.toggle-sm > span::after{width:14px;height:14px}
.toggle-sm input:checked + span::after{transform:translateX(14px)}
.grid-split{grid-template-columns:1.5fr 1fr}
@media (max-width:900px){.grid-split{grid-template-columns:1fr}}

/* ---------- settings layout ----------
   One long column made every section stretch the full width, so a two-field
   card looked as heavy as a twelve-field one. Masonry columns let each card take
   the height it needs while the page stays balanced. */
.settings-grid{display:grid;gap:16px;align-items:start}
@media (min-width:1080px){
  .settings-grid{grid-template-columns:repeat(2,minmax(0,1fr))}
  /* The payment and email cards hold the most fields, so they are allowed to
     run full width rather than being squeezed into half. */
  .settings-grid > .card.card-wide{grid-column:1/-1}
}
.settings-grid > .card{margin:0}
.settings-grid .card-body{display:flex;flex-direction:column;gap:14px}
.settings-grid .form-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:14px}
.settings-grid .field{min-width:0}
.settings-grid h3.pay-title{margin:6px 0 0;padding-top:14px;border-top:1px solid var(--border);
  font-size:13.5px;font-weight:700}

/* ---------- close buttons ----------
   These were written as bare <button>&times;</button> with a class that had no
   rules at all, so they inherited the browser's default button chrome: a grey
   box with a mis-centred glyph. The multiplication sign also sits slightly high
   in most fonts, which is why line-height is pinned rather than left to default. */
.icon-btn{display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;
  width:34px;height:34px;padding:0;border:0;border-radius:9px;background:transparent;
  color:var(--muted);font-size:22px;line-height:1;cursor:pointer;
  transition:background .14s,color .14s}
.icon-btn:hover{background:var(--surface-3);color:var(--text)}
.icon-btn:active{transform:scale(.94)}
.icon-btn:focus-visible{outline:2px solid var(--brand);outline-offset:2px}
.icon-btn svg{width:18px;height:18px}

/* The one on the festival greeting sits on artwork rather than on a panel, so it
   carries its own backing to stay legible over anything. */
.fest-x{display:inline-flex;align-items:center;justify-content:center;
  transition:background .14s,transform .14s}
.fest-x:active{transform:scale(.94)}
.fest-x:focus-visible{outline:2px solid #fff;outline-offset:2px}

/* ---------- upload zones ----------
   These were defined in the builder's own stylesheet while four pages used
   them, so everywhere except the builder fell back to a bare file input. */
.dropzone{display:flex;align-items:center;gap:12px;padding:16px;border:1.5px dashed var(--border);
  border-radius:12px;background:var(--surface-muted,rgba(127,127,127,.04));cursor:pointer;
  transition:border-color .14s,background .14s}
.dropzone:hover,.dropzone:focus-within{border-color:var(--brand);background:var(--brand-tint,rgba(90,79,224,.06))}
.dropzone.dz-over{border-color:var(--brand);background:var(--brand-tint,rgba(90,79,224,.12));border-style:solid}
.dropzone.dz-busy{opacity:.6;pointer-events:none}
.dz-icon{display:flex;align-items:center;justify-content:center;flex:0 0 auto;width:40px;height:40px;
  border-radius:10px;background:var(--brand-tint,rgba(90,79,224,.12));color:var(--brand)}
.dz-text{display:flex;flex-direction:column;gap:2px;flex:1 1 auto;min-width:0}
.dz-title{font-size:13.5px;font-weight:600;line-height:1.3}
.dz-hint{font-size:11.5px;color:var(--muted-text);line-height:1.35}
.dz-btn{flex:0 0 auto;height:34px;padding:0 12px;font-size:12.5px}
@media (max-width:520px){
  .dropzone{flex-wrap:wrap}
  .dz-btn{width:100%}
}

/* ---- standee template strip and custom paper size ---- */
.std-strip{display:grid;grid-template-columns:repeat(auto-fill,minmax(96px,1fr));gap:8px;margin-top:10px}
.std-chip{display:flex;flex-direction:column;gap:4px;padding:4px;border:1.5px solid var(--border);
  border-radius:10px;background:var(--surface);cursor:pointer;text-align:center}
.std-chip:hover{border-color:var(--brand)}
.std-chip.selected{border-color:var(--brand);box-shadow:0 0 0 3px var(--brand-tint,rgba(90,79,224,.12))}
.std-chip img{width:100%;aspect-ratio:2/3;object-fit:cover;border-radius:6px;display:block}
.std-chip span{font-size:11px;line-height:1.25;word-break:break-word}
.size-pair{display:flex;align-items:center;gap:8px}
.size-pair .input{flex:1 1 0;min-width:0}
.size-x{color:var(--muted-text);font-size:13px}

/* ---- an upload that already has a file ---- */
.dz-current{display:flex;align-items:center;gap:10px;margin-top:8px;padding:8px 10px;
  border:1px solid var(--border);border-radius:10px;background:var(--surface-2)}
.dz-current img{width:40px;height:40px;object-fit:contain;border-radius:7px;flex:0 0 auto;
  background-image:linear-gradient(45deg,var(--surface-3) 25%,transparent 25%),
    linear-gradient(-45deg,var(--surface-3) 25%,transparent 25%),
    linear-gradient(45deg,transparent 75%,var(--surface-3) 75%),
    linear-gradient(-45deg,transparent 75%,var(--surface-3) 75%);
  background-size:8px 8px;background-position:0 0,0 4px,4px -4px,-4px 0}
.dz-current-text{flex:1 1 auto;min-width:0;font-size:12.5px;color:var(--muted-text)}

/* ---------- plan feature picker ---------- */
.feat-pick{display:flex;flex-direction:column;gap:14px;max-height:340px;overflow-y:auto;
  overscroll-behavior:contain;padding:12px;border:1px solid var(--border);border-radius:11px;
  background:var(--surface-2)}
.feat-group{display:flex;flex-direction:column;gap:4px}
.feat-group-name{font-size:11px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;
  color:var(--muted-text)}
.feat-line{display:flex;align-items:flex-start;gap:8px;padding:4px 6px;border-radius:7px;
  font-size:12.5px;line-height:1.4;cursor:pointer}
.feat-line:hover{background:var(--surface-3)}
.feat-line input{margin-top:2px;flex:0 0 auto;accent-color:var(--brand)}

/* ---------- free trial ---------- */
.trial-card{display:flex;align-items:center;gap:16px;padding:16px 20px;border-radius:14px;
  background:linear-gradient(120deg,var(--brand),color-mix(in srgb,var(--brand) 55%,#22D3EE));
  color:#fff;flex-wrap:wrap}
.trial-text{display:flex;flex-direction:column;gap:4px;flex:1 1 260px;min-width:0}
.trial-title{font-size:17px;font-weight:700}
.trial-sub{font-size:12.5px;opacity:.92;line-height:1.45}
.trial-card .btn{flex:0 0 auto;background:#fff;color:var(--brand);border-color:#fff}
.trial-card .btn:hover{background:rgba(255,255,255,.9)}

/* ---------- responsive floor ----------
   Nothing should ever push the page sideways: on a phone a horizontal scroll
   hides content rather than revealing it. Grid and flex children need the
   explicit min-width:0 because their default of auto refuses to shrink. */
.grid,.grid-2,.grid-3,.settings-grid,.form-grid,.plan-grid,.tut-wrap{min-width:0}
.grid > *,.grid-2 > *,.grid-3 > *,.settings-grid > *,.form-grid > *{min-width:0}
.card-head{min-width:0;gap:10px}
.card-head h2{min-width:0;overflow-wrap:anywhere}
@media (max-width:400px){
  .grid-2,.grid-3{grid-template-columns:1fr}
}

/* ---------- 360 tour viewer ---------- */
.tv{position:relative;width:100%;height:100%;overflow:hidden;background:#1e2128;
  touch-action:none;user-select:none}
.tv-canvas{display:block;width:100%;height:100%;cursor:grab}
.tv-dragging .tv-canvas{cursor:grabbing}
.tv-fail{padding:28px;color:#e5e7eb;font-size:13px;line-height:1.6;text-align:center}
.tv-spots{position:absolute;inset:0;pointer-events:none}
/* A hotspot the projection has hidden is display:none, so its label goes with
   it. Without this a marker behind the camera left its tooltip parked in the
   corner of the screen. */
.tv-spot[style*="display: none"] .tv-spot-label{display:none}
.tv-spot{position:absolute;transform:translate(-50%,-50%) scale(var(--sc,1));
  width:34px;height:34px;padding:0;border:0;border-radius:50%;cursor:pointer;
  background:none;pointer-events:auto;color:var(--hc,#fff)}
.tv-spot::before{content:"";position:absolute;inset:0;border-radius:50%;
  border:2.5px solid currentColor;background:rgba(0,0,0,.28);backdrop-filter:blur(2px);
  box-shadow:0 2px 10px rgba(0,0,0,.4);transition:transform .16s}
.tv-spot::after{content:"";position:absolute;inset:0;border-radius:50%;
  border:2px solid currentColor;opacity:.55;animation:tv-pulse 2.2s ease-out infinite}
.tv-spot:hover::before{transform:scale(1.16)}
@keyframes tv-pulse{0%{transform:scale(1);opacity:.55}70%{transform:scale(1.9);opacity:0}100%{opacity:0}}
.tv-spot-dot::after{display:none}
.tv-spot-ring::before{background:none}
.tv-spot-label{position:absolute;left:50%;top:calc(100% + 7px);transform:translateX(-50%);
  padding:4px 8px;border-radius:7px;background:rgba(0,0,0,.78);color:#fff;
  font-size:11.5px;font-weight:600;white-space:nowrap;opacity:0;pointer-events:none;
  transition:opacity .16s}
.tv-spot:hover .tv-spot-label,.tv-spot:focus-visible .tv-spot-label{opacity:1}
.tv-loading .tv-canvas{opacity:.55;transition:opacity .2s}
@media (prefers-reduced-motion:reduce){.tv-spot::after{animation:none}}

/* ---------- tours list ---------- */
.tour-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(268px,1fr));gap:16px}
.tour-card{display:flex;flex-direction:column;border:1px solid var(--border);border-radius:14px;
  background:var(--surface);overflow:hidden;min-width:0}
.tour-cover{position:relative;display:block;aspect-ratio:16/9;background:var(--surface-3);overflow:hidden}
.tour-cover img{width:100%;height:100%;object-fit:cover;display:block;transition:transform .25s}
.tour-card:hover .tour-cover img{transform:scale(1.04)}
.tour-empty{position:absolute;inset:0;display:grid;place-items:center;color:var(--muted-text);opacity:.45}
.tour-empty svg{width:30px;height:30px}
.tour-flag{position:absolute;top:8px;left:8px;padding:2px 8px;border-radius:20px;
  background:var(--warn-tint);color:var(--warn-text);font-size:10.5px;font-weight:700}
/* Published is the good state, so it gets the good colour. Same badge in the
   same place, so the two can be told apart at a glance across a grid. */
.tour-flag.is-live{background:var(--ok-tint);color:var(--ok-text)}
.tour-meta{display:flex;flex-direction:column;gap:4px;padding:12px 12px 8px;min-width:0}
.tour-name{font-size:14px;font-weight:700;color:inherit;text-decoration:none;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.tour-name:hover{color:var(--brand)}
.tour-facts{font-size:11.5px;color:var(--muted-text)}
.tour-link{display:flex;align-items:center;gap:8px;margin:0 12px 10px;padding:6px 8px;
  border-radius:8px;background:var(--surface-2);min-width:0}
.tour-link code{flex:1 1 auto;min-width:0;font-size:11px;overflow:hidden;text-overflow:ellipsis;
  white-space:nowrap;font-family:ui-monospace,SFMono-Regular,Menlo,monospace}
.tour-actions{display:flex;gap:6px;padding:0 12px 12px;flex-wrap:wrap;margin-top:auto}
.tour-actions .btn{flex:1 1 auto}

/* ---------- tour builder ---------- */
.tb{display:grid;grid-template-columns:250px minmax(0,1fr) 290px;gap:16px;align-items:start}
@media (max-width:1200px){.tb{grid-template-columns:230px minmax(0,1fr)}
  .tb-side{grid-column:1/-1}}
@media (max-width:760px){.tb{grid-template-columns:1fr}}

.tb-scenes,.tb-side{border:1px solid var(--border);border-radius:13px;background:var(--surface);
  padding:14px;min-width:0}
.tb-panel-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px}
.tb-panel-head h2{font-size:13px;font-weight:700;margin:0}

/* In a 250px column the icon, text and button cannot sit in a row without the
   words breaking mid-phrase, so this one stacks. */
.tb-drop{padding:14px 12px;flex-direction:column;text-align:center;gap:8px}
.tb-drop .dz-text{align-items:center;text-align:center}
.tb-drop .dz-title,.tb-drop .dz-hint{white-space:nowrap}
.tb-drop .dz-btn{width:100%}
.tb-progress{height:4px;border-radius:2px;background:var(--surface-3);overflow:hidden;margin-top:8px}
.tb-progress > span{display:block;height:100%;width:0;background:var(--brand);transition:width .25s}

.tb-list{display:flex;flex-direction:column;gap:6px;margin-top:12px;
  max-height:52vh;overflow-y:auto;overscroll-behavior:contain;min-height:0}
.tb-scene{display:flex;align-items:center;gap:8px;padding:6px;border-radius:9px;cursor:pointer;
  border:1px solid transparent;min-width:0}
.tb-scene:hover{background:var(--surface-2)}
.tb-scene.on{border-color:var(--brand);background:var(--brand-tint,rgba(90,79,224,.1))}
.tb-scene-thumb{position:relative;flex:0 0 auto;width:52px;height:36px;border-radius:6px;
  overflow:hidden;background:var(--surface-3)}
.tb-scene-thumb img{width:100%;height:100%;object-fit:cover;display:block}
.tb-badge{position:absolute;left:2px;bottom:2px;padding:0 4px;border-radius:3px;
  background:rgba(0,0,0,.7);color:#fff;font-size:7.5px;font-weight:700}
.tb-scene-meta{display:flex;flex-direction:column;gap:1px;min-width:0;flex:1 1 auto}
.tb-scene-name{font-size:12.5px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.tb-scene-sub{font-size:10.5px;color:var(--muted-text)}
.tb-start{flex:0 0 auto;padding:1px 4px;border-radius:4px;background:var(--ok-tint);color:var(--ok-text);
  font-size:8.4px;font-weight:700}

.tb-stage-wrap{display:flex;flex-direction:column;gap:10px;min-width:0}
.tb-toolbar{display:flex;align-items:center;gap:12px;flex-wrap:wrap}
.tb-tools{display:flex;gap:4px;flex-wrap:wrap}
.tb-tool{display:inline-flex;align-items:center;gap:6px;padding:8px 12px;border-radius:9px;
  border:1px solid var(--border);background:var(--surface);color:var(--muted-text);
  font:inherit;font-size:12px;font-weight:600;cursor:pointer}
.tb-tool svg{width:14px;height:14px}
.tb-tool:hover{border-color:var(--brand);color:var(--brand)}
.tb-tool.selected{background:var(--brand);border-color:var(--brand);color:#fff}
.tb-hint{font-size:11.5px;color:var(--muted-text)}

.tb-stage{position:relative;aspect-ratio:16/10;min-height:320px;border-radius:13px;overflow:hidden;
  background:#1e2128;border:1px solid var(--border)}
/* Crosshair while a tool is armed, so it is obvious the next click places
   something rather than looking around. */
.tb-placing .tv-canvas,.tb-placing .tv-flat{cursor:crosshair}
.tb-under{display:flex;gap:8px;flex-wrap:wrap}

.tb-switches{display:flex;flex-direction:column;gap:2px}
.tb-switch{display:flex;align-items:center;gap:8px;padding:6px 8px;border-radius:7px;
  font-size:12.5px;cursor:pointer}
.tb-switch:hover{background:var(--surface-2)}
.tb-switch input{accent-color:var(--brand);flex:0 0 auto}
.tb-spot-foot{display:flex;gap:8px;margin-top:12px}
.tb-spot-foot .btn{flex:1 1 auto}

/* ---------- appointments ---------- */
.bk-top{display:flex;gap:10px;align-items:center;flex-wrap:wrap;margin-bottom:14px}
.bk-pick{flex:1 1 220px;max-width:340px;min-width:0}
.bk-range{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.bk-range .input{width:auto;min-width:132px}

.bk-stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(110px,1fr));gap:10px;margin-bottom:16px}
/* min-width:0, and the text inside allowed to break.
 *
 * Measured: an email address in one of these needed 263px inside a 131px box
 * and was simply cut off — a flex item will not shrink below its content's
 * own width unless told it may. This is the whole of what "shrink" and "merge"
 * look like, and the fix is the same everywhere it appears.
 */
.bk-stat{display:flex;flex-direction:column;gap:1px;padding:12px 14px;border:1px solid var(--border);
  min-width:0;
  border-radius:11px;background:var(--surface);font-size:11.5px;color:var(--muted-text)}
.bk-stat span{font-size:21px;font-weight:700;color:var(--text);line-height:1.1}

.bk-day{margin-bottom:20px}
.bk-day-head{display:flex;align-items:center;gap:8px;font-size:13px;font-weight:700;
  margin-bottom:8px;color:var(--muted-text)}
.bk-list{display:flex;flex-direction:column;gap:8px}
.bk-row{display:grid;grid-template-columns:78px minmax(0,1.4fr) minmax(0,1fr) auto;gap:14px;
  align-items:center;padding:12px 14px;border:1px solid var(--border);border-radius:12px;
  background:var(--surface);border-left-width:3px}
.bk-confirmed{border-left-color:var(--ok-text)}
.bk-pending{border-left-color:var(--warn-text)}
.bk-cancelled{border-left-color:var(--danger-text);opacity:.62}
.bk-no_show{border-left-color:var(--danger-text)}
.bk-done{border-left-color:var(--border-strong)}
@media (max-width:820px){
  .bk-row{grid-template-columns:70px minmax(0,1fr);gap:10px}
  .bk-contact,.bk-actions{grid-column:1/-1}
  .bk-actions{justify-content:flex-start}
}
.bk-time{display:flex;flex-direction:column;font-size:13.5px;font-weight:700;
  font-variant-numeric:tabular-nums}
.bk-time small{font-size:10.5px;font-weight:500;color:var(--muted-text)}
.bk-who{display:flex;flex-direction:column;gap:2px;min-width:0}
.bk-name{font-size:13.5px;font-weight:600;display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.bk-party{padding:1px 8px;border-radius:20px;background:var(--surface-3);font-size:10.5px;
  font-weight:600;color:var(--muted-text)}
.bk-svc{font-size:11.5px;color:var(--muted-text)}
.bk-note{font-size:11.5px;color:var(--muted-text);font-style:italic;overflow:hidden;
  text-overflow:ellipsis;white-space:nowrap}
.bk-contact{display:flex;flex-direction:column;gap:1px;min-width:0;font-size:11.5px}
.bk-contact a{color:var(--muted-text);text-decoration:none;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.bk-contact a:hover{color:var(--brand)}
.bk-wa{font-weight:600}
.bk-ref{color:var(--muted-text);opacity:.7;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;
  font-size:10.5px}
.bk-actions{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.bk-badge{padding:4px 8px;border-radius:20px;font-size:10.5px;font-weight:700;white-space:nowrap}
.bk-badge-confirmed{background:var(--ok-tint);color:var(--ok-text)}
.bk-badge-pending{background:var(--warn-tint);color:var(--warn-text)}
.bk-badge-cancelled,.bk-badge-no_show{background:var(--danger-tint);color:var(--danger-text)}
.bk-badge-done{background:var(--surface-3);color:var(--muted-text)}
.select-sm{padding:4px 24px 4px 8px;font-size:12px;width:auto;min-width:104px}

/* ---------- booking setup ---------- */
.svc-list{display:flex;flex-direction:column;gap:8px}
.svc-row{display:flex;align-items:center;gap:12px;padding:10px 12px;border:1px solid var(--border);
  border-radius:11px;background:var(--surface);flex-wrap:wrap}
.svc-row-ic{width:34px;height:34px;border-radius:9px;flex:0 0 auto;display:grid;place-items:center;
  background:var(--brand-tint,rgba(90,79,224,.12));color:var(--brand)}
.svc-row-ic svg{width:18px;height:18px}
.svc-row-meta{display:flex;flex-direction:column;gap:1px;flex:1 1 160px;min-width:0}
.svc-row-n{font-size:13.5px;font-weight:600}
.svc-row-d{font-size:11.5px;color:var(--muted-text)}
.svc-row-act{display:flex;gap:6px;flex:0 0 auto}

.icon-pick{display:grid;grid-template-columns:repeat(auto-fill,minmax(46px,1fr));gap:8px}
.icon-opt{aspect-ratio:1;display:grid;place-items:center;border:1.5px solid var(--border);
  border-radius:10px;background:var(--surface);cursor:pointer;color:var(--muted)}
.icon-opt svg{width:20px;height:20px}
.icon-opt:hover{border-color:var(--brand);color:var(--brand)}
.icon-opt.on{border-color:var(--brand);background:var(--brand-tint,rgba(90,79,224,.12));color:var(--brand)}

.hours{display:flex;flex-direction:column;gap:8px}
.hours-row{display:flex;align-items:flex-start;gap:12px;padding:8px 12px;border:1px solid var(--border);
  border-radius:11px;background:var(--surface);flex-wrap:wrap}
.hours-day{display:flex;align-items:center;gap:8px;width:118px;flex:0 0 auto;font-size:13px;
  font-weight:600;cursor:pointer;padding-top:8px}
.hours-day input{accent-color:var(--brand)}
.hours-ranges{display:flex;flex-direction:column;gap:6px;flex:1 1 260px;min-width:0}
.hours-range{display:flex;align-items:center;gap:8px;min-width:0;flex-wrap:wrap}
/* Two time inputs at a fixed 112px plus the remove button no longer fit a
   320px screen, so they shrink instead of pushing the page sideways. */
.hours-range .input{flex:1 1 96px;width:auto;min-width:0}
@media (max-width:380px){
  .hours-row{gap:8px}
  .hours-day{width:100%;padding-top:0}
}

.blocks{display:flex;flex-direction:column;gap:8px}
.block-row{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:8px 12px;
  border:1px solid var(--border);border-radius:10px;background:var(--surface);flex-wrap:wrap}
.block-when{display:block;font-size:12.5px;font-weight:600}
.block-why{display:block;font-size:11.5px;color:var(--muted-text)}

.bb-help{margin-top:14px;font-size:13px}
.bb-help summary{cursor:pointer;font-weight:600;padding:8px 0}
.bb-code{margin-top:8px;padding:12px;border-radius:10px;background:var(--surface-3);
  font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11.5px;line-height:1.55;
  overflow-x:auto;white-space:pre}

/* ---------- section tabs ---------- */
.sec-tabs{display:flex;gap:4px;margin-bottom:16px;padding:4px;border-radius:12px;
  background:var(--surface-2);border:1px solid var(--border);width:fit-content;max-width:100%;
  overflow-x:auto;scrollbar-width:none}
.sec-tabs::-webkit-scrollbar{display:none}
.sec-tab{display:inline-flex;align-items:center;gap:8px;padding:8px 16px;border-radius:9px;
  font-size:13px;font-weight:600;color:var(--muted-text);text-decoration:none;white-space:nowrap;
  transition:background .14s,color .14s}
.sec-tab svg{width:15px;height:15px}
.sec-tab:hover{color:var(--text)}
.sec-tab.on{background:var(--surface);color:var(--brand);box-shadow:var(--shadow-xs)}

/* ---------- booking form editor ---------- */
.fld-list{display:flex;flex-direction:column;gap:8px}
.fld-row{display:flex;align-items:center;gap:10px;padding:10px 12px;border:1px solid var(--border);
  border-radius:10px;background:var(--surface);cursor:grab;flex-wrap:wrap}
.fld-row.dragging{opacity:.45;cursor:grabbing}
.fld-grip{flex:0 0 auto;color:var(--muted-text);opacity:.55;display:grid;place-items:center}
.fld-meta{display:flex;flex-direction:column;gap:1px;flex:1 1 160px;min-width:0}
.fld-label{font-size:13.5px;font-weight:600}
.fld-sub{font-size:11.5px;color:var(--muted-text)}
.fld-act{display:flex;gap:6px;flex:0 0 auto}

/* ---------- design picker ---------- */
.tpl-pick{display:grid;grid-template-columns:repeat(auto-fill,minmax(88px,1fr));gap:8px}
.tpl-opt{display:flex;flex-direction:column;gap:4px;padding:0;border:0;background:none;
  cursor:pointer;font:inherit;min-width:0}
.tpl-mini{display:block;aspect-ratio:4/3;border-radius:9px;overflow:hidden;padding:8px;
  background:var(--m-bg);border:1.5px solid var(--border);transition:border-color .15s,transform .15s;
  display:flex;flex-direction:column;gap:4px}
.tpl-opt:hover .tpl-mini{transform:translateY(-2px)}
.tpl-opt.on .tpl-mini{border-color:var(--brand);
  box-shadow:0 0 0 3px var(--brand-tint,rgba(90,79,224,.2))}
.tpl-mini i{display:block;border-radius:calc(var(--m-r) / 3);background:var(--m-card);
  border:1px solid var(--m-line)}
.m-bar{height:9px;background:var(--m-ac)!important;border:0!important}
.m-row{height:12px;flex:1 1 auto}
.m-btn{height:8px;background:var(--m-ac)!important;border:0!important}
.tpl-mini[data-shell="banner"] .m-bar{height:16px;margin:-7px -7px 2px}
.tpl-mini[data-shell="split"]{flex-direction:row;align-items:stretch}
.tpl-mini[data-shell="split"] .m-bar{width:8px;height:auto}
.tpl-mini[data-shell="split"] .m-row{flex:1 1 auto;height:auto}
.tpl-mini[data-shell="split"] .m-btn{width:8px;height:auto}
.tpl-mini[data-shell="single"]{padding:12px}
.tpl-opt-n{font-size:11px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
  /* --ink-2 rather than --muted-text: this label sits on the template
     preview's own #EFEFEF, where --muted-text is 4.45:1 — 0.05 short. */
  text-align:center;color:var(--ink-2)}
.tpl-opt.on .tpl-opt-n{color:var(--brand)}

/* ---------- seat plans in the builder ---------- */
.map-list{display:flex;flex-direction:column;gap:8px}
.map-row{display:flex;align-items:center;gap:12px;padding:10px 12px;border:1px solid var(--border);
  border-radius:11px;background:var(--surface);flex-wrap:wrap}
.map-ic{width:34px;height:34px;border-radius:9px;flex:0 0 auto;display:grid;place-items:center;
  background:var(--brand-tint,rgba(90,79,224,.12));color:var(--brand)}
.map-ic svg{width:17px;height:17px}
.map-meta{display:flex;flex-direction:column;gap:1px;flex:1 1 180px;min-width:0}
.map-n{font-size:13.5px;font-weight:600}
.map-d{font-size:11.5px;color:var(--muted-text)}
.map-act{display:flex;gap:6px;flex:0 0 auto}

.kind-pick{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:8px}
.kind-opt{display:flex;flex-direction:column;gap:2px;padding:10px 12px;border-radius:10px;
  border:1.5px solid var(--border);background:var(--surface);cursor:pointer;text-align:left;
  font:inherit;color:inherit}
.kind-opt:hover{border-color:var(--brand)}
.kind-opt.on{border-color:var(--brand);background:var(--brand-tint,rgba(90,79,224,.1))}
.kind-n{font-size:12.5px;font-weight:700}
.kind-a{font-size:11px;color:var(--muted-text);line-height:1.35}

/* The preview only has to convey the shape, so the seats are dots rather than
   a second full renderer that could drift from the real one. */
.map-preview{display:flex;flex-direction:column;gap:4px;padding:12px;border-radius:10px;
  background:var(--surface-2);border:1px solid var(--border);overflow-x:auto;max-height:220px}
.mp-row{display:flex;gap:4px;justify-content:center}
.mp-seat{width:9px;height:9px;border-radius:2px 2px 1px 1px;background:var(--brand);opacity:.65;
  flex:0 0 auto}
.mp-gap{width:7px;flex:0 0 auto}
.bk-token{display:inline-block;margin-top:4px;padding:1px 8px;border-radius:5px;font-style:normal;
  background:var(--brand-tint,rgba(90,79,224,.12));color:var(--brand);font-size:10.5px;font-weight:700}
.bk-seats{font-size:11.5px;color:var(--brand);font-weight:600}
.bk-slip{font-size:10.5px;font-weight:600}

/* ---------- lead pipeline ---------- */
.bk-stat-due span{color:var(--warn-text)}

/* An email or a long word inside a stat card. Broken rather than cut: the
   whole value matters, and half an email address is worse than a wrapped
   one. */
.bk-stat > *{min-width:0;overflow-wrap:anywhere}
.lead-bar{display:flex;gap:10px;align-items:center;flex-wrap:wrap;margin-bottom:14px}
.lead-search{flex:1 1 220px;max-width:320px;min-width:0}
.lead-sort{flex:0 0 auto;width:auto;min-width:150px}
.stage-chips{display:flex;gap:6px;flex-wrap:wrap;flex:1 1 auto}
.chip{display:inline-flex;align-items:center;gap:6px;padding:6px 12px;border-radius:999px;
  border:1px solid var(--border);background:var(--surface);color:var(--muted-text);
  font-size:12px;font-weight:600;text-decoration:none;white-space:nowrap;transition:all .14s}
.chip span{font-size:10.5px;opacity:.8;font-variant-numeric:tabular-nums}
.chip:hover{border-color:var(--brand);color:var(--brand)}
.chip.on{background:var(--brand);border-color:var(--brand);color:#fff}
.chip-danger.on{background:var(--danger);border-color:var(--danger-text)}
.chip-warn.on{background:var(--warn);border-color:var(--warn-text)}
.chip-ok.on{background:var(--ok);border-color:var(--ok-text)}

.lead-list{display:flex;flex-direction:column;gap:8px}
.lead{display:grid;grid-template-columns:minmax(0,1.5fr) minmax(0,1.4fr) 128px auto;gap:14px;
  align-items:center;padding:12px 14px;border:1px solid var(--border);border-radius:12px;
  background:var(--surface);border-left-width:3px}
.lead-info{border-left-color:var(--brand)}
.lead-danger{border-left-color:var(--danger-text)}
.lead-warn{border-left-color:var(--warn-text)}
.lead-ok{border-left-color:var(--ok-text)}
.lead-muted{border-left-color:var(--border-strong);opacity:.72}
@media (max-width:900px){
  .lead{grid-template-columns:minmax(0,1fr) auto;gap:10px}
  .lead-facts{grid-column:1/-1}
  .lead-stage{grid-column:1}
}
.lead-who{display:flex;align-items:center;gap:12px;min-width:0}
.lead-av{width:36px;height:36px;border-radius:50%;flex:0 0 auto;display:grid;place-items:center;
  background:var(--brand-tint,rgba(90,79,224,.12));color:var(--brand);font-size:14px;font-weight:700}
.lead-id{display:flex;flex-direction:column;gap:1px;min-width:0}
.lead-name{padding:0;border:0;background:none;font:inherit;font-size:13.5px;font-weight:700;
  color:inherit;cursor:pointer;text-align:left;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.lead-name:hover{color:var(--brand)}
.lead-meta{font-size:11.5px;color:var(--muted-text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.lead-facts{display:flex;gap:12px;flex-wrap:wrap;font-size:11.5px;color:var(--muted-text);min-width:0}
.lead-fact b{color:var(--text);font-weight:700}
.lead-fact.due{color:var(--warn-text)}
.lead-fact.due b{color:var(--warn-text)}
.lead-do{display:flex;gap:4px;flex:0 0 auto}
.ico-btn{width:34px;height:34px;border-radius:9px;border:1px solid var(--border);background:var(--surface);
  display:grid;place-items:center;color:var(--muted-text);cursor:pointer;text-decoration:none;
  transition:all .14s}
.ico-btn:hover{border-color:var(--brand);color:var(--brand)}
.ico-call:hover{border-color:var(--ok-text);color:var(--ok-text)}
.ico-wa{color:#25D366}
.ico-wa:hover{border-color:#25D366;color:#1da851}

.dl-contact{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:16px}
.dl-chip{display:inline-flex;align-items:center;padding:4px 12px;border-radius:999px;
  border:1px solid var(--border);background:var(--surface-2);font-size:12px;font-weight:600;
  color:var(--text);text-decoration:none}
.dl-chip:hover{border-color:var(--brand);color:var(--brand)}
.dl-wa{color:#128C7E}
.dl-log-head{margin:24px 0 10px;padding-top:16px;border-top:1px solid var(--border)}
.dl-log-head h3{font-size:13px;font-weight:700}
.dl-log{display:flex;flex-direction:column;gap:8px;margin-top:14px}
.dl-item{display:flex;flex-direction:column;gap:2px;padding:8px 12px;border-radius:9px;
  background:var(--surface-2);border-left:2.5px solid var(--border-strong)}
.dl-call{border-left-color:var(--ok-text)}
.dl-whatsapp{border-left-color:#25D366}
.dl-email{border-left-color:var(--brand)}
.dl-stage{border-left-color:var(--warn-text)}
.dl-kind{font-size:10.5px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;
  color:var(--muted-text)}
.dl-body{font-size:12.5px;line-height:1.45}
.dl-when{font-size:10.5px;color:var(--muted-text)}

/* ---------- API docs ---------- */
.api-top{margin-bottom:14px}
.api-top .select{max-width:320px}
.api-facts{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:16px}
.api-fact{display:flex;flex-direction:column;gap:4px;min-width:0}
.api-lab{font-size:11px;font-weight:700;letter-spacing:.05em;text-transform:uppercase;
  color:var(--muted-text)}
.api-secret{letter-spacing:.08em}

.api-list{display:flex;flex-direction:column;gap:14px}
.api-head{align-items:center}
.api-head h2{display:flex;align-items:center;gap:8px;font-size:13.5px;flex-wrap:wrap;min-width:0}
.api-verb{padding:2px 8px;border-radius:5px;font-size:10.5px;font-weight:700;letter-spacing:.04em}
.api-verb-get{background:var(--ok-tint);color:var(--ok-text)}
.api-verb-post{background:var(--brand-tint,rgba(90,79,224,.12));color:var(--brand)}

.api-pair{display:grid;grid-template-columns:repeat(auto-fit,minmax(300px,1fr));gap:14px}
.api-col{display:flex;flex-direction:column;gap:6px;min-width:0}
.code-wrap{position:relative;min-width:0}
.code{margin:0;padding:14px 16px;border-radius:10px;background:var(--surface-3);
  font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11.5px;line-height:1.6;
  overflow-x:auto;white-space:pre;color:var(--text)}
.code-json{color:var(--muted-text)}
.code-copy{position:absolute;top:8px;right:8px}
.api-notes{margin:14px 0 0;padding-left:16px;font-size:12.5px;color:var(--muted-text);line-height:1.65}
.api-notes li{margin-bottom:4px}
.api-try{margin-top:14px}
.api-out{margin-top:10px;max-height:280px;overflow:auto}
.mono{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.92em}

/* ---------- placing hotspots ---------- */
/* Existing markers glow while a tool is armed, so it is obvious where things
   already sit before dropping another one on top. */
.tb-glow .tv-spot::before{box-shadow:0 0 0 3px color-mix(in srgb,var(--brand) 35%,transparent),
  0 2px 10px rgba(0,0,0,.45)}
.tb-glow .tv-spot::after{animation-duration:1.2s}
.tb-glow .tv-spot-label{opacity:1}

/* The marker being dragged lifts off the picture. */
.tv-dragging-spot{z-index:6}
.tv-dragging-spot::before{transform:scale(1.3);
  box-shadow:0 0 0 5px color-mix(in srgb,var(--brand) 40%,transparent),0 6px 18px rgba(0,0,0,.5)}
.tb-dragging .tv-canvas,.tb-dragging .tv-flat{cursor:grabbing}

/* A ghost that follows the pointer, showing where a click would land. */
.tb-ghost{position:absolute;z-index:5;width:30px;height:30px;margin:-15px 0 0 -15px;
  border-radius:50%;pointer-events:none;border:2px dashed rgba(255,255,255,.9);
  background:color-mix(in srgb,var(--brand) 28%,transparent);
  box-shadow:0 0 0 3px rgba(0,0,0,.18);animation:tb-ghost 1.4s ease-out infinite}
@keyframes tb-ghost{0%{transform:scale(.9);opacity:.9}70%{transform:scale(1.25);opacity:.45}
  100%{transform:scale(.9);opacity:.9}}

.tb-coords{position:absolute;left:50%;bottom:12px;transform:translateX(-50%);z-index:7;
  padding:6px 12px;border-radius:9px;background:rgba(16,20,26,.82);color:#fff;
  font-size:11.5px;font-weight:600;font-variant-numeric:tabular-nums;pointer-events:none;
  white-space:nowrap}
@media (prefers-reduced-motion:reduce){.tb-ghost{animation:none}}

/* ---------- other ways to get a photo in ---------- */
.alt-upload{display:flex;gap:8px;margin-top:8px}
.alt-btn{flex:1 1 0;display:inline-flex;align-items:center;justify-content:center;gap:6px;
  padding:8px 10px;border:1px solid var(--border);border-radius:9px;background:var(--surface);
  color:var(--muted-text);font:inherit;font-size:12px;font-weight:600;cursor:pointer;
  transition:border-color .14s,color .14s}
.alt-btn:hover{border-color:var(--brand);color:var(--brand)}
.alt-btn:disabled{opacity:.45;cursor:default}
.alt-btn svg{flex:0 0 auto}

/* ---------- narration ---------- */
.tb-narr{display:flex;flex-direction:column;gap:8px;padding:12px 14px;border:1px solid var(--border);
  border-radius:12px;background:var(--surface)}
.tb-narr-head{display:flex;align-items:baseline;justify-content:space-between;gap:10px;flex-wrap:wrap}
.tb-narr-t{font-size:13px;font-weight:700}
.tb-narr-s{font-size:11.5px;color:var(--muted-text)}
.tb-narr-player{width:100%;height:38px}
.tb-narr-acts{display:flex;gap:8px;flex-wrap:wrap}
/* A dot that pulses while recording: the button's own label counts the seconds,
   but the dot is what reads as "live" at a glance. */
.rec-dot{width:9px;height:9px;border-radius:50%;background:var(--danger);flex:0 0 auto}
#tb-rec.recording{border-color:var(--danger-text);color:var(--danger-text)}
#tb-rec.recording .rec-dot{animation:rec-pulse 1s ease-in-out infinite}
@keyframes rec-pulse{50%{opacity:.25}}
@media (prefers-reduced-motion:reduce){#tb-rec.recording .rec-dot{animation:none}}

/* ---------- chatbot list ---------- */
.bot-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:16px}
.bot-card{display:flex;flex-direction:column;gap:12px;padding:16px;border:1px solid var(--border);
  border-radius:14px;background:var(--surface);min-width:0}
.bot-top{display:flex;align-items:flex-start;gap:12px;min-width:0}
.bot-ic{width:42px;height:42px;flex:0 0 auto;border-radius:12px;display:grid;place-items:center;
  background:color-mix(in srgb,var(--ac,#5A4FE0) 14%,transparent);color:var(--ac,#5A4FE0)}
.bot-ic svg{width:21px;height:21px}
.bot-meta{display:flex;flex-direction:column;gap:2px;min-width:0;flex:1 1 auto}
.bot-name{font-size:14.5px;font-weight:700;color:inherit;text-decoration:none;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.bot-name:hover{color:var(--brand)}
.bot-sub{font-size:11.5px;color:var(--muted-text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.bot-warn{display:flex;align-items:flex-start;gap:8px;padding:8px 10px;border-radius:9px;
  background:var(--warn-tint);color:var(--warn-text);font-size:11.5px;line-height:1.4}
.bot-warn svg{width:14px;height:14px;flex:0 0 auto;margin-top:1px}
.bot-facts{display:flex;gap:12px;font-size:11.5px;color:var(--muted-text);flex-wrap:wrap}
.bot-link{display:flex;align-items:center;gap:8px;padding:6px 8px;border-radius:8px;
  background:var(--surface-2);min-width:0}
.bot-link code{flex:1 1 auto;min-width:0;font-size:11px;overflow:hidden;text-overflow:ellipsis;
  white-space:nowrap;font-family:ui-monospace,SFMono-Regular,Menlo,monospace}
.bot-actions{display:flex;gap:6px;flex-wrap:wrap;margin-top:auto}
.bot-actions .btn{flex:1 1 auto}

/* A switch that reads as on or off at a glance, since it decides whether a
   customer's visitors are being answered at all. */
.switch{flex:0 0 auto;cursor:pointer}
.switch input{position:absolute;opacity:0;width:0;height:0}
.switch-track{display:block;width:40px;height:23px;border-radius:20px;background:var(--surface-3);
  border:1px solid var(--border);transition:background .16s,border-color .16s;position:relative}
.switch-dot{position:absolute;top:2px;left:2px;width:17px;height:17px;border-radius:50%;
  background:#fff;box-shadow:0 1px 3px rgba(16,24,40,.3);transition:transform .16s}
.switch input:checked + .switch-track{background:var(--ok);border-color:var(--ok-text)}
.switch input:checked + .switch-track .switch-dot{transform:translateX(17px)}
.switch input:focus-visible + .switch-track{outline:2px solid var(--brand);outline-offset:2px}

/* ---------- AI credits ---------- */
.cred-head{display:flex;align-items:baseline;justify-content:space-between;gap:12px;
  flex-wrap:wrap;margin-bottom:10px}
.cred-left{font-size:28px;font-weight:700;line-height:1;font-variant-numeric:tabular-nums}
.cred-of{font-size:12.5px;color:var(--muted-text);margin-left:8px}
/* A bar rather than a number alone: "nine thousand left" means nothing without
   knowing what it started from. */
.cred-bar{height:8px;border-radius:5px;background:var(--surface-3);overflow:hidden}
.cred-bar > span{display:block;height:100%;background:var(--brand);transition:width .3s}
.key-row{display:flex;gap:8px;align-items:center;min-width:0}
.key-row .input{flex:1 1 auto;min-width:0}
.ok-note{display:inline-block;margin-left:10px;font-size:12.5px;color:var(--ok-text);font-weight:600}

/* ---------- the "powered by" line ---------- */
/* Deliberately quiet: it belongs to us, not to the business whose page this is,
   so it sits at the foot and does not compete with their own content. */
.eab-powered{padding:14px 16px 16px;text-align:center;font:500 11px/1.4 system-ui,
  -apple-system,'Segoe UI',Roboto,sans-serif;color:rgba(120,128,142,.85)}
.eab-powered a{color:inherit;text-decoration:none}
.eab-powered a:hover{text-decoration:underline}
.brand-rows{display:flex;flex-direction:column;gap:8px}
.brand-row{display:flex;align-items:center;gap:16px;padding:10px 12px;border:1px solid var(--border);
  border-radius:10px;background:var(--surface);flex-wrap:wrap}
.brand-name{flex:1 1 160px;font-size:13px;font-weight:600;min-width:0}
.chan-rows{display:flex;flex-direction:column;gap:8px;max-height:340px;overflow-y:auto}
.chan-row{display:flex;align-items:center;gap:12px;padding:8px 12px;border:1px solid var(--border);
  border-radius:10px;background:var(--surface)}
.chan-name{flex:1 1 auto;min-width:0;font-size:13px;font-weight:600;display:flex;
  flex-direction:column;gap:1px}
.chan-name small{font-size:11px;font-weight:400;color:var(--muted-text)}
.chan-row .select{flex:0 0 auto;width:150px}
.rule{border:0;border-top:1px solid var(--border);margin:20px 0 16px}
.sub-h{font-size:14px;font-weight:700;margin:0 0 4px}

/* Which AI key each module uses, one row per module. */
.ai-row{display:flex;align-items:center;gap:10px;padding:8px 0;flex-wrap:wrap}
.ai-row + .ai-row{border-top:1px solid var(--border)}
.ai-name{flex:1 1 130px;min-width:0;font-size:13px;font-weight:600}
.ai-row .select{flex:0 0 auto;width:190px}
.ai-row .input{flex:0 0 auto;width:120px}
.ai-row .input:disabled{opacity:.45}
.ai-used{flex:0 0 auto;font-size:11px;color:var(--muted-text);min-width:56px}

/* ---------- dialogs ----------
   Replacing the browser's own, which cannot be styled and on a phone announce
   the site's domain in a way that reads as a warning. */
.ui-dialog-wrap{position:fixed;inset:0;z-index:1200;display:flex;align-items:center;
  justify-content:center;padding:20px;background:rgba(16,24,40,.5);
  backdrop-filter:blur(3px);opacity:0;transition:opacity .16s}
.ui-dialog-wrap.shown{opacity:1}
.ui-dialog{width:100%;max-width:420px;background:var(--surface);border-radius:16px;
  box-shadow:0 24px 64px rgba(16,24,40,.28);overflow:hidden;
  transform:translateY(12px) scale(.98);transition:transform .18s cubic-bezier(.16,1,.3,1)}
.ui-dialog-wrap.shown .ui-dialog{transform:none}
/* The body scrolls, the dialog does not grow past the screen.
 *
 * .modal has had this cap for a while; .ui-dialog never did — and every
 * confirm, prompt and form dialog in the app is a .ui-dialog. A dialog with
 * six fields in it simply ran off the bottom of a phone with its buttons out
 * of reach.
 *
 * The body scrolls rather than the whole card, so the title stays visible and
 * the buttons stay on screen where a thumb can reach them.
 */
.ui-dialog{display:flex;flex-direction:column;max-height:calc(100dvh - 40px)}
.ui-dialog-body{
  padding:24px 24px 4px;
  min-height:0;overflow-y:auto;overscroll-behavior:contain;
}

/* Fields inside a dialog.
 *
 * Flush against each other before this: the container a dialog is handed
 * comes from whichever module opened it, and most set no spacing of their
 * own. Set here so every dialog in every module is spaced the same, rather
 * than each module having to remember.
 */
.ui-dialog-body .field + .field{margin-top:16px}
.ui-dialog-body .field{margin-bottom:0}
/* A form container passed in carries the gap itself, so nested fields do not
   get it twice. */
.ui-dialog-body > [class$="-form"]{display:grid;gap:16px}
.ui-dialog-body > [class$="-form"] .field + .field{margin-top:0}
.ui-dialog-title{margin:0 0 6px;font-size:16px;font-weight:700}
/* With a message under it, 6px is right — the two read as one block. With no
   message, the next thing is a form field and 6px is cramped against its
   label. */
.ui-dialog-body:has(.ui-dialog-text:empty) .ui-dialog-title{margin-bottom:18px}
.ui-dialog-text{margin:0;font-size:14px;line-height:1.55;color:var(--muted-text);
  white-space:pre-wrap}
.ui-dialog-input{margin-top:14px;width:100%;resize:vertical;min-height:44px}
.ui-dialog-foot{display:flex;justify-content:flex-end;gap:8px;padding:16px 24px 20px}
.ui-dialog.danger .ui-dialog-title{color:var(--danger-text)}
/* A destructive action's button carries the project's own danger colour rather
   than a raw red, and keeps the focus ring the rest of the app uses. */
.ui-dialog .btn-danger{background:var(--danger);color:#fff;border:0}
.ui-dialog .btn-danger:hover{filter:brightness(.94)}
.ui-dialog .btn{min-width:96px}
/* Without a title the message carries the whole weight, so it reads as the
   heading it effectively is. */
.ui-dialog-body:not(:has(.ui-dialog-title)) .ui-dialog-text{
  font-size:15px;color:var(--text);font-weight:600}
@media (max-width:520px){
  /* On a phone it comes up from the bottom, where the thumb is. */
  .ui-dialog-wrap{align-items:flex-end;padding:0}
  .ui-dialog{max-width:100%;border-radius:18px 18px 0 0;
    transform:translateY(100%);
    /* A little of the page still showing above it, so a sheet reads as a sheet
       rather than as a page that replaced the one underneath. */
    max-height:calc(100dvh - 24px)}
  .ui-dialog-foot{padding-bottom:calc(20px + env(safe-area-inset-bottom,0px))}
  .ui-dialog-foot .btn{flex:1 1 auto}
}
@media (prefers-reduced-motion:reduce){
  .ui-dialog-wrap,.ui-dialog{transition:none}
}
.block-form{margin-top:14px}
.block-form .label{display:block;margin-bottom:4px}
.block-form .input{width:100%}

/* ---------- colour picker ----------
   Replacing <input type="color">, which cannot do transparency, opens an
   operating-system dialog that looks nothing like the page, and on several
   mobile browsers does not open at all. */
.cp{position:relative;display:flex;align-items:center;gap:8px}
.cp > .input{flex:1 1 auto;min-width:0;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;
  font-size:12.5px;text-transform:uppercase}
.cp-trigger{flex:0 0 auto;width:38px;height:38px;padding:4px;border-radius:9px;
  border:1.5px solid var(--border);background:var(--surface);cursor:pointer}
.cp-trigger:hover{border-color:var(--brand)}
/* The chequerboard shows through wherever the colour is not opaque, which is
   the only way transparency reads as deliberate rather than as a bug. */
.cp-chip{display:block;width:100%;height:100%;border-radius:6px;overflow:hidden;
  background-image:linear-gradient(45deg,#D0D5DD 25%,transparent 25%),
    linear-gradient(-45deg,#D0D5DD 25%,transparent 25%),
    linear-gradient(45deg,transparent 75%,#D0D5DD 75%),
    linear-gradient(-45deg,transparent 75%,#D0D5DD 75%);
  background-size:10px 10px;
  background-position:0 0,0 5px,5px -5px,-5px 0}
.cp-chip > span{display:block;width:100%;height:100%}

.cp-panel{display:none;position:absolute;z-index:60;top:calc(100% + 8px);left:0;
  width:248px;padding:12px;border-radius:14px;background:var(--surface);
  border:1px solid var(--border);box-shadow:0 18px 44px rgba(16,24,40,.2)}
.cp.open .cp-panel{display:block}

.cp-area{position:relative;height:132px;border-radius:9px;cursor:crosshair;
  background:
    linear-gradient(to top,#000,transparent),
    linear-gradient(to right,#fff,var(--cp-hue,#5A4FE0))}
.cp-area:focus-visible,.cp-hue:focus-visible,.cp-alpha:focus-visible{outline:2px solid var(--brand);
  outline-offset:2px}
.cp-dot{position:absolute;width:14px;height:14px;margin:-7px 0 0 -7px;border-radius:50%;
  border:2px solid #fff;box-shadow:0 0 0 1px rgba(16,24,40,.3);pointer-events:none}

.cp-sliders{margin-top:12px;display:flex;flex-direction:column;gap:8px}
.cp-hue,.cp-alpha{position:relative;height:13px;border-radius:99px;cursor:pointer}
.cp-hue{background:linear-gradient(to right,#F00,#FF0,#0F0,#0FF,#00F,#F0F,#F00)}
.cp-alpha{background-image:linear-gradient(45deg,#D0D5DD 25%,transparent 25%),
  linear-gradient(-45deg,#D0D5DD 25%,transparent 25%),
  linear-gradient(45deg,transparent 75%,#D0D5DD 75%),
  linear-gradient(-45deg,transparent 75%,#D0D5DD 75%);
  background-size:9px 9px;background-position:0 0,0 4.5px,4.5px -4.5px,-4.5px 0}
.cp-a-fill{position:absolute;inset:0;border-radius:99px}
.cp-h-dot,.cp-a-dot{position:absolute;top:50%;width:15px;height:15px;
  margin:-7.5px 0 0 -7.5px;border-radius:50%;border:2px solid #fff;
  box-shadow:0 0 0 1px rgba(16,24,40,.3);pointer-events:none}

.cp-swatches{margin-top:12px;display:grid;grid-template-columns:repeat(8,1fr);gap:4px}
.cp-sw{aspect-ratio:1;border-radius:5px;border:1px solid rgba(16,24,40,.12);cursor:pointer;
  padding:0}
.cp-sw:hover{transform:scale(1.14)}
.cp-clear{background-image:linear-gradient(45deg,#D0D5DD 25%,transparent 25%),
  linear-gradient(-45deg,#D0D5DD 25%,transparent 25%),
  linear-gradient(45deg,transparent 75%,#D0D5DD 75%),
  linear-gradient(-45deg,transparent 75%,#D0D5DD 75%);
  background-size:8px 8px;background-position:0 0,0 4px,4px -4px,-4px 0}

@media (max-width:520px){
  .cp-panel{width:min(280px,calc(100vw - 40px))}
}
@media (prefers-reduced-motion:reduce){.cp-sw:hover{transform:none}}

/* The picker inside a colour field. The trigger takes the place the old chip
   held, so the row lays out exactly as before. */
.colour-field .cp{flex:1 1 auto;min-width:0}
.colour-field .cp-trigger{width:34px;height:34px}
.colour-field .cp-panel{left:0}

/* ---------- scrollbars ----------
   The browser's own is a grey slab with arrows, drawn in whatever the operating
   system uses, and it fights every panel it sits in. A thin one that only
   shows under the pointer is what every modern app draws, and it is the same
   on Windows, Mac and Linux rather than three different things. */
.sidebar,.fb-palette,.fb-props,.fb-canvas,.fb-try-log,.eab-body,
.fb-problems,.cb-preview,textarea,.kb-list,.modal-body,.drawer-body{
  scrollbar-width:thin;scrollbar-color:transparent transparent}
.sidebar:hover,.fb-palette:hover,.fb-props:hover,.fb-canvas:hover,.fb-try-log:hover,
.eab-body:hover,.fb-problems:hover,textarea:hover,.kb-list:hover,
.modal-body:hover,.drawer-body:hover{scrollbar-color:var(--border-strong) transparent}

.sidebar::-webkit-scrollbar,.fb-palette::-webkit-scrollbar,.fb-props::-webkit-scrollbar,
.fb-canvas::-webkit-scrollbar,.fb-try-log::-webkit-scrollbar,.eab-body::-webkit-scrollbar,
.fb-problems::-webkit-scrollbar,textarea::-webkit-scrollbar,.kb-list::-webkit-scrollbar,
.modal-body::-webkit-scrollbar,.drawer-body::-webkit-scrollbar{width:8px;height:8px}
.sidebar::-webkit-scrollbar-thumb,.fb-palette::-webkit-scrollbar-thumb,
.fb-props::-webkit-scrollbar-thumb,.fb-canvas::-webkit-scrollbar-thumb,
.fb-try-log::-webkit-scrollbar-thumb,.eab-body::-webkit-scrollbar-thumb,
.fb-problems::-webkit-scrollbar-thumb,textarea::-webkit-scrollbar-thumb,
.kb-list::-webkit-scrollbar-thumb,.modal-body::-webkit-scrollbar-thumb,
.drawer-body::-webkit-scrollbar-thumb{background:transparent;border-radius:99px;
  border:2px solid transparent;background-clip:padding-box}
.sidebar:hover::-webkit-scrollbar-thumb,.fb-palette:hover::-webkit-scrollbar-thumb,
.fb-props:hover::-webkit-scrollbar-thumb,.fb-canvas:hover::-webkit-scrollbar-thumb,
.fb-try-log:hover::-webkit-scrollbar-thumb,.eab-body:hover::-webkit-scrollbar-thumb,
.fb-problems:hover::-webkit-scrollbar-thumb,textarea:hover::-webkit-scrollbar-thumb,
.kb-list:hover::-webkit-scrollbar-thumb,.modal-body:hover::-webkit-scrollbar-thumb,
.drawer-body:hover::-webkit-scrollbar-thumb{background:var(--border-strong)}
.sidebar::-webkit-scrollbar-track,.fb-canvas::-webkit-scrollbar-track{background:transparent}

/* The page itself keeps a visible bar: somebody looking for it on a long page
   should find it without having to hover on nothing. */
html{scrollbar-width:thin;scrollbar-color:var(--border-strong) transparent}
html::-webkit-scrollbar{width:10px}
html::-webkit-scrollbar-thumb{background:var(--border-strong);border-radius:99px;
  border:2px solid var(--bg);background-clip:padding-box}

/* ---------- a narrowed menu ----------
   The burger gives the space back on a desktop rather than hiding navigation
   behind a second click. Everything stays where it was; only the words go, and
   they come back on hover as a label beside the icon. */
/* The width is a variable, and everything that has to make room for the menu
   already reads it. Changing the variable moves all of them at once; overriding
   the sidebar alone moved the menu and left the gap it used to fill. */
body.nav-narrow{--sidebar-w:66px}
/* The burger is the way back to the full menu, so it should not be the palest
   thing on the bar while the menu is narrowed. */
body.nav-narrow .burger{background:var(--text);color:var(--surface)}
body.nav-narrow .sidebar{overflow:visible}
body.nav-narrow .nav-item{justify-content:center;padding:8px 0;gap:0}
body.nav-narrow .nav-item > *:not(svg){display:none}
/* The section headings ("Workspace", "Administration") have nothing to label
   once the names are gone, and a clipped word is worse than no word. */
body.nav-narrow .nav-label,
body.nav-narrow .brand-text,
body.nav-narrow .side-user .grow{display:none}
body.nav-narrow .sidebar-brand{justify-content:center;padding-left:0;padding-right:0}
body.nav-narrow .sidebar-foot,body.nav-narrow .side-user{justify-content:center}

/* The name, on hover. Positioned off the item rather than as a title attribute
   so it appears at once — a browser tooltip takes a second, which is longer
   than it takes to give up and widen the menu again. */
body.nav-narrow .nav-item::after{content:attr(data-label);
  position:absolute;left:calc(100% + 8px);top:50%;transform:translateY(-50%);
  padding:4px 10px;border-radius:8px;white-space:nowrap;
  background:var(--text);color:var(--surface);font-size:12px;font-weight:600;
  opacity:0;pointer-events:none;transition:opacity .12s;z-index:40}
body.nav-narrow .nav-item:hover::after{opacity:1}

@media (max-width:900px){
  /* No room to narrow anything: the menu is a drawer here. */
  body.nav-narrow{--sidebar-w:262px}
  body.nav-narrow .nav-item{justify-content:flex-start;padding:8px 12px;gap:12px}
  body.nav-narrow .nav-item > *:not(svg){display:inline}
  body.nav-narrow .nav-label,body.nav-narrow .brand-text,
  body.nav-narrow .side-user .grow{display:block}
  body.nav-narrow .nav-item::after{display:none}
}

/* The merchant's own payment QR, in the builder.
   Shown in the same square-with-a-curve frame the landing page uses, so what
   is set here is what a customer sees. */
.pay-own{display:flex;align-items:center;gap:14px;flex-wrap:wrap}
.pay-own-box{flex:0 0 auto;display:grid;place-items:center;width:112px;height:112px;
  border-radius:12px;background:#fff;border:1px solid var(--border);padding:8px}
/* contain, never cover: cropping a QR code removes part of the code. */
.pay-own-box img{max-width:100%;max-height:100%;object-fit:contain;display:block}
.pay-own-empty{font-size:11.5px;color:var(--muted-text);text-align:center;padding:0 6px}
.pay-own-acts{display:flex;gap:8px;flex-wrap:wrap}
.pay-own-acts label{cursor:pointer;margin:0}

/* The banner options, shown only for the style that uses them. */
.cover-opt.hide{display:none}
.cover-preview{width:96px;height:36px;border-radius:8px}
.cover-preview img{width:100%;height:100%;object-fit:cover}

/* A textarea holding code. Monospace, because alignment is meaning here, and
   no spellcheck squiggles under tag names. */
textarea.input.code{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
  font-size:12.5px;line-height:1.6;white-space:pre;overflow-wrap:normal;overflow-x:auto}

/* ============================================================ the chrome ==
 *
 * The topbar and sidebar, brought to the dashboard's design. These are on
 * every screen, so they are in the shared stylesheet rather than the
 * dashboard's own.
 */

.tb-title{
  flex:none;font-family:var(--ff-display);font-weight:600;font-size:15px;
  color:var(--ink);white-space:nowrap;
}

/* Search takes the space between the title and the account controls, which is
 * where the eye goes when somebody is looking for a thing rather than a page. */
.tb-search{
  position:relative;flex:1;min-width:0;max-width:460px;margin:0 4px;
}
.tb-search svg{
  position:absolute;left:14px;top:50%;transform:translateY(-50%);
  width:16px;height:16px;color:var(--faint);pointer-events:none;
}
.tb-search input{
  width:100%;height:38px;padding:0 16px 0 38px;
  border-radius:999px;border:1px solid transparent;background:var(--surface-3);
  font:inherit;font-size:13px;color:var(--ink);
}
.tb-search input::placeholder{color:var(--faint)}
.tb-search input:focus{outline:none;background:var(--surface);border-color:var(--brand)}

/* Light or dark, as a switch rather than a button: the state is the point. */
.tb-theme{
  flex:none;display:flex;align-items:center;gap:4px;
  width:62px;height:30px;padding:0 4px;
  border:0;border-radius:999px;background:var(--surface-3);cursor:pointer;
}
.tb-theme-sun{
  display:grid;place-items:center;width:22px;height:22px;
  border-radius:50%;background:#FEF3C7;color:#F59E0B;
}
.tb-theme-sun svg{width:13px;height:13px}
.tb-theme-knob{
  width:22px;height:22px;margin-left:auto;border-radius:50%;
  background:var(--surface);box-shadow:0 1px 2px rgba(16,24,40,.12);
}

.tb-bell{position:relative}
.tb-badge{
  position:absolute;top:3px;right:3px;
  display:grid;place-items:center;min-width:16px;height:16px;padding:0 4px;
  border-radius:999px;background:var(--danger);color:#fff;
  font-size:9.5px;font-weight:700;line-height:1;
}

/* The account control carries who you are, not just a circle. */
.tb-who{
  display:flex;align-items:center;gap:8px;
  height:auto;padding:4px 6px 4px 4px;
  background:none;border:0;
}
.tb-who:hover{background:var(--surface-2)}
.tb-who-t{display:none;text-align:left;line-height:1.25;min-width:0}
.tb-who-t strong{display:block;font-size:13px;font-weight:600;color:var(--ink)}
.tb-who-t em{display:block;font-size:11px;font-style:normal;color:var(--muted-text)}
.tb-who-c{flex:none;font-size:17px;line-height:1;color:var(--muted-text);transform:rotate(90deg)}

@media (min-width:900px){ .tb-who-t{display:block} }
@media (max-width:820px){ .tb-search{display:none} }
@media (max-width:640px){ .tb-theme,.tb-title{display:none} }

/* ------------------------------------------------------------- plan card */

.plan-card{
  display:block;margin:0 0 12px;padding:14px;
  border-radius:14px;text-decoration:none;color:#fff;
  background:linear-gradient(160deg,#8B5CF6,#7C3AED 55%,#6D28D9);
}
.plan-i{display:block;margin-bottom:8px;color:#fff}
.plan-i svg{width:20px;height:20px;display:block}
.plan-card strong{display:block;font-size:14px;font-weight:700;line-height:1.2}
.plan-card em{
  display:block;margin-top:4px;font-size:11.5px;font-style:normal;
  line-height:1.45;color:rgba(255,255,255,.85);
}
.plan-b{
  display:grid;place-items:center;height:34px;margin-top:12px;
  border-radius:9px;background:#fff;color:var(--brand-700);
  font-size:12.5px;font-weight:700;
}

/* ══════════════════════════════════════════════ the topbar, filled out ══ */

.tb-title{
  flex:none;font-family:var(--ff-display);font-weight:600;
  font-size:15px;color:var(--ink);white-space:nowrap;
}

/* Search.
 *
 * Given the room between the page title and the account controls, and it takes
 * whatever is left — a fixed width would be a stub on a wide screen and would
 * push the account block off a narrow one. */
.tb-search{
  flex:1;min-width:0;max-width:460px;position:relative;
  display:flex;align-items:center;margin:0 4px;
}
.tb-search svg{
  position:absolute;left:14px;width:16px;height:16px;
  color:var(--faint);pointer-events:none;
}
.tb-search input{
  width:100%;height:38px;padding:0 16px 0 38px;
  border:1px solid transparent;border-radius:999px;
  background:var(--surface-3);
  font:inherit;font-size:13px;color:var(--ink);
}
.tb-search input::placeholder{color:var(--faint)}
.tb-search input:focus{
  outline:none;background:var(--surface);border-color:var(--brand);
}

/* The light-or-dark switch, drawn as the switch it is rather than as another
 * icon button, so its state is readable without being pressed. */
.tb-theme{
  flex:none;position:relative;display:flex;align-items:center;
  width:62px;height:30px;padding:0 4px;
  border:0;border-radius:999px;background:var(--surface-3);cursor:pointer;
}
.tb-theme-sun{
  display:grid;place-items:center;width:22px;height:22px;
  border-radius:50%;background:#FEF3C7;color:#F59E0B;
}
.tb-theme-sun svg{width:13px;height:13px}
.tb-theme-knob{
  width:22px;height:22px;margin-left:auto;
  border-radius:50%;background:var(--surface);
  box-shadow:0 1px 2px rgba(16,24,40,.14);
}
[data-theme="dark"] .tb-theme-sun{background:var(--surface-2);color:var(--muted-text)}
[data-theme="dark"] .tb-theme-knob{margin-left:0;order:-1;background:#312E81}

.tb-bell{position:relative}
.tb-badge{
  position:absolute;top:3px;right:3px;
  display:grid;place-items:center;min-width:16px;height:16px;padding:0 4px;
  border-radius:999px;background:var(--danger);
  color:#fff;font-size:9.5px;font-weight:700;line-height:1;
}

/* The account button carries the name and role, so somebody managing several
 * accounts can see which one they are in without opening anything. */
.tb-who{
  flex:none;display:flex;align-items:center;gap:8px;
  height:auto;padding:4px 8px 4px 4px;
  background:none;border:0;
}
.tb-who:hover{background:var(--surface-2)}
.tb-who-t{display:none;text-align:left;line-height:1.25;min-width:0}
.tb-who-t strong{display:block;font-size:13px;font-weight:600;color:var(--ink)}
.tb-who-t em{display:block;font-size:11px;font-style:normal;color:var(--muted-text)}
.tb-who-c{
  display:none;flex:none;color:var(--muted-text);
  font-size:17px;line-height:1;transform:rotate(90deg);
}
@media (min-width:900px){
  .tb-who-t,.tb-who-c{display:block}
}
@media (max-width:820px){
  /* The title goes before the search does: the page name is repeated in the
   * sidebar, and a search box nobody can reach is worse than a missing label. */
  .tb-title{display:none}
}
@media (max-width:560px){
  .tb-search{display:none}
}

/* ═══════════════════════════════════════════ the plan card, in the foot ══ */

.plan-card{
  display:block;margin:0 10px 10px;padding:14px;
  border-radius:14px;text-decoration:none;
  background:linear-gradient(160deg,#8B5CF6,#7C3AED 55%,#6D28D9);
  color:#fff;
}
.plan-i{display:block;margin-bottom:8px;color:#fff}
.plan-i svg{width:21px;height:21px;display:block}
.plan-card strong{display:block;font-size:14px;font-weight:700;line-height:1.2}
.plan-card em{
  display:block;margin-top:4px;font-size:11.5px;font-style:normal;
  line-height:1.4;color:rgba(255,255,255,.85);
}
.plan-b{
  display:flex;align-items:center;justify-content:center;
  height:34px;margin-top:12px;border-radius:9px;
  background:#fff;color:#6D28D9;font-size:12.5px;font-weight:700;
}
.plan-card:hover .plan-b{background:#F4F2FF}

/* Collapsed to the rail, the card would be a violet block with no room for
 * words, so only its mark survives. */
.sidebar.mini .plan-card{padding:10px;text-align:center}
.sidebar.mini .plan-card strong,
.sidebar.mini .plan-card em,
.sidebar.mini .plan-b{display:none}
.sidebar.mini .plan-i{margin:0}
.sidebar.mini .plan-i svg{margin:0 auto}

/* ---------- Touch targets on a phone ----------
 * Measured at 390px across twelve pages: bare checkboxes came out at 13x13
 * (the browser default — only the ones wrapped in .check were sized), and the
 * icon buttons at 29-33px. Both are hard to hit with a thumb.
 *
 * Phone widths only, so nothing about the desktop layout moves. The icon
 * buttons keep their drawn size and gain the hit area around it, which is why
 * this sets min-width/min-height rather than width.
 */
@media (max-width:560px){
  input[type=checkbox]:not(.check input),
  input[type=radio]:not(.check input){width:20px;height:20px}

  .icon-btn,.btn-icon,.ico-btn,.ig-action,.bs-dots{min-width:40px;min-height:40px}

  /* A bare text link in a row of actions is 16px tall; this gives it a
     thumb-sized band without changing how it reads. */
  .bk-wa,.bk-slip{display:inline-flex;align-items:center;min-height:40px}
}

/* ---------- Leads: selecting, and the card's own menu ---------- */

/* The tick stays out of the way until the row is hovered or already chosen, so
 * a list of leads reads as a list rather than as a form. */
.lead-tick{display:flex;align-items:center;flex:0 0 auto;opacity:0;transition:opacity .12s}
.lead:hover .lead-tick,
.lead:focus-within .lead-tick,
.lead.is-picked .lead-tick{opacity:1}
.lead-tick input{width:18px;height:18px;accent-color:var(--brand);cursor:pointer}
.lead.is-picked{background:var(--brand-tint);border-color:var(--brand-tint-2)}

.lead-more{position:relative;flex:0 0 auto}

.lead-menu{
  position:absolute;right:0;top:calc(100% + 4px);z-index:30;min-width:168px;
  display:grid;padding:4px;
  background:var(--surface);border:1px solid var(--border);
  border-radius:var(--radius);box-shadow:var(--shadow-md);
}
.lead-menu[hidden]{display:none}
.lead-menu > *{
  display:block;width:100%;padding:8px 12px;
  font:inherit;font-size:13px;text-align:left;text-decoration:none;
  color:var(--ink-2);background:none;border:0;border-radius:var(--radius-sm);cursor:pointer;
}
.lead-menu > *:hover{background:var(--surface-3);color:var(--ink)}
.lead-menu .is-danger{color:var(--danger-text)}
.lead-menu .is-danger:hover{background:var(--danger-tint)}

/* On a phone the menu would run off the right edge of a narrow card. */
@media (max-width:560px){
  .lead-menu{right:auto;left:0;min-width:148px}
  .lead-tick{opacity:1}
}

/* A numbered setup list, used where a setting needs steps taken somewhere else
 * first — Google's console, a DNS panel. Numbers because the order matters and
 * skipping one is the usual reason it does not work. */
.set-steps{margin:0 0 16px;padding-left:20px;font-size:13px;color:var(--ink-2);line-height:1.8}
.set-steps li{margin-bottom:4px}
.set-steps code{
  display:inline-block;padding:2px 6px;border-radius:6px;
  background:var(--surface-3);font-size:12px;overflow-wrap:anywhere;
}

/* ==========================================================================
   Dark
   ==========================================================================
   The toggle has been setting data-theme="dark" all along, but only two rules
   ever looked for it — both for the toggle's own knob. Nothing else changed, so
   pressing it appeared to do nothing.
   
   Only the colours are redefined here. Radii, shadows, fonts and sizes are the
   same in both themes; the shadows are darkened because a shadow built for a
   white page is invisible on a dark one.
   
   Surfaces go lighter as they come forward, which is the opposite of the light
   theme and is what makes depth read correctly when the page is dark. */
/* Text on a brand fill, in dark mode.
 *
 * --brand is #5A4FE0 in light, where white text is 5.79:1 and fine. In dark it
 * is #8B80FF — a light purple, chosen so it reads against a dark page — and
 * white on that measures 3.17:1. Button text needs 4.5.
 *
 * Measured across 92 pages: 156 of the dark-mode contrast failures were the
 * primary button alone, and 16 rules in six stylesheets share the same shape
 * of a brand fill with white text.
 *
 * Fixed here rather than in each of the sixteen, and by changing the text
 * rather than the fill: --brand is also borders, links and focus rings in dark
 * mode, so darkening it to rescue white text would have moved all of those.
 * Near-black on a light accent is how a light accent is meant to be used.
 *
 * #0B0E14 on #8B80FF is 6.10:1.
 */
[data-theme="dark"] .btn-primary:not(.btn-ghost):not(.btn-outline),
[data-theme="dark"] .btn-primary:not(.btn-ghost):not(.btn-outline):hover,
[data-theme="dark"] .pager button.active,
[data-theme="dark"] .lay-tag,
[data-theme="dark"] .plan-flag,
[data-theme="dark"] .tb-tool.selected,
[data-theme="dark"] .chip.on,
[data-theme="dark"] .bd-cats button.on,
[data-theme="dark"] .ns-cats button.on,
[data-theme="dark"] .cb-step.on .cb-step-n,
[data-theme="dark"] .act-opt.on,
[data-theme="dark"] .tpl-preview-bar .btn.on,
[data-theme="dark"] .fb-try-msg.you,
[data-theme="dark"] .pick-chip.selected,
[data-theme="dark"] .pick-tick{
  color:var(--bg);
}

/* Text on a status fill in dark mode.
 *
 * The same shape as the brand button: --danger is #D92D20 in light, where
 * white reads, and #F87171 in dark — a light red, so white on it is 2.77:1.
 * Near-black instead. */
/* Not .btn-ghost: a ghost button has no fill, so near-black on it is
   invisible — measured 1.09:1, which I caused and then found. The override
   only applies where the danger fill actually is. */
[data-theme="dark"] .btn-danger:not(.btn-ghost):not(.btn-outline),
[data-theme="dark"] .btn-danger:not(.btn-ghost):not(.btn-outline):hover{
  color:var(--bg);
}

/* --brand-600 and --brand-700 used as a text colour.
 *
 * They are darker shades of the brand, which is right in light mode where the
 * brand is already dark. In dark mode --brand is a light purple and these two
 * are the mid shades #7A6EF5 and #6B5FE8 — measured 3.09 to 3.75:1 on a card,
 * so they read as almost-invisible on the thing they are meant to stand out
 * from. --brand is the readable one there.
 *
 * Only where they are text. As a fill or a border they are still correct. */
[data-theme="dark"] .pd-args dt,
[data-theme="dark"] .lay-fallback,
/* .pl-cat is deliberately not here: poster.css defines .pl-cat.on twice and
   the winning one already uses --surface on an --ink fill. Its child rules are
   fixed in poster.css itself, where the cascade is visible, rather than being
   guessed at from a dark override. */
[data-theme="dark"] .ps-more summary,
[data-theme="dark"] .tpl-opt.on .tpl-opt-n{
  color:var(--brand);
}

/* White on a light status fill. --danger is #F87171 in dark, so white on it is
   2.77:1 — the same shape as the buttons. */
[data-theme="dark"] .tb-badge{
  color:var(--bg);
}

[data-theme="dark"]{
  --bg:#0B0E14;
  --surface:#131822;
  --surface-2:#191F2B;
  --surface-3:#212936;
  --surface-muted:var(--surface-2);

  --border:#2A3341;
  --border-strong:#3A4553;

  /* Not pure white: on a dark surface it glares and makes long text tiring. */
  --ink:#E8EBF0;
  --ink-2:#C3C9D4;
  --muted:#93A0B3;
  --faint:#6B7789;
  --text:var(--ink);

  /* Lifted, because the light-theme brand is too dark to read against #0B0E14. */
  --brand:#8B80FF;
  --brand-600:#7A6EF5;
  --brand-700:#6B5FE8;
  --brand-tint:rgba(139,128,255,.14);
  --brand-tint-2:rgba(139,128,255,.22);

  --ok:#34D399;
  /* The status text shades are the plain status colours here: the light-mode
     shades are dark reds and browns and measure under 3:1 on a dark page. */
  /* --muted already measures 5.52 to 7.29:1 on the dark surfaces, so there is
     nothing to darken here. --faint does not: 3.22 to 4.25:1. */
  --muted-text:var(--muted);

  --danger-text:var(--danger);
  --warn-text:var(--warn);
  --ok-text:var(--ok);
  --info-text:var(--info);

  --ok-tint:rgba(52,211,153,.13);
  --warn:#FBBF24;
  --warn-tint:rgba(251,191,36,.13);
  --danger:#F87171;
  --danger-tint:rgba(248,113,113,.14);
  --info:#60A5FA;
  --info-tint:rgba(96,165,250,.13);

  --shadow-xs:0 1px 2px rgba(0,0,0,.40);
  --shadow-sm:0 2px 8px rgba(0,0,0,.45);
  --shadow-md:0 10px 28px rgba(0,0,0,.55);
  --shadow-lg:0 24px 60px rgba(0,0,0,.65);

  /* So form controls, scrollbars and the browser's own chrome follow. */
  color-scheme:dark;
}

/* Images and charts are made for a light page. Knocking the brightness down a
   little stops them glowing out of a dark one, without washing them out. */
[data-theme="dark"] img:not([src*=".svg"]),
[data-theme="dark"] .qr-preview img{filter:brightness(.92)}

/* The header search, and what it suggests.
 *
 * The list hangs below the box rather than pushing the header taller: a header
 * that changes height as somebody types moves the whole page under them.
 */
/* .tb-search itself is already defined above; redefining it here dropped the
   display:flex that keeps the icon and the input on one line. */
.tb-hits{
  position:absolute;top:calc(100% + 6px);left:0;right:0;z-index:60;
  max-height:min(60vh,380px);overflow-y:auto;padding:6px;
  background:var(--surface);border:1px solid var(--border);
  border-radius:var(--radius);box-shadow:var(--shadow-md);
}
.tb-hit{
  display:flex;align-items:baseline;gap:8px;
  padding:8px 10px;border-radius:8px;text-decoration:none;
}
.tb-hit strong{font-size:14px;font-weight:600;color:var(--ink)}
.tb-hit em{font-style:normal;font-size:12px;color:var(--muted-text)}
/* One highlight for both the keyboard and the mouse, so arrowing down and
   hovering cannot disagree about what enter would open. */
.tb-hit.on{background:var(--surface-3)}
.tb-hit.on strong{color:var(--brand-700)}
.tb-hits-none{margin:0;padding:10px;font-size:13px;color:var(--muted-text)}

@media (max-width:720px){
  /* On a phone the box is behind the burger anyway, and a 460px dropdown would
     hang off the screen. */
  .tb-hits{left:auto;right:0;width:min(88vw,340px)}
}

/* Segmented control.
 *
 * Two or three choices, all worth showing, one press to move between them.
 * Lived in qr-builder.css until a second module needed it.
 */
.seg{display:inline-flex;padding:4px;gap:2px;background:var(--surface-3);border-radius:10px}
.seg-btn{padding:6px 14px;border:none;background:transparent;border-radius:8px;font:inherit;font-size:13px;
         font-weight:600;color:var(--muted-text);cursor:pointer;display:inline-flex;align-items:center;gap:6px}
.seg-btn svg{width:16px;height:16px}
.seg-btn.active{background:var(--surface);color:var(--ink);box-shadow:0 1px 2px rgba(16,24,40,.08)}
.seg-btn:focus-visible{outline:2px solid var(--brand);outline-offset:1px}

/* The Google sign-in button.
 *
 * The mark sits at its own size beside the words rather than being scaled with
 * the text — Google's brand terms ask for that, and a squashed logo on a
 * sign-in button reads as a phishing page.
 */
.btn-google{
  display:flex;align-items:center;justify-content:center;gap:10px;
  background:var(--surface);
}
.btn-google svg{flex:0 0 auto;width:18px;height:18px}

/* The divider between the two ways in. A line with words in it, so it reads as
   "or" rather than as a heading for what follows. */
.auth-or{
  display:flex;align-items:center;gap:12px;
  margin:20px 0;
  /* --muted-text, not --faint. This is my own rule from the Google sign-in
     work and the sweep skipped it — 2.38:1 on the sign-in page, and it is the
     only label telling somebody the form below is the other way in. */
  font-size:11px;color:var(--muted-text);
  text-transform:uppercase;letter-spacing:.06em;
}
.auth-or::before,
.auth-or::after{content:'';flex:1 1 auto;height:1px;background:var(--border)}

/* Four numbers around a box, in the form builder's panel.
 *
 * One letter per side rather than a word: four labelled boxes in a narrow
 * panel wrap onto four lines and stop reading as a set.
 */
.fb-box4{display:grid;grid-template-columns:repeat(4,1fr);gap:6px}
.fb-box4-cell{display:grid;gap:4px;justify-items:center;min-width:0}
.fb-box4-cell > span{
  font-size:10px;font-weight:700;color:var(--muted-text);
  text-transform:uppercase;letter-spacing:.04em;cursor:help;
}
.fb-box4-cell .input{
  width:100%;min-width:0;padding:6px 4px;text-align:center;
  font-size:12px;font-variant-numeric:tabular-nums;
}
/* Hide the spinner arrows: in a 4-across row they take the space the number
   needs, and the value is typed rather than nudged. */
.fb-box4-cell .input::-webkit-outer-spin-button,
.fb-box4-cell .input::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}
.fb-box4-cell .input[type=number]{-moz-appearance:textfield;appearance:textfield}

/* A value the phone is inheriting from desktop rather than setting itself.
   Shown, so it is obvious what the block will actually do, but greyed so it
   is equally obvious it was not set here. */
.fb-box4-cell .input.is-inherited{color:var(--faint);font-style:italic}

.fb-inherit-note{
  margin:14px 0 0;padding:10px 12px;border-radius:var(--radius-sm);
  background:var(--surface-2);
  font-size:11px;line-height:1.6;color:var(--muted-text);
}

/* The Google link row on the profile page. */
.pf-google{
  display:flex;align-items:center;justify-content:space-between;gap:16px;
  flex-wrap:wrap;
  padding:14px 16px;border-radius:var(--radius);
  border:1px solid var(--border);background:var(--surface-2);
}
.pf-google.is-on{border-color:var(--ok-text);background:var(--ok-tint)}
.pf-google-who strong{display:block;font-size:13px;font-weight:700;color:var(--ok-text)}
.pf-google-who em{
  font-style:normal;font-size:12px;color:var(--ink-2);
  overflow-wrap:anywhere;
}
.pf-google form{margin:0}

/* Said before Unlink is pressed, not after it is refused — somebody with no
   password is one click from an account nobody can open. */
.pf-google-warn{
  /* A readable line length, like every other paragraph. Mine, and it was
     running to about 183 characters at 1440px. */
  max-width:70ch;
  margin:12px 0 0;padding:10px 12px;border-radius:var(--radius-sm);
  background:var(--warn-tint);border:1px solid var(--warn);
  font-size:12px;line-height:1.6;color:var(--warn-text);
}

/* Site settings: getting around eleven sections.
 *
 * Measured before this was added: the page runs eight screens on a desktop and
 * twelve on a phone, with no navigation at all and one Save button at the very
 * bottom. Both are additive — no section's markup changed.
 */
.st-jump{
  display:flex;gap:6px;flex-wrap:wrap;
  position:sticky;top:calc(var(--topbar-h) + 8px);z-index:30;
  margin:0 0 16px;padding:8px;
  border-radius:12px;border:1px solid var(--border);
  /* Opaque enough to read over whatever scrolls behind it. */
  background:color-mix(in srgb, var(--surface) 92%, transparent);
  backdrop-filter:saturate(160%) blur(8px);
}
.st-jump-link{
  padding:6px 12px;border-radius:8px;
  font-size:12.5px;font-weight:600;color:var(--muted-text);
  text-decoration:none;white-space:nowrap;
  transition:background .14s,color .14s;
}
.st-jump-link:hover{background:var(--surface-2);color:var(--ink)}
.st-jump-link:focus-visible{outline:2px solid var(--brand);outline-offset:1px}
/* Where you are, not only where you could go. */
.st-jump-link.on{background:var(--brand-tint);color:var(--brand-700)}

/* Save, always reachable. */
.st-save{
  position:sticky;bottom:0;z-index:20;
  display:flex;align-items:center;justify-content:space-between;gap:16px;
  flex-wrap:wrap;
  background:color-mix(in srgb, var(--surface) 94%, transparent);
  backdrop-filter:saturate(160%) blur(8px);
  border-top:1px solid var(--border);
}
.st-save-note{font-size:12px;color:var(--muted-text)}

/* Sticky was wrong: it only sticks while the element is in view, and this row
 * sits at the foot of a page six thousand pixels tall — so from the top of the
 * page Save was nowhere on screen, which is the thing it was meant to fix.
 * Fixed, with room reserved below the form so nothing hides under it.
 */
.st-save{
  position:fixed;left:0;right:0;bottom:0;
  border-radius:0;border-top:1px solid var(--border);
  padding:12px 24px;
}
/* The sidebar sits to the left on a desktop, so the bar starts after it. */
@media (min-width:961px){
  .st-save{left:var(--sidebar-w,260px)}
}
#st-form{padding-bottom:84px}

/* And the jump bar is sticky under the topbar, so a section scrolled to by
 * name would land beneath it. Reserved here rather than by guessing an offset
 * in the script — the browser does the arithmetic and stays right if the bar
 * ever changes height. */
/* The offset is set by the script from the bar's measured height, because a
 * fixed number cannot be right: eleven links fit on one row at 1280px and wrap
 * to two at 820px, so the bar is twice as tall there. A 96px guess cleared the
 * bar on a desktop and left the heading 19px under it on a tablet.
 *
 * This is the fallback for the moment before the script runs. */
#st-form .card{scroll-margin-top:var(--st-jump-clear, calc(var(--topbar-h) + 96px))}

@media (max-width:560px){
  /* On a phone the bar scrolls sideways rather than growing to four rows and
     taking a third of the screen. */
  .st-jump{flex-wrap:nowrap;overflow-x:auto;scrollbar-width:none}
  .st-jump::-webkit-scrollbar{display:none}
  /* The note is the first thing to go: the button is what matters. */
  .st-save-note{display:none}
  .st-save{justify-content:flex-end}
}

@media (prefers-reduced-motion:reduce){
  .st-jump-link{transition:none}
}

/* Touch targets where a finger is used.
 *
 * WCAG 2.5.8 (AA) asks 24px and every control here already passes it. 2.5.5
 * (AAA) asks 44px, and .btn is 40px, .btn-sm and .icon-btn 34px.
 *
 * Raising them everywhere was measured first: 413 .btn uses, 198 .btn-sm, 49
 * action rows and 29 rules that set a height on something button-shaped. Doing
 * it globally reflows every toolbar in the project, and on a desktop 40px with
 * a mouse was never the problem AAA is about.
 *
 * So it applies only where the pointer is coarse — a finger or a stylus. A
 * mouse keeps the design as it was, and nothing on a desktop moves by a pixel.
 *
 * min-height rather than height, so a button with two lines of text still
 * grows instead of clipping.
 */
@media (pointer: coarse) {
  .btn,
  .btn-sm,
  .icon-btn,
  .ico-btn,
  .btn-icon,
  .seg-btn,
  .step-btn,
  .dz-btn {
    min-height: 44px;
  }

  /* Square controls need the width too, or a 44px-tall icon button is still a
     34px-wide target. */
  .icon-btn,
  .ico-btn,
  .btn-icon {
    min-width: 44px;
  }

  /* A menu item is a full-width row, so only its height is short. */
  .dropdown-menu button,
  .dropdown-menu a {
    min-height: 44px;
  }
}
