/**
 * Copyright (c) 2026 AInvirion LLC. All Rights Reserved.
 *
 * PROPRIETARY AND CONFIDENTIAL
 *
 * This source code is the proprietary and confidential property of AInvirion LLC.
 * Unauthorized copying, modification, distribution, or use of this file,
 * via any medium, is strictly prohibited without written permission from AInvirion LLC.
 */

/* The console's design system.
 *
 * This is the surface the *owner* sees — sign-in, their sites, the editor, the
 * import flow, the admin screens. It is deliberately separate from
 * `zzsites/design/`, which styles the sites we publish: those are a customer's
 * brand and are governed by the section property allowlist (spec 01 §1.5).
 * Nothing here is subject to that allowlist, and nothing here ever reaches a
 * published page.
 *
 * It used to be an inline `<style>` block in `base.html`, which is why every
 * screen had square corners, uppercase buttons and no accent colour: there was
 * nowhere to put a component, so every template invented its own spacing. The
 * rule now is that a screen composes the classes below and adds nothing of its
 * own. If a screen needs something this file does not have, it goes in here.
 *
 * Layers, in order: tokens, reset, shell, typography, controls, components.
 */

/* ==================================================================== */
/* Tokens                                                               */
/* ==================================================================== */

:root {
  /* Ink is near-black with a blue cast rather than pure #000 — black on white
     is harsher than any screen needs, and the cast ties the greys to the
     accent so the palette reads as one decision. */
  --ink:        #14112b;
  --ink-2:      #3f3b5c;
  --sub:        #6e6a89;
  --faint:      #9b98ad;

  --bg:         #ffffff;
  --panel:      #f7f7fb;
  --panel-2:    #f0eff7;
  --line:       #e6e4f0;
  --line-2:     #f0eff6;

  --accent:     #5b4bf5;
  --accent-ink: #ffffff;
  --accent-dark:#4638d8;
  --accent-soft:#eeecff;
  --accent-line:#cfcaff;

  --ok:         #0d7346;
  --ok-soft:    #e8f6ef;
  --ok-line:    #bde3d0;
  --warn:       #8a5300;
  --warn-soft:  #fdf5e6;
  --warn-line:  #eeddb8;
  --danger:     #b3261e;
  --danger-soft:#fdecea;
  --danger-line:#f2c6c2;

  /* Type. One family, four sizes above the base and two below it. */
  --font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
          "Helvetica Neue", Arial, sans-serif;
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;

  --fs-xs:   0.75rem;
  --fs-sm:   0.8125rem;
  --fs-base: 0.9375rem;
  --fs-md:   1.0625rem;
  --fs-lg:   1.375rem;
  --fs-xl:   1.875rem;
  --fs-2xl:  2.5rem;

  /* Space. A 4px scale — every gap in the console is one of these. */
  --sp-1: 0.25rem;  --sp-2: 0.5rem;   --sp-3: 0.75rem;
  --sp-4: 1rem;     --sp-5: 1.5rem;   --sp-6: 2rem;
  --sp-7: 3rem;     --sp-8: 4rem;

  --r-sm:   6px;
  --r:      10px;
  --r-lg:   14px;
  --r-pill: 999px;

  --shadow-sm: 0 1px 2px rgb(20 17 43 / 0.06);
  --shadow:    0 1px 3px rgb(20 17 43 / 0.06), 0 8px 24px -10px rgb(20 17 43 / 0.14);
  --shadow-lg: 0 2px 6px rgb(20 17 43 / 0.06), 0 20px 48px -16px rgb(20 17 43 / 0.20);

  --header-h: 3.5rem;

  color-scheme: light;
}

/* ==================================================================== */
/* Reset                                                                */
/* ==================================================================== */

*, *::before, *::after { box-sizing: border-box; }

body {
  margin: 0;
  font-family: var(--font);
  font-size: var(--fs-base);
  line-height: 1.6;
  color: var(--ink);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

img { max-width: 100%; height: auto; }

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--r-sm);
}

/* ==================================================================== */
/* Shell — the header and the page column                               */
/* ==================================================================== */

.site-header {
  display: flex;
  align-items: center;
  gap: var(--sp-5);
  height: var(--header-h);
  padding: 0 var(--sp-5);
  background: rgb(255 255 255 / 0.85);
  backdrop-filter: saturate(180%) blur(12px);
  border-bottom: 1px solid var(--line);
  position: sticky;
  top: 0;
  z-index: 20;
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  font-size: var(--fs-base);
  font-weight: 650;
  letter-spacing: -0.02em;
  color: var(--ink);
  text-decoration: none;
  flex: none;
}
.brand .bolt { display: block; flex: none; color: var(--accent); }

.site-nav-group { display: flex; align-items: center; gap: var(--sp-3); min-width: 0; }
.site-nav { display: flex; align-items: center; gap: var(--sp-1); }
.site-nav a {
  font-size: var(--fs-sm);
  font-weight: 500;
  color: var(--sub);
  text-decoration: none;
  padding: var(--sp-2) var(--sp-3);
  border-radius: var(--r-sm);
  white-space: nowrap;
}
.site-nav a:hover { color: var(--ink); background: var(--panel); }
.site-nav a[aria-current="page"] { color: var(--ink); background: var(--panel-2); }

/* The admin group sits behind a hairline so it reads as a different tier of
   navigation rather than four more of the owner's own screens. */
.site-nav.admin { padding-left: var(--sp-3); border-left: 1px solid var(--line); }

.who {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  font-size: var(--fs-sm);
  color: var(--sub);
}
.who .avatar {
  width: 1.75rem; height: 1.75rem;
  border-radius: var(--r-pill);
  background: var(--accent-soft);
  color: var(--accent-dark);
  border: 1px solid var(--accent-line);
  font-size: var(--fs-xs);
  font-weight: 650;
  display: grid; place-items: center;
  text-transform: uppercase;
  overflow: hidden;
  flex: none;
}
.who img.avatar { object-fit: cover; border-color: var(--line); }
.who .name { color: var(--ink); font-weight: 500; }
.pick-language {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  margin: 0;
  color: var(--sub);
}
.language-icon { display: flex; color: var(--faint); }
.language-options {
  display: inline-flex;
  padding: 2px;
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  background: var(--panel);
}
.language-options button {
  min-width: 2.25rem;
  border: 0;
  border-radius: var(--r-pill);
  padding: 2px var(--sp-2);
  font: inherit;
  font-size: var(--fs-xs);
  font-weight: 650;
  line-height: 1.5;
  color: var(--sub);
  background: transparent;
}
.language-options button:hover { color: var(--ink); }
.language-options button[aria-pressed="true"] {
  color: var(--accent-dark);
  background: var(--bg);
  box-shadow: var(--shadow-sm);
  cursor: default;
}
.site-header > .pick-language { margin-left: auto; }
.who + .pick-language { margin-left: 0; }
.sign-out { margin: 0; }
.sign-out button {
  font: inherit;
  font-size: var(--fs-sm);
  color: var(--sub);
  background: none;
  border: 0;
  padding: var(--sp-1) var(--sp-2);
  border-radius: var(--r-sm);
  cursor: pointer;
  white-space: nowrap;
}
.sign-out button:hover { color: var(--ink); background: var(--panel); }

/* A phone cannot fit the brand, both navs and the account on one row, and a
   header that does not fit is a page that scrolls sideways — every screen at
   once, because the header is on every screen. Below the breakpoint the pair
   of navs drops to a second row and the account keeps its place beside the
   brand, so nothing is hidden and nothing overflows. */
@media (max-width: 47.99em) {
  .site-header {
    flex-wrap: wrap;
    height: auto;
    min-height: var(--header-h);
    padding-block: var(--sp-2);
    gap: var(--sp-3);
    row-gap: var(--sp-1);
  }
  .brand { order: 1; }
  .who   { order: 2; }
  .pick-language { order: 3; }
  .sign-out { order: 4; }
  .site-nav-group {
    order: 5;
    flex-basis: 100%;
    /* Enough room for four items on the narrowest phone; scrolling the strip
       beats dropping a screen out of reach. */
    overflow-x: auto;
    scrollbar-width: none;
  }
  .site-nav-group::-webkit-scrollbar { display: none; }
  .site-nav a { padding-inline: var(--sp-2); }
  /* The avatar and the sign-out still identify the account; the given name is
     the part that can go when the row is short of room. */
  .who .name { display: none; }
}

@media (max-width: 30rem) {
  /* Give the brand its own row so identity, language and sign-out still sit
     together, in that order, without squeezing or spilling at 320px. */
  .site-header:has(.who) .brand { flex-basis: 100%; }
}

main {
  max-width: 52rem;
  margin: 0 auto;
  padding: var(--sp-7) var(--sp-5) var(--sp-8);
}
/* The editor is a two-pane workbench and wants the whole window — and means
   it (#374). Capped at 92rem it left the preview about 830px on a 1440px
   screen to show a page that will be looked at full width. */
main:has(.workbench) { max-width: 110rem; padding: var(--sp-5) var(--sp-5) var(--sp-7); }
/* So does anything that says so outright. */
main:has(> .wide) { max-width: 76rem; }
/* A single centred card — sign-in, invite, waitlist. */
main:has(> .auth-card) { max-width: 27rem; padding-top: var(--sp-8); }

/* ==================================================================== */
/* Typography                                                           */
/* ==================================================================== */

h1 {
  font-size: var(--fs-xl);
  font-weight: 650;
  letter-spacing: -0.025em;
  line-height: 1.2;
  margin: 0 0 var(--sp-3);
}
h2 {
  font-size: var(--fs-md);
  font-weight: 650;
  letter-spacing: -0.015em;
  margin: var(--sp-7) 0 var(--sp-3);
}
h3 {
  font-size: var(--fs-base);
  font-weight: 650;
  letter-spacing: -0.01em;
  margin: var(--sp-5) 0 var(--sp-2);
}
/* The first heading on a screen has the page's own top padding above it. */
h1:first-child, h2:first-child, h3:first-child { margin-top: 0; }

p { margin: 0 0 var(--sp-4); }
p:last-child { margin-bottom: 0; }

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; text-underline-offset: 3px; }

.muted { color: var(--sub); }
.small { font-size: var(--fs-sm); }
.lede  { font-size: var(--fs-md); color: var(--ink-2); line-height: 1.55; }

code, kbd, samp, pre { font-family: var(--mono); font-size: 0.92em; }
code {
  background: var(--panel);
  border: 1px solid var(--line-2);
  border-radius: var(--r-sm);
  padding: 0.1em 0.35em;
}
pre {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--r);
  padding: var(--sp-3);
  overflow-x: auto;
}
pre code { background: none; border: 0; padding: 0; }

/* --- the whole import report, for whoever wants it (#358) ------------- */
details.report { margin: var(--sp-5) 0; }
/* The card ends where its last line does. */
.card > ul.findings:last-child { margin-bottom: 0; }
details.report h3 {
  font-size: var(--fs-sm);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--sub);
  margin: var(--sp-5) 0 var(--sp-2);
}
details.report h3:first-child { margin-top: 0; }

/* --- a pasted section's own markup (#248) ---------------------------- */
textarea.markup {
  font-family: var(--mono);
  font-size: var(--fs-sm);
  line-height: 1.5;
  min-height: 24rem;
  /* Imported markup arrives as one long line, so it wraps rather than
     scrolling sideways. Wrapping is display only — what is typed is what is
     saved, byte for byte. */
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
ul.markup-history { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--sp-3); }
ul.markup-history li { display: flex; align-items: baseline; gap: var(--sp-3); }

/* --- site settings (#355) ------------------------------------------- */
/* Two columns where there is room, so the whole site fits on one screen
   instead of a column somebody scrolls through looking for the menu. */
.settings-cols { display: grid; gap: var(--sp-5); align-items: start; }
@media (min-width: 64em) {
  .settings-cols { grid-template-columns: 1fr 1fr; }
}
.settings-col { display: grid; gap: var(--sp-5); align-content: start; }
ul.settings-links { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--sp-4); }
ul.settings-links li { display: grid; gap: var(--sp-1); }
ul.settings-links a { font-weight: 550; }

/* A label that a screen reader reads and nobody sees (#353). Five of the
   editor's labels asked for this and nothing defined it, so every menu row
   printed "Menu label" and "Where it goes" beside its own inputs. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* The invite code, wherever it is shown to be read aloud or typed. */
.code {
  font-family: var(--mono);
  font-size: var(--fs-md);
  font-weight: 600;
  letter-spacing: 0.08em;
  background: none; border: 0; padding: 0;
}

/* The ready-made message a customer copies into an assistant's chat (#505).
   `.code` is dressed for an invite code read aloud — bold, widely tracked, a
   line long. A paragraph wearing that is a wall, so this puts it back to
   ordinary prose in a box you can see the edges of. */
.prompt-copy {
  display: block;
  font-size: var(--fs-sm);
  font-weight: 400;
  letter-spacing: normal;
  line-height: 1.5;
  white-space: pre-wrap;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  padding: var(--sp-3);
  margin-bottom: var(--sp-2);
}

/* A page's title block: heading, one line under it, and nothing else. */
.page-head { margin: 0 0 var(--sp-6); }
.page-head h1 { margin-bottom: var(--sp-2); }
.page-head p  { margin: 0; color: var(--sub); }

/* ==================================================================== */
/* Controls                                                             */
/* ==================================================================== */

button, .btn { cursor: pointer; }
button:disabled, .btn:disabled { cursor: not-allowed; opacity: 0.5; }

/* One button shape — a pill — in three weights. Sentence case, never
   uppercase-with-tracking: that treatment is what dated every screen. */
.btn, button.primary, button.quiet, button.danger {
  font: inherit;
  font-size: var(--fs-sm);
  font-weight: 550;
  line-height: 1.2;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  padding: var(--sp-3) var(--sp-5);
  border-radius: var(--r-pill);
  border: 1px solid transparent;
  text-decoration: none;
  white-space: nowrap;
  transition: background-color .12s ease, border-color .12s ease, color .12s ease;
}
.btn:hover, button.primary:hover, button.quiet:hover, button.danger:hover {
  text-decoration: none;
}

button.primary, .btn.primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink);
}
button.primary:hover, .btn.primary:hover {
  background: var(--accent-dark);
  border-color: var(--accent-dark);
}

button.quiet, .btn.quiet {
  background: var(--bg);
  border-color: var(--line);
  color: var(--ink);
  box-shadow: var(--shadow-sm);
}
button.quiet:hover, .btn.quiet:hover { border-color: var(--faint); background: var(--panel); }

button.danger, .btn.danger {
  background: var(--bg);
  border-color: var(--danger-line);
  color: var(--danger);
}
button.danger:hover, .btn.danger:hover { background: var(--danger-soft); border-color: var(--danger); }

/* A button that is really a link — "Delete this page", "Undo". The element
   selector is carried through every rule here because the pill variants above
   are written as `button.danger`, which outranks a bare `.btn-link`. */
button.btn-link, .btn-link {
  font: inherit;
  font-size: var(--fs-sm);
  background: none;
  border: 0;
  padding: 0;
  color: var(--sub);
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}
button.btn-link:hover, .btn-link:hover { color: var(--ink); }
button.btn-link.danger, .btn-link.danger { color: var(--danger); }

/* A small square control that carries one glyph — the reorder arrows. */
/* The site bar's leading icons (#430). `.btn` is already an inline-flex row
   with a gap, so the icon needs no positioning — what it does need is to be
   told not to shrink: in a bar that wraps on a narrow screen a flex child
   with intrinsic size is otherwise squashed before the text is. */
.icon { flex: none; }
.icon-btn {
  font: inherit;
  font-size: var(--fs-sm);
  width: 1.875rem; height: 1.875rem;
  display: inline-grid; place-items: center;
  padding: 0;
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  color: var(--sub);
  cursor: pointer;
  transition: color .12s ease, border-color .12s ease, background-color .12s ease;
}
.icon-btn:hover { color: var(--ink); border-color: var(--faint); background: var(--panel); }

/* --- fields --------------------------------------------------------- */

form.stack { display: grid; gap: var(--sp-4); }
form.stack > button, form.stack > .actions { justify-self: start; }

label {
  display: grid;
  gap: var(--sp-2);
  font-size: var(--fs-sm);
  font-weight: 550;
  color: var(--ink);
}
label .muted, label .hint { font-weight: 400; }

input, textarea, select {
  font: inherit;
  font-size: var(--fs-base);
  font-weight: 400;
  color: var(--ink);
  padding: var(--sp-3);
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--bg);
  width: 100%;
  transition: border-color .12s ease, box-shadow .12s ease;
}
input::placeholder, textarea::placeholder { color: var(--faint); }
input:hover, textarea:hover, select:hover { border-color: var(--faint); }
input:focus, textarea:focus, select:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
textarea { resize: vertical; min-height: 6rem; line-height: 1.55; }
textarea.richtext, textarea.textarea { min-height: 7rem; }

select {
  appearance: none;
  padding-right: var(--sp-7);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath fill='none' stroke='%236e6a89' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round' d='M1 1.5 6 6.5l5-5'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right var(--sp-3) center;
}

/* Checkboxes and radios keep their own size. Left to the rule above they
   stretched to the full width of a choice card and pushed its words into a
   column a few letters wide (#353). */
input[type="checkbox"], input[type="radio"] {
  width: 1.05rem; height: 1.05rem;
  accent-color: var(--accent);
  padding: 0;
  justify-self: start;
}

/* The browser's own file control is the single most dated thing on any
   screen. Hide it and drive a styled label instead; the input keeps working
   exactly as it did, so nothing about validation or submission changes. */
.file {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-4);
  border: 1px dashed var(--line);
  border-radius: var(--r);
  background: var(--panel);
  font-weight: 400;
  color: var(--sub);
  cursor: pointer;
  transition: border-color .12s ease, background-color .12s ease;
}
.file:hover { border-color: var(--accent-line); background: var(--accent-soft); }
/* The native control is clipped to 1px, so its own focus ring lands on
   something nobody can see. The ring has to move to the thing that is
   visible, or tabbing to the picker looks like a control that did not
   respond. Same treatment a focused text field gets, for the same reason. */
.file:focus-within {
  border-color: var(--accent);
  border-style: solid;
  box-shadow: 0 0 0 3px var(--accent-soft);
}
.file:focus-within .file-cue { border-color: var(--accent); }

/* Adding a photo from the field that wants one (#378). The select and the way
   to add sit on one line, because they are one decision: which photo. */
.photo-field { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; }
.photo-field select { flex: 1 1 12rem; min-width: 0; }
/* Not a `+` on its own: a sign with no word beside it reads as nothing to
   somebody who has not already worked out what it does. */
.photo-field .add-photo { white-space: nowrap; }
.photo-dialog {
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--bg);
  color: var(--ink);
  padding: var(--sp-5);
  /* Room to read the description field, and never wider than a phone. */
  width: min(28rem, calc(100vw - var(--sp-5) * 2));
}
.photo-dialog::backdrop { background: rgb(0 0 0 / 0.4); }
.photo-dialog > * + * { margin-top: var(--sp-4); }
/* The HTML editor's modal (#422). It borrows the photo dialog's frame and
   widens it: 28rem was sized for a description field, and a section's markup
   wrapped into that is an unreadable ribbon. Still never wider than the
   screen, which is the half of that rule worth keeping. */
.markup-dialog { width: min(56rem, calc(100vw - var(--sp-5) * 2)); }
.markup-dialog .markup { width: 100%; }
/* Only while the request is in flight, and it takes the row's height whether
   or not it is showing — so the dialog does not jump when it appears. */
.htmx-indicator { visibility: hidden; }
.htmx-request .htmx-indicator, .htmx-request.htmx-indicator { visibility: visible; }
.file input[type="file"] {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap;
}
.file .file-cue {
  font-size: var(--fs-sm);
  font-weight: 550;
  color: var(--ink);
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  padding: var(--sp-2) var(--sp-4);
  flex: none;
}
.file .file-name { font-size: var(--fs-sm); min-width: 0; overflow-wrap: anywhere; }
/* Big enough to tell two photos apart, small enough that the dialog does not
   resize around it. No `display` here on purpose: the script hides it with
   the `hidden` attribute, and any author `display` would outrank the
   browser's own `[hidden]` rule and leave an empty box behind. */
.file .file-preview {
  flex: none;
  width: 3rem;
  height: 3rem;
  object-fit: cover;
  border-radius: var(--r-sm);
  border: 1px solid var(--line);
  background: var(--bg);
}

label.has-error input, label.has-error textarea, label.has-error select { border-color: var(--danger); }
label.has-error input:focus, label.has-error textarea:focus, label.has-error select:focus {
  box-shadow: 0 0 0 3px var(--danger-soft);
}
.error { color: var(--danger); font-size: var(--fs-sm); font-weight: 400; }
/* A live region a script writes into is **not hidden when it is empty**, and
   that is the whole of the rule (#398). `display: none` is what the `hidden`
   attribute already was: either takes the element out of the accessibility
   tree, so the text and the element arrive there together and the change is
   not reliably announced — which for a screen-reader customer is the silence
   this was meant to end. An empty inline span occupies no room anyway, so
   there is nothing to hide; if one ever needs to be a block, take its margin
   and padding away rather than its display. */

fieldset {
  border: 1px solid var(--line);
  border-radius: var(--r);
  padding: var(--sp-4);
  margin: 0;
  display: grid;
  gap: var(--sp-4);
  background: var(--bg);
}
legend {
  font-size: var(--fs-xs);
  font-weight: 650;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--sub);
  padding: 0 var(--sp-2);
}

/* One of a small set of answers, each with a sentence saying what it means
   (#99). A radio and its words are one target, because a sentence somebody
   has to read and then find the button for is a sentence they skip. */
label.choice {
  display: flex;
  gap: var(--sp-3);
  align-items: start;
  padding: var(--sp-3);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  cursor: pointer;
}
label.choice:has(input:checked) { border-color: var(--accent); background: var(--accent-soft); }
label.choice input { margin-top: 0.2rem; flex: none; }
label.choice strong { display: inline; }

/* Fields side by side where they are short and read as a set — an address,
   a week of opening hours. One column on a phone, always. */
.field-grid {
  display: grid;
  gap: var(--sp-4);
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
}
.field-grid > label.wide { grid-column: 1 / -1; }

/* A dropdown question's own choices: one box per choice the customer has
   written, plus one empty box to add the next. Stacked, because they are a
   list rather than a row of separate fields — and named `option-list` rather
   than `options`, which would read as a sibling of the `.choice` label above
   and is a different thing entirely. */
.option-list { display: flex; flex-direction: column; gap: var(--sp-2); }

/* Cards stacked down a form need air between them, and the buttons need more
   than that so they do not read as part of the last card. */
form > .card + .card { margin-top: var(--sp-4); }
form > .card + .actions { margin-top: var(--sp-5); }
.card > h2 { margin: 0 0 var(--sp-4); font-size: var(--fs-base); }
.card > h2 + p { margin-top: calc(var(--sp-4) * -1 + var(--sp-1)); }

/* The days a business is open — a row of toggles, not seven more rows of
   form. `label` is a grid everywhere else, so these opt out of it. */
fieldset.days {
  border: 0;
  padding: 0;
  margin: 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
  align-items: center;
}
fieldset.days legend {
  padding: 0;
  margin-bottom: var(--sp-2);
  float: left;
  width: 100%;
}
label.day {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  font-size: var(--fs-sm);
  font-weight: 500;
  padding: var(--sp-2) var(--sp-3);
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  background: var(--bg);
  cursor: pointer;
  user-select: none;
  transition: border-color .12s ease, background-color .12s ease;
}
label.day:hover { border-color: var(--faint); }
label.day:has(input:checked) {
  border-color: var(--accent);
  background: var(--accent-soft);
  color: var(--accent-dark);
}
label.day:has(input:focus-visible) { box-shadow: 0 0 0 3px var(--accent-soft); }
label.day:has(input:disabled) { opacity: 0.55; cursor: not-allowed; }
label.day input { margin: 0; }

/* A field the screen has taken out of play still has to read as one. */
input:disabled { background: var(--panel); color: var(--sub); cursor: not-allowed; }

/* ==================================================================== */
/* Components                                                           */
/* ==================================================================== */

/* --- messages ------------------------------------------------------- */

.notice, .warn, .warning, .flash {
  padding: var(--sp-3) var(--sp-4);
  border-radius: var(--r);
  border: 1px solid var(--line);
  background: var(--panel);
  font-size: var(--fs-sm);
  margin: 0 0 var(--sp-5);
}
.warn, .warning { background: var(--warn-soft); border-color: var(--warn-line); color: var(--warn); }
.flash { background: var(--ok-soft); border-color: var(--ok-line); color: var(--ok); }
p.error[role="alert"] {
  padding: var(--sp-3) var(--sp-4);
  border-radius: var(--r);
  background: var(--danger-soft);
  border: 1px solid var(--danger-line);
  margin: 0 0 var(--sp-5);
}

/* --- cards ---------------------------------------------------------- */

.card {
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  padding: var(--sp-5);
  box-shadow: var(--shadow-sm);
}

/* Two cards in a row need a gap. A page that stacks them straight into `main`
   — the assistants screen, the suggestions — had none, and a list of them read
   as one long box with lines through it (#382).

   `main >` and not `.card + .card`: cards inside a grid are siblings too, and
   the gap there is the grid's. Unscoped, this rule would add a margin on top
   of that gap and knock the second card of every row out of line. */
main > .card + .card { margin-top: var(--sp-5); }

.auth-card {
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  padding: var(--sp-6);
  box-shadow: var(--shadow);
}
.auth-card h1 { font-size: var(--fs-lg); }
.auth-card + .auth-aside {
  margin-top: var(--sp-5);
  text-align: center;
  font-size: var(--fs-sm);
  color: var(--sub);
}

/* --- badges --------------------------------------------------------- */

.badge {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
  font-size: var(--fs-xs);
  font-weight: 600;
  padding: 0.15rem var(--sp-2);
  border-radius: var(--r-pill);
  border: 1px solid var(--line);
  background: var(--panel);
  color: var(--sub);
  white-space: nowrap;
}
.badge.live  { background: var(--ok-soft);     border-color: var(--ok-line);     color: var(--ok); }
.badge.draft { background: var(--warn-soft);   border-color: var(--warn-line);   color: var(--warn); }
.badge.todo  { background: var(--warn-soft);   border-color: var(--warn-line);   color: var(--warn); }
.badge.accent{ background: var(--accent-soft); border-color: var(--accent-line); color: var(--accent-dark); }

/* The dot before "Live". A ring rather than a tick — a tick claims the site
   is correct, which we do not know; a lit dot only claims it is up. */
.badge.live::before {
  content: "";
  width: 0.4rem; height: 0.4rem;
  border-radius: var(--r-pill);
  background: currentColor;
}

/* --- tables --------------------------------------------------------- */

.table-wrap {
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  overflow: hidden;
  background: var(--bg);
}
.table-wrap + .table-wrap { margin-top: var(--sp-4); }
/* Wide tables scroll inside their own frame; the page never does. */
.table-wrap.scroll { overflow-x: auto; }

table { width: 100%; border-collapse: collapse; font-size: var(--fs-sm); }
th {
  text-align: left;
  font-size: var(--fs-xs);
  font-weight: 650;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--sub);
  padding: var(--sp-3) var(--sp-4);
  background: var(--panel);
  border-bottom: 1px solid var(--line);
  white-space: nowrap;
}
td {
  padding: var(--sp-3) var(--sp-4);
  border-bottom: 1px solid var(--line-2);
  vertical-align: middle;
}
tbody tr:last-child td { border-bottom: 0; }
tbody tr:hover { background: var(--panel); }
td.num { text-align: right; font-variant-numeric: tabular-nums; }

/* A cell that cannot break makes the whole table wider than its frame, and
   the frame is what scrolls — so every column past the edge disappears with
   no sign that it is there. A publish error carries an object key sixty
   characters long with no space in it, which on a 1440px screen pushed the
   rollback button itself out of sight. Breaking mid-token is the lesser
   evil: an ugly wrap beats a hidden control. */
td.breakable, td .breakable, td code { overflow-wrap: anywhere; }

/* Six columns do not fit a phone however they wrap, and a column scrolled
   past the frame's edge is a column nobody knows is there. A screen this
   narrow drops the two that are only ever context; the one carrying the
   button never goes. */
@media (max-width: 47.99em) {
  th.on-wide, td.on-wide { display: none; }
}

/* --- the empty state ------------------------------------------------- */

.empty {
  text-align: center;
  padding: var(--sp-7) var(--sp-5);
  border: 1px dashed var(--line);
  border-radius: var(--r-lg);
  background: var(--panel);
  color: var(--sub);
}
.empty h2 { margin: 0 0 var(--sp-2); color: var(--ink); font-size: var(--fs-md); }
.empty p { margin: 0 auto var(--sp-5); max-width: 30rem; }
.empty p:last-child { margin-bottom: 0; }

/* --- provider buttons ------------------------------------------------ */

.choices { display: grid; gap: var(--sp-3); margin: var(--sp-5) 0 0; }
.provider {
  font: inherit;
  font-size: var(--fs-base);
  font-weight: 550;
  color: var(--ink);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-3);
  text-decoration: none;
  padding: var(--sp-3) var(--sp-5);
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  background: var(--bg);
  box-shadow: var(--shadow-sm);
  width: 100%;
  cursor: pointer;
  transition: border-color .12s ease, background-color .12s ease;
}
.provider:hover { border-color: var(--faint); background: var(--panel); text-decoration: none; }
.provider .mark { flex: none; display: block; }

/* --- a row of controls ----------------------------------------------- */

.row { display: flex; gap: var(--sp-2); align-items: center; }
.row.wrap { flex-wrap: wrap; }
.grow { flex: 1; min-width: 0; }
a.grow { text-decoration: none; color: inherit; }
.actions { display: flex; gap: var(--sp-3); align-items: center; flex-wrap: wrap; }
.spacer { flex: 1; }

/* A `<details>` used as a disclosure for a form — "+ page", "Add a section".
   The default marker triangle is the tell of an unstyled page. */
details.disclosure > summary {
  list-style: none;
  cursor: pointer;
  font-size: var(--fs-sm);
  font-weight: 550;
  color: var(--accent);
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-3);
  border-radius: var(--r-sm);
}
details.disclosure > summary::-webkit-details-marker { display: none; }
details.disclosure > summary::before {
  content: "+";
  font-size: var(--fs-md);
  line-height: 1;
  font-weight: 400;
}
details.disclosure[open] > summary::before { content: "\2212"; }
details.disclosure > summary:hover { background: var(--accent-soft); }
details.disclosure[open] > summary { margin-bottom: var(--sp-3); }
details.disclosure > form,
details.disclosure > .card { margin-bottom: var(--sp-3); }

/* An imported section's own text, gathered under one disclosure (#255). The
   fields somebody was actually offered lead; these are the rest of what is on
   the page, and there can be a great many of them, so the panel scrolls
   rather than pushing Save off the bottom of a phone. */
details.words > label { display: block; margin-bottom: var(--sp-3); }
details.words[open] { max-height: 60vh; overflow-y: auto; }
details.words > p { margin: 0 0 var(--sp-3); }

/* The addresses an imported section links to that none of its fields control
   (#609), and the repair for the ones that can have one (#645). One block per
   link, because the whole point is reading three values against each other —
   what it shows, what it opens, what the business details say — and three
   lines of prose run together would be one sentence nobody compares. */
details.links > p { margin: 0 0 var(--sp-3); }
.link-check { margin-bottom: var(--sp-3); padding-left: var(--sp-3); }
.link-check + .link-check {
  margin-top: var(--sp-3);
  padding-top: var(--sp-3);
  border-top: 1px solid var(--line);
}
.link-check > p { margin: 0 0 var(--sp-1); }
/* The label ahead of each value, so the three read as a column rather than as
   three sentences. Fixed rather than fluid: the values beside them are a
   number and an address, and a ragged left edge is what stops two numbers
   being compared at a glance. */
.link-check > p > span.muted { display: inline-block; min-width: 11rem; }

/* An imported site's own colours, in place of the theme picker (#256). One row
   per colour: the swatch is the control, the words beside it say what the
   colour is for — because on a real sheet several of them are "text" and only
   the swatch tells them apart. */
ul.brand-colours { list-style: none; margin: 0 0 var(--sp-3); padding: 0; }
ul.brand-colours > li + li { margin-top: var(--sp-2); }
form.brand-colour { display: flex; align-items: center; gap: var(--sp-3); }
form.brand-colour input[type="color"] {
  width: 2.5rem;
  height: 2.5rem;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: none;
  cursor: pointer;
}
.brand-role { font-weight: 550; }

/* The menu panel (#264, #266): a label and a target side by side, with the
   buttons that act on the whole entry beneath them. */
form.nav-edit { display: flex; flex-wrap: wrap; gap: var(--sp-2); align-items: center; }
form.nav-edit input { flex: 1 1 8rem; min-width: 0; }
.nav-buttons { display: flex; flex-wrap: wrap; gap: var(--sp-3); align-items: center; }
/* The list's own indent and bullets are the browser's, not ours (#353). */
ul.nav-entries { list-style: none; margin: var(--sp-3) 0; padding: 0; display: grid; gap: var(--sp-4); }
.nav-entry { display: flex; flex-direction: column; gap: var(--sp-2); }
/* A hidden entry is still listed, so it can be shown again, and must not look
   like one that is on the site (#353). */
.nav-entry.is-hidden input { color: var(--sub); background: var(--panel); text-decoration: line-through; }
.brand-swatch {
  width: 2.5rem;
  height: 2.5rem;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  display: inline-block;
  flex: none;
}
details.sheets pre.sheet {
  max-height: 18rem;
  overflow: auto;
  font-size: var(--fs-sm);
  background: var(--panel);
  padding: var(--sp-3);
  border-radius: var(--r-sm);
}

/* ==================================================================== */
/* The editor                                                           */
/* ==================================================================== */

/* --- the site bar ---------------------------------------------------- */

/* The way back, first in the bar and set apart from the name (#374). */
.site-bar .back { margin-right: var(--sp-2); }

.site-bar {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  flex-wrap: wrap;
  padding-bottom: var(--sp-4);
  margin-bottom: var(--sp-4);
  border-bottom: 1px solid var(--line);
}
.site-bar .site-name {
  font-size: var(--fs-lg);
  font-weight: 650;
  letter-spacing: -0.02em;
}
.site-bar .btn { flex: none; }
/* One row on a wide screen *when one row is possible* (#381, narrowed by #529).

   The original sentence was "one row on a wide screen", full stop, and it was
   enforced with `flex-wrap: nowrap` above 64em. That reading is gone, and this
   is the reversal #529 had to make: a published site with a long business name,
   a long address, twelve changes waiting and three unread came to two rows at
   1280, which puts the things to *do* on a second line under the things to
   read — but forbidding the wrap did not make the content fit, it only removed
   the last thing that could give. Buttons are `flex: none` by #439, and once
   they and the address floor exceeded the width **overflow was the only outcome
   available**: measured on the real editor page in Spanish, a control sat
   **245px past the right edge at 1024px** and was still 69px past at 1200px.
   English was 81px past at 1024px.

   Oscar's constraint is that no control ends up off the edge, so "one row" is
   now what the bar does whenever it can rather than what it does regardless.
   The ellipsis still gets its chance first — see `flex-basis` below, which is
   what makes truncation happen *instead of* a wrap rather than after being
   forbidden one. Measured after: one row everywhere it fits (English from
   1200, Spanish from 1280), two rows only in the band where one was
   impossible, and nothing off the edge at any width in either language.

   **#439 is untouched.** What gives way is still the text, never a button's
   label: a shrunk name still reads and "Site setti…" does not.

   Below 64em the bar wraps as it always did, and the name must be free to wrap
   with it: on a phone a long name across two readable lines beats one line
   ending in an ellipsis, and there is no row of buttons beside it to protect.

   What gives way is the text, never a button: a shrunk name still reads, and
   "Site setti…" does not.

   **The name gives way first, and that is a reversal (#439).** It used to be
   the address, on the reasoning that it "is the least of what the bar says,
   and the published site's own button repeats it". That button is gone, so
   the address is now the only way to open the published site from here — a
   control rather than a caption, and a control that shrinks to nothing is not
   one. Measured at 1024px before this changed: a 40-character name left the
   address 53px wide and clipped, and a 60-character name left it at zero.

   So the address keeps a floor it cannot shrink past, and the name yields
   instead. A name is still readable truncated; a link 24px wide is not
   clickable, and there is nothing behind it any more. */
@media (min-width: 64em) {
  .site-bar .site-name,
  .site-bar .host {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  /* `flex-basis: 0`, and that is the whole of the fix (#529).

     A flex line is broken on the items' *base* sizes, before any of them is
     asked to shrink. At `auto` the name's base is its full text, so a long one
     pushed the buttons onto a second row while there was still room to be had
     by truncating it — which is the regression #381 answered with `nowrap`,
     and `nowrap` is what made overflow the only outcome left once the buttons
     no longer fitted. From zero the name never forces a break and grows into
     whatever the controls did not take, so the ellipsis gets its chance
     *without* the row being forbidden to wrap.

     The same trick, for the same reason, as `shell.css`'s brand beside the
     menu button. */
  .site-bar .site-name { flex: 999 1 0; max-width: max-content; }
  /* Enough to stay a target and to show which site it opens. Basis zero for
     the reason above — the address is long and would otherwise break the line
     itself — with the floor doing the arguing instead. */
  .site-bar .host { flex: 999 1 0; max-width: max-content; min-width: 9rem; }
}
.site-bar .host {
  font-size: var(--fs-sm);
  color: var(--sub);
  font-family: var(--mono);
}
.site-bar a.host:hover { color: var(--accent); }

/* --- the page tabs --------------------------------------------------- */

nav.pages {
  display: flex;
  gap: var(--sp-1);
  flex-wrap: wrap;
  align-items: center;
  margin-bottom: var(--sp-5);
}
nav.pages a {
  color: var(--sub);
  text-decoration: none;
  font-size: var(--fs-sm);
  font-weight: 550;
  padding: var(--sp-2) var(--sp-3);
  border-radius: var(--r-sm);
}
nav.pages a:hover { color: var(--ink); background: var(--panel); }
nav.pages a.current { color: var(--ink); background: var(--panel-2); }
nav.pages details.disclosure { position: relative; }
/* The add-a-page form floats over the workbench instead of pushing the tabs
   apart, which used to shove the whole preview down the screen. */
nav.pages details.disclosure[open] > summary { margin-bottom: 0; }
nav.pages details.disclosure > .card {
  position: absolute;
  z-index: 15;
  top: calc(100% + var(--sp-2));
  left: 0;
  width: 20rem;
  box-shadow: var(--shadow-lg);
  margin: 0;
}
/* On a phone the form hangs from the tab row rather than from its own button,
   which sits far enough right that a 20rem card ran off the screen and made
   the whole editor scroll sideways (#353). */
@media (max-width: 40em) {
  nav.pages { position: relative; }
  nav.pages details.disclosure { position: static; }
  nav.pages details.disclosure > .card { width: auto; right: 0; }
}

/* --- the two panes --------------------------------------------------- */

/* --- edit, or preview, on a phone (#357) ----------------------------- */
/* The radios are the state and are never seen; moved out of sight rather than
   removed, so the labels stay reachable from a keyboard. */
.pane-pick {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  clip-path: inset(50%);
}
.pane-tabs { display: none; gap: var(--sp-1); margin-bottom: var(--sp-4); }
.pane-tab {
  font-size: var(--fs-sm);
  font-weight: 550;
  color: var(--sub);
  padding: var(--sp-2) var(--sp-4);
  border-radius: var(--r-sm);
  cursor: pointer;
}
#pane-edit:checked ~ .pane-tabs [for="pane-edit"],
#pane-preview:checked ~ .pane-tabs [for="pane-preview"] {
  color: var(--ink);
  background: var(--panel-2);
}
#pane-edit:focus-visible ~ .pane-tabs [for="pane-edit"],
#pane-preview:focus-visible ~ .pane-tabs [for="pane-preview"] {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
@media (min-width: 64em) {
  .pane-pick { display: none; }
}
@media (max-width: 63.99em) {
  .pane-tabs { display: flex; }
  #pane-edit:checked ~ .workbench .preview,
  #pane-preview:checked ~ .workbench .editor-pane { display: none; }
  /* The preview is the whole screen when it is the one being looked at. */
  #pane-preview:checked ~ .workbench .preview-frame iframe {
    height: calc(100vh - 14rem);
    height: calc(100dvh - 14rem);
  }
}

.workbench { display: grid; gap: var(--sp-5); align-items: start; }
@media (min-width: 64em) {
  /* The editing column takes what a form needs and no more; the preview
     takes the rest, because it is the half being judged (#374). */
  .workbench { grid-template-columns: minmax(22rem, 28rem) 1fr; }
  .preview { position: sticky; top: calc(var(--header-h) + var(--sp-4)); }
}

.editor-pane { display: grid; gap: var(--sp-3); }

/* --- a section row --------------------------------------------------- */

.section {
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--bg);
  box-shadow: var(--shadow-sm);
  transition: border-color .12s ease, box-shadow .12s ease;
}
.section:not(.open):hover { border-color: var(--faint); box-shadow: var(--shadow); }
.section.hidden { background: var(--panel); }
.section.hidden .section-label { color: var(--sub); }
.section.invalid { border-left: 3px solid var(--warn-line); }
.section.open { border-color: var(--accent-line); box-shadow: var(--shadow); }

.section > .row { padding: var(--sp-3) var(--sp-3) var(--sp-3) var(--sp-4); }
.section .section-label { font-weight: 600; font-size: var(--fs-sm); }
.section .section-summary {
  color: var(--sub);
  font-size: var(--fs-sm);
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.section .move { display: flex; gap: var(--sp-1); flex: none; }
/* The arrows are for the row the pointer is on; they stay reachable by
   keyboard, so this hides noise without hiding function. */
@media (hover: hover) {
  .section .move { opacity: 0; transition: opacity .12s ease; }
  .section:hover .move,
  .section .move:focus-within { opacity: 1; }
}

/* --- the open section's form ----------------------------------------- */

form.open { padding: var(--sp-5); gap: var(--sp-4); }
form.open > .row:first-child { padding: 0 0 var(--sp-2); border-bottom: 1px solid var(--line-2); }
form.open .form-title { font-size: var(--fs-md); font-weight: 650; letter-spacing: -0.015em; }
form.open .save-bar {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  flex-wrap: wrap;
  padding-top: var(--sp-4);
  border-top: 1px solid var(--line-2);
}
form.open .save-bar .rest { display: flex; gap: var(--sp-4); align-items: center; margin-left: auto; }

/* --- the rich-text toolbar ------------------------------------------- */

/* Built by `editor.js` and inserted above each richtext area. */
.fmt { display: flex; gap: var(--sp-1); flex-wrap: wrap; }
.fmt button {
  font: inherit;
  font-size: var(--fs-xs);
  font-weight: 550;
  padding: var(--sp-1) var(--sp-3);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--bg);
  color: var(--sub);
  cursor: pointer;
}
.fmt button:hover { color: var(--ink); border-color: var(--faint); background: var(--panel); }
/* The label wraps both the toolbar and the field, so the two need the same
   gap the rest of a label has and no more. */
label:has(> .fmt) { gap: var(--sp-2); }

/* The save confirmation, which doubles as Undo's home for sixty seconds. */
.notice.saved {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  background: var(--ok-soft);
  border-color: var(--ok-line);
  color: var(--ok);
  margin: 0;
}
.notice.saved .muted { color: inherit; opacity: 0.75; }
.notice.saved .btn-link { color: inherit; }

/* --- the preview ----------------------------------------------------- */

.preview { display: grid; gap: var(--sp-3); }
.preview .preview-head { display: flex; align-items: center; gap: var(--sp-3); }
.preview .preview-head strong { font-size: var(--fs-sm); font-weight: 650; }
.preview-frame {
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--bg);
  box-shadow: var(--shadow);
  overflow: hidden;
}
.preview-frame iframe {
  display: block;
  width: 100%;
  height: 38rem;
  border: 0;
  background: var(--bg);
}
@media (min-width: 64em) {
  .preview-frame iframe { height: calc(100vh - var(--header-h) - 9rem); }
}

/* --- theme swatches --------------------------------------------------- */

form.themes { display: flex; gap: var(--sp-3); flex-wrap: wrap; }
.theme-swatch {
  font: inherit;
  display: grid;
  gap: var(--sp-2);
  justify-items: center;
  padding: var(--sp-3);
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--bg);
  cursor: pointer;
  font-size: var(--fs-sm);
  font-weight: 550;
  color: var(--sub);
  transition: border-color .12s ease, color .12s ease;
}
.theme-swatch:hover { border-color: var(--faint); color: var(--ink); }
.theme-swatch[aria-pressed="true"] {
  border-color: var(--accent);
  color: var(--ink);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
.theme-swatch .swatch-chips { display: flex; }
.theme-swatch .chip {
  width: 1.5rem; height: 1.5rem;
  border: 1px solid rgb(20 17 43 / 0.12);
}
.theme-swatch .chip:first-child { border-radius: var(--r-sm) 0 0 var(--r-sm); }
.theme-swatch .chip:last-child  { border-radius: 0 var(--r-sm) var(--r-sm) 0; }
.theme-swatch .chip + .chip { border-left: 0; }

/* --- the photo library ------------------------------------------------ */

/* One chip per photo, labelled with the words it was described with. */
.chips {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
  margin: 0 0 var(--sp-4);
  padding: 0;
}
.chips li {
  font-size: var(--fs-xs);
  color: var(--sub);
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  padding: 0.15rem var(--sp-3);
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* A titled block inside the editor's left column. */
.panel-block h2 { margin: 0 0 var(--sp-2); font-size: var(--fs-base); }
.panel-block > p { margin: 0 0 var(--sp-4); }

/* A suggestion that was not applied (#518). Kept on the screen so the owner
   can see what they are missing, and drawn so it cannot be mistaken for one
   that is still waiting: no checkbox, no buttons, and quieter than the live
   ones above it. The reason inside it keeps its own `.warn` colour — that is
   the sentence the owner came back to read. */
.refused { border-style: dashed; background: var(--line-2); }

/* The frame holding a suggestion's preview (#523). Tall enough to be a page
   rather than a strip, and bordered so it reads as a window onto something
   else rather than as part of the console around it. */
.suggestion-frame {
  display: block;
  width: 100%;
  height: 70vh;
  min-height: 24rem;
  border: 1px solid var(--line);
  border-radius: var(--radius, 6px);
  background: #fff;
}
/* The same frame on an approval card, where it is one of a list of them
   (#629). Shorter than the full-page one: on the card it answers "does this
   look right", and the tab beside it is there for reading the page. */
.suggestion-frame.on-card { height: 26rem; min-height: 0; }
details.peek > p { margin: var(--sp-2) 0 0; }

.refused .diff { opacity: 0.75; }

/* ==================================================================== */
/* The import flow                                                      */
/* ==================================================================== */

/* Four doors, each its own card, so a paste and a zip are visibly separate
   choices rather than one long form with headings in it. */
.doors { display: grid; gap: var(--sp-4); margin: 0 0 var(--sp-5); }
.door { display: grid; gap: var(--sp-3); }
.door h2 { margin: 0; font-size: var(--fs-base); }
.door p { margin: 0; font-size: var(--fs-sm); color: var(--sub); }
.door textarea { min-height: 9rem; font-family: var(--mono); font-size: var(--fs-sm); }

.findings { list-style: none; margin: 0 0 var(--sp-5); padding: 0; display: grid; gap: var(--sp-2); }
.findings li {
  display: flex;
  gap: var(--sp-3);
  align-items: flex-start;
  font-size: var(--fs-sm);
  padding: var(--sp-3) var(--sp-4);
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--bg);
}
.findings li::before {
  content: "";
  width: 0.4rem; height: 0.4rem;
  margin-top: 0.5rem;
  border-radius: var(--r-pill);
  background: var(--faint);
  flex: none;
}
.findings.good li::before { background: var(--ok); }
.findings.changed li::before { background: var(--warn); }
.findings.lost li::before { background: var(--danger); }

/* The decision at the end of the review: two buttons, weighted. */
.decide {
  display: flex;
  gap: var(--sp-3);
  align-items: center;
  flex-wrap: wrap;
  padding: var(--sp-5);
  border: 1px solid var(--accent-line);
  border-radius: var(--r-lg);
  background: var(--accent-soft);
  margin: var(--sp-6) 0 var(--sp-4);
}
.decide form { display: contents; }

/* ==================================================================== */
/* Admin                                                                */
/* ==================================================================== */

.request-list { list-style: none; margin: 0 0 var(--sp-5); padding: 0; display: grid; gap: var(--sp-3); }

/* The referral tree. The rule is the line of descent, so it sits on the
   nested list rather than on each row. */
ul.tree { list-style: none; margin: 0; padding: 0; }
ul.tree ul {
  list-style: none;
  margin: var(--sp-2) 0 0;
  padding-left: var(--sp-4);
  border-left: 1px solid var(--line);
}
ul.tree li { margin: 0 0 var(--sp-2); font-size: var(--fs-sm); }
ul.tree li:last-child { margin-bottom: 0; }

/* The publish history's own bits. */
.publish-note {
  font-size: var(--fs-sm);
  color: var(--sub);
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--r);
  padding: var(--sp-4);
  margin-top: var(--sp-5);
}

/* ==================================================================== */
/* Utilities                                                            */
/* ==================================================================== */

.stack-sm { display: grid; gap: var(--sp-2); }
.mt-0 { margin-top: 0; }
.mb-0 { margin-bottom: 0; }

/* The honeypot field: off the canvas rather than display:none, which some
   bots know to skip. Never seen, never tabbed to, never announced. */
.hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

/* ==================================================================== */
/* The site list                                                        */
/* ==================================================================== */

.site-list {
  list-style: none;
  margin: 0 0 var(--sp-5);
  padding: 0;
  display: grid;
  gap: var(--sp-3);
  grid-template-columns: repeat(auto-fill, minmax(18rem, 1fr));
}
.site-card {
  display: grid;
  gap: var(--sp-2);
  padding: var(--sp-5);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--bg);
  box-shadow: var(--shadow-sm);
  color: inherit;
  text-decoration: none;
  height: 100%;
  transition: border-color .12s ease, box-shadow .12s ease;
}
.site-card:hover { border-color: var(--accent-line); box-shadow: var(--shadow); text-decoration: none; }
.site-card strong { font-size: var(--fs-md); font-weight: 650; letter-spacing: -0.015em; }
.site-card .host {
  font-family: var(--mono);
  font-size: var(--fs-sm);
  color: var(--sub);
  overflow-wrap: anywhere;
}

/* --- the domain wizard (#181) ---------------------------------------- */

/* A DNS record is a form somebody is copying into another window, so every
   value is monospaced, selectable as one piece, and allowed to break rather
   than push the card sideways on a phone. Nothing here is decoration: the
   customer is comparing two screens, and every character matters. */

.breakable { overflow-wrap: anywhere; }

.domain-free, .domain { margin: 0 0 var(--sp-4); }
/* The second name a bare domain answers on (#379). Beside the name it belongs
   to and quieter than it: one address was added, and this says what else it
   covers rather than reading as a second thing to look after. */
.domain .also { font-weight: 400; color: var(--sub); }
.domain-free {
  display: grid;
  gap: var(--sp-2);
  justify-items: start;
  background: var(--panel);
}
.domain-free .host, .domain-state .host {
  font-family: var(--mono);
  font-size: var(--fs-md);
  overflow-wrap: anywhere;
}

/* The waiting panel. One row per domain, and the whole block is what the
   poll swaps - so it carries its own spacing rather than relying on a
   neighbour's margin that a swap could leave behind. */
#domain-states { display: grid; gap: var(--sp-2); margin: 0 0 var(--sp-4); }
.domain-state {
  display: grid;
  gap: var(--sp-1);
  grid-template-columns: auto 1fr;
  align-items: center;
  padding: var(--sp-3) var(--sp-4);
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--bg);
}
.domain-state p { grid-column: 1 / -1; }
.domain-state.ok    { border-color: var(--ok-line);   background: var(--ok-soft); }
.domain-state.stuck { border-color: var(--warn-line); background: var(--warn-soft); }

/* One record at a time (DESIGN §9). The number down the left is the point of
   the ordered list - "the second one" is how somebody refers to these on the
   phone - so the marker stays visible rather than being replaced by a tick. */
ol.records {
  list-style: decimal;
  margin: var(--sp-4) 0 0;
  padding-left: var(--sp-5);
  display: grid;
  gap: var(--sp-4);
}
ol.records > li { padding-left: var(--sp-1); }

.record {
  border: 1px solid var(--line);
  border-radius: var(--r);
  padding: var(--sp-4);
  background: var(--panel);
}

/* The checkbox is the customer's own place-keeping and sits above the record
   rather than inside it, so it never reads as part of what they must type. */
.record-done {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  font-size: var(--fs-sm);
  color: var(--sub);
  margin: 0 0 var(--sp-2);
  cursor: pointer;
}
.record-done input { margin: 0; }

.record-fields {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--sp-2) var(--sp-4);
  margin: 0;
  align-items: baseline;
}
.record-fields dt {
  font-size: var(--fs-xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--sub);
}
.record-fields dd {
  margin: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--sp-2);
  min-width: 0;
}
.record-fields .code { font-size: var(--fs-base); letter-spacing: 0.04em; }

/* A phone is where somebody actually does this, with the registrar's panel in
   the other tab. Two columns of labels there costs more width than the values
   can spare. */
@media (max-width: 30rem) {
  .record-fields { grid-template-columns: 1fr; gap: var(--sp-1); }
  .record-fields dt { margin-top: var(--sp-2); }
}

/* ==================================================================== */
/* The enquiry inbox (#45)                                              */
/* ==================================================================== */

/* Somebody else's words, in the owner's console. Nothing here is markup —
   every value is autoescaped by the template — so the only job of this block
   is to make a message readable and a spam card obviously set aside. */
.enquiry { display: flex; flex-direction: column; gap: var(--sp-2); }

/* Faded rather than hidden: the whole argument for keeping a honeypot hit is
   that a real enquiry wrongly flagged is still findable. */
.enquiry.spam { border-style: dashed; }
.enquiry.spam .message, .enquiry.spam .fields { color: var(--sub); }

.enquiry .fields {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: var(--sp-1) var(--sp-3);
  margin: 0;
  align-items: baseline;
}
.enquiry .fields dt {
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--sub);
  min-width: 0;
  overflow-wrap: anywhere;
}
.enquiry .fields dd { margin: 0; min-width: 0; overflow-wrap: anywhere; }

/* `pre-wrap`, so the paragraphs somebody typed survive, and `anywhere`, so a
   pasted URL with no spaces in it cannot widen the page. */
.enquiry .message {
  margin: 0;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  padding: var(--sp-3);
  border-radius: var(--r-sm);
  background: var(--panel-2);
}

/* The name column would otherwise take half the width on a phone. */
@media (max-width: 30rem) {
  .enquiry .fields { grid-template-columns: 1fr; gap: 0; }
  .enquiry .fields dt { margin-top: var(--sp-2); }
}

/* The staff bug button (#364): ours, and only on our own screens. Fixed to the
   bottom corner so it is reachable from any screen without taking a place in a
   layout that was designed without it. */
.staff-bug {
  position: fixed;
  right: var(--sp-3);
  bottom: var(--sp-3);
  z-index: 40;
}
.staff-bug button { box-shadow: var(--shadow); }

/* On a phone the label would sit on top of whatever is in the corner. */
@media (max-width: 30rem) {
  .staff-bug button span:last-child { display: none; }
}
