:root {
  color-scheme: light dark;

  /* --- Ground and surfaces --------------------------------------------- */
  --page: #eef2f4;
  --card: #ffffff;
  --raised: #f6fafb;
  --line: #dde6ea;
  --line-soft: #eaf0f2;

  /* --- Ink -------------------------------------------------------------- */
  --ink: #0f1c21;
  --muted: #47606a;

  /* --- The action colour ------------------------------------------------- */
  --accent: #0a94a8;
  --accent-ink: #ffffff;
  --accent-press: #087d8e;
  --accent-text: #0a7a8b;
  --accent-soft: #e3f1f4;
  --focus: #0a94a8;

  /* --- The states a website can be in ------------------------------------ */
  --live: #0a6b52;
  --live-soft: #e2f2ec;
  --stopped: #8a5a12;
  --stopped-soft: #fbf1de;

  --shadow: 0 1px 2px rgba(15, 28, 33, .05), 0 8px 24px -16px rgba(15, 28, 33, .35);
  --shadow-bar: 0 1px 0 rgba(15, 28, 33, .06);
}

/**
 * Dark, and WHO GETS TO DECIDE.
 *
 * Three states, not two. An explicit choice stamps 'data-theme' on <html>; the
 * default -- "follow the system" -- stamps nothing, and then only
 * 'prefers-color-scheme' separates light from dark. So:
 *
 *   :root                                     the complete LIGHT palette
 *   @media dark + :root:not([data-theme=light])   dark, unless light was chosen
 *   :root[data-theme='dark']                  dark, chosen, beating a light OS
 *
 * The media query is GUARDED. Without ':not([data-theme='light'])' a customer
 * on a dark OS who picks light gets the media query's tokens anyway and the
 * switch appears to do nothing -- which is the bug this shape exists to make
 * impossible, and it is the one CLAUDE.md names.
 *
 * EVERY TOKEN IS REDECLARED IN BOTH DARK BLOCKS AND NONE IS INVENTED THERE. A
 * colour whose only declaration sits inside a media query resolves to nothing
 * in the other scheme, silently. The accent fill stays the same teal in both,
 * exactly as the transactional email does -- only the colours it sits ON move.
 */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) {
    --page: #0f1c21;
    --card: #16262c;
    --raised: #122126;
    --line: #2a3f47;
    --line-soft: #223138;

    --ink: #e3eaed;
    --muted: #9db1b9;

    --accent: #0a94a8;
    --accent-ink: #ffffff;
    --accent-press: #0aa6bd;
    --accent-text: #5ec8d8;
    --accent-soft: #10333b;
    --focus: #5ec8d8;

    --live: #6fd7b4;
    --live-soft: #10312a;
    --stopped: #f0c07a;
    --stopped-soft: #2f2513;

    --shadow: 0 1px 2px rgba(0, 0, 0, .4), 0 8px 24px -16px rgba(0, 0, 0, .8);
    --shadow-bar: 0 1px 0 rgba(0, 0, 0, .35);}
}

:root[data-theme='dark'] {
    --page: #0f1c21;
    --card: #16262c;
    --raised: #122126;
    --line: #2a3f47;
    --line-soft: #223138;

    --ink: #e3eaed;
    --muted: #9db1b9;

    --accent: #0a94a8;
    --accent-ink: #ffffff;
    --accent-press: #0aa6bd;
    --accent-text: #5ec8d8;
    --accent-soft: #10333b;
    --focus: #5ec8d8;

    --live: #6fd7b4;
    --live-soft: #10312a;
    --stopped: #f0c07a;
    --stopped-soft: #2f2513;

    --shadow: 0 1px 2px rgba(0, 0, 0, .4), 0 8px 24px -16px rgba(0, 0, 0, .8);
    --shadow-bar: 0 1px 0 rgba(0, 0, 0, .35);}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--page);
  color: var(--ink);
  font: 15px/1.6 system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  -webkit-text-size-adjust: 100%;
}

/**
 * THE LAYOUT IS WRITTEN IN LOGICAL PROPERTIES THROUGHOUT.
 *
 * 'padding-inline', 'margin-inline', 'inset-inline-start', 'text-align:start'
 * and 'border-inline-*' -- never 'left' or 'right'. Arabic is one of the
 * seventeen and the console is served 'dir="rtl"' for it, so a physical side
 * in here is a layout that mirrors everything except the one rule that names
 * a side. '__tests__/console-style.test.ts' fails on a physical inset,
 * margin, padding or border side, the same guard
 * 'packages/cloud-renderer' grew after ten thousand pixels of sideways scroll
 * on every right-to-left published page.
 */
#console {
  max-width: 68rem;
  margin-inline: auto;
  padding-inline: 1.25rem;
  padding-block: 0 4rem;
}

h1, h2, h3 { color: var(--ink); }
h1 { font-size: 1.6rem; line-height: 1.25; letter-spacing: -.015em; margin: 0 0 .35rem; }
h2 { font-size: 1.2rem; line-height: 1.3; letter-spacing: -.01em; margin: 0 0 .15rem; }
h3 {
  font-size: .75rem; font-weight: 600; text-transform: uppercase; letter-spacing: .08em;
  color: var(--muted); margin: 1.5rem 0 .6rem;
}
/* A measure, everywhere. A paragraph set across the full working width of
 * this console is around ninety characters a line, which is past the point
 * where the eye reliably finds the start of the next one. */
p { margin: 0 0 1rem; max-width: 68ch; }
p:last-child { margin-block-end: 0; }
.lede { color: var(--muted); }

/* --- The application bar ------------------------------------------------
 *
 * It carries the product's name and the one action that is always available.
 * There is NO LOGOTYPE here and no image: the estate has no Cloud mark, and a
 * console is the last place to invent one -- it would be the first thing a
 * customer took for the brand. The name is set in the product's own ink with
 * the word that distinguishes it in the action colour, which is typography
 * and not a mark somebody has to live with.
 */
header.bar {
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  padding-block: 1.1rem; margin-block-end: 1.75rem;
  border-block-end: 1px solid var(--line);
}
.brand { font-size: 1rem; font-weight: 700; letter-spacing: -.01em; }
.brand .mark { color: var(--accent-text); }

/* --- Cards ---------------------------------------------------------------- */

.card {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 12px;
  padding: 1.4rem 1.5rem;
  margin-block-end: 1rem;
  box-shadow: var(--shadow);
}
.card > h3:first-child { margin-block-start: 0; }
.notice {
  background: var(--stopped-soft);
  border-color: var(--stopped);
  box-shadow: none;
}
.notice .button { margin-block-start: .75rem; }

/* --- Controls -------------------------------------------------------------
 *
 * One filled action, one outlined action, one text action, and a focus ring
 * that is visible on all three grounds. The console had no focus style at all,
 * which is the accessibility defect that never appears in a screenshot.
 */
.button {
  display: inline-block; background: var(--accent); color: var(--accent-ink);
  border: 1px solid var(--accent); border-radius: 8px;
  padding: .6rem 1.15rem;
  font: inherit; font-weight: 700; line-height: 1.25;
  text-decoration: none; text-align: center; cursor: pointer;
  transition: background-color 120ms ease-out;
}
.button:hover { background: var(--accent-press); border-color: var(--accent-press); }
.quiet {
  background: var(--card); border: 1px solid var(--line); border-radius: 8px;
  color: var(--ink); font: inherit; font-weight: 600; font-size: .85rem;
  padding: .4rem .8rem; line-height: 1.25; cursor: pointer;
  transition: border-color 120ms ease-out, color 120ms ease-out;
}
.quiet:hover { border-color: var(--accent); color: var(--accent-text); }
.quiet[disabled] { color: var(--muted); border-color: var(--line-soft); cursor: default; }
.quiet[disabled]:hover { border-color: var(--line-soft); color: var(--muted); }
.link {
  background: none; border: 0; padding: 0;
  color: var(--accent-text); font: inherit; font-weight: 600;
  text-decoration: underline; text-underline-offset: 2px; cursor: pointer;
}
:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
  border-radius: 4px;
}
@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; }
}

/* --- The signed-out page --------------------------------------------------
 *
 * The front door of something somebody pays for, rather than a card with one
 * sentence in it. It is a column of its own width -- the workspace's 68rem is
 * a working surface, and a headline measured across it is unreadable -- with
 * the three things the product actually does underneath.
 */
.doorway { padding-block-start: clamp(2.5rem, 1rem + 8vh, 6rem); }
.doorway .brand { display: block; margin-block-end: 1.75rem; font-size: 1.05rem; }
.doorway h1 {
  font-size: clamp(1.9rem, 1.35rem + 2.1vw, 2.7rem);
  line-height: 1.12; letter-spacing: -.025em;
  max-width: 20ch; margin-block-end: .9rem;
}
.doorway .lede { font-size: 1.05rem; max-width: 46ch; margin-block-end: 1.5rem; }
.doorway .button { padding-inline: 1.6rem; padding-block: .75rem; font-size: 1rem; }
.doorway .refused {
  max-width: 46ch;
  border-inline-start: 3px solid var(--line);
  padding-inline-start: .9rem;
}
.points {
  list-style: none; margin: 3.5rem 0 0; padding: 0;
  display: grid; gap: 1rem;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  border-block-start: 1px solid var(--line);
  padding-block-start: 2rem;
}
.points h2 { font-size: .95rem; font-weight: 700; margin-block-end: .3rem; }
.points p { color: var(--muted); font-size: .9rem; }

/* --- The sign-in card -----------------------------------------------------
 *
 * OUR OWN, REPLACING AMAZON'S HOSTED UI. What "Sign in" led to until now was
 * Cognito's stock page: grey, an empty strip where a logotype would go, an
 * Amazon-blue button, no Housing Market anywhere on it. Mike looked at the
 * live product and asked for ours.
 *
 * IT SITS BESIDE THE ARGUMENT RATHER THAN UNDER IT, once there is width for
 * both. A signed-out console has two jobs at once -- say what the product is,
 * and let somebody in -- and stacked they compete: the form lands below the
 * fold on a laptop and the reader has to scroll past the pitch to reach the
 * thing they came for. The same call '.workbench' makes about the editor and
 * its preview, at the same breakpoint reasoning: one column until there is
 * genuinely room, and the FORM goes second in the source so a phone reader
 * meets the headline first and a screen reader reads them in that order.
 */
.doorway-split { display: grid; gap: 2rem; align-items: start; }
@media (min-width: 62rem) {
  .doorway-split { grid-template-columns: minmax(0, 1fr) minmax(0, 22rem); gap: 3.5rem; }
}
/**
 * THE THREE POINTS ARE OUTSIDE THE SPLIT, AND THAT IS THE WHOLE POINT OF IT.
 *
 * FOUND BY LOOKING, AT 390 PIXELS. With the points inside the story column,
 * one column stacked them as headline, lede, three cards, THEN the form --
 * which put the sign-in form roughly twelve hundred pixels down a phone
 * screen. Somebody opening this on their phone to sign in had to scroll past
 * the entire pitch to reach it. That is exactly the failure the two-column
 * layout above was written to avoid, avoided on a laptop and created on a
 * phone, and every test was green through it: they assert that the strings are
 * on the page, and they were.
 *
 * Outside, the order is right in both: headline, then the way in, then what
 * the product does -- and on a wide screen the points get their full width and
 * their three columns back.
 */
.gate {
  background: var(--card); border: 1px solid var(--line); border-radius: 12px;
  padding: 1.4rem 1.5rem 1.5rem; box-shadow: var(--shadow);
}
.gate h2 { font-size: 1.05rem; margin-block-end: .25rem; }
.gate .lede { font-size: .9rem; margin-block-end: 1.1rem; }
/* The one action, at the card's full width, so it is unmistakably the way in. */
.gate .button { display: block; width: 100%; margin-block-start: 1.1rem; }
/*
 * EACH ON ITS OWN LINE, FLUSH WITH THE CARD, AND 'width: max-content' SO THE
 * CLICKABLE AREA IS THE WORDS RATHER THAN THE ROW.
 *
 * A '<button>' with its chrome removed still lays out INLINE, which was
 * invisible while this card had one link and wrong the moment it had two --
 * see below.
 */
.gate .link { display: block; width: max-content; max-width: 100%; margin-block-start: 1rem; }
/*
 * TWO LINKS SIDE BY SIDE, AND WHY THIS RULE HAD TO EXIST.
 *
 * FOUND BY LOOKING, and no test could have. '.link' is a button with its
 * chrome removed, so it lays out INLINE -- and until this card had a second
 * one there was never a pair to separate. Appended one after another with no
 * text node between them, "Forgot your password?" and "Create an account"
 * rendered as
 *
 *     Forgot your password?Create an account
 *
 * underlined end to end, reading as one broken sentence. Every assertion was
 * green: both strings were on the page and each button was findable by its own
 * label, which is exactly what a test can see and a person cannot unsee.
 *
 * TWO DECLARATIONS ON '.gate .link' ABOVE, NOT A WRAPPER ELEMENT. A flex row
 * around the pair would have been tidier and would have put a node between the
 * test helpers and the buttons they find by their text; making the link itself
 * a block leaves the DOM exactly as it was. 'width: max-content' is the half
 * that matters after that: without it the link fills the card and a click
 * anywhere on that line follows it.
 */
/* The password rule, under the field it applies to rather than above it. */
.rule { color: var(--muted); font-size: .8rem; margin: -.6rem 0 .9rem; }

/* --- What the card says back ----------------------------------------------
 *
 * TWO SHAPES, NOT TWO COLOURS. A refusal and a confirmation are different
 * facts and they carry the estate's stopped and live grounds -- but each also
 * carries its own border, because a reader who cannot tell the amber from the
 * teal is exactly the reader who most needs to know which of the two this is.
 * That is the rule '.status' already follows for a website's state.
 */
.alert, .said {
  margin: 0 0 1rem; padding: .6rem .8rem; border-radius: 8px;
  font-size: .9rem; line-height: 1.45;
}
.alert { background: var(--stopped-soft); border: 1px solid var(--stopped); color: var(--stopped); }
.said { background: var(--live-soft); border: 1px solid var(--live); color: var(--live); }

/* --- The workspace --------------------------------------------------------
 *
 * One panel per organisation. The websites come first inside it, because
 * building one is what this product is; the plan, the offices and the team are
 * the facts that qualify them.
 */
.panel {
  background: var(--card); border: 1px solid var(--line); border-radius: 12px;
  margin-block-end: 1.5rem; box-shadow: var(--shadow); overflow: hidden;
}
.panel-head {
  padding: 1.4rem 1.5rem 1.2rem;
  border-block-end: 1px solid var(--line);
  background: var(--raised);
}
.panel-body { padding: 1.4rem 1.5rem 1.5rem; }
.panel-body > h3:first-child { margin-block-start: 0; }
/* The organisation's name and the plan it is on, which qualifies every
 * allowance and every refusal below it, on one line where there is room. */
.panel-title { display: flex; flex-wrap: wrap; align-items: baseline; gap: .3rem .75rem; }
.panel-title h2 { margin: 0; }
.panel-head .facts { margin-block-start: .7rem; }
.tag {
  display: inline-block; font-size: .78rem; font-weight: 600; line-height: 1.5;
  padding: .1rem .55rem; border-radius: 999px;
  background: var(--card); border: 1px solid var(--line); color: var(--muted);
}
.tag-plan { background: var(--accent-soft); border-color: transparent; color: var(--accent-text); }

/* The two supporting lists, side by side where there is room for them. */
.aside-pair {
  display: grid; gap: 0 2rem;
  grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
}

/* --- Facts and allowances -------------------------------------------------- */

dl.facts { display: grid; grid-template-columns: auto 1fr; gap: .3rem 1.25rem; margin: 0; }
dl.facts dt { color: var(--muted); }
dl.facts dd { margin: 0; }

table { width: 100%; border-collapse: collapse; }
th, td { text-align: start; padding: .5rem .75rem; border-block-end: 1px solid var(--line-soft); }
th:first-child, td:first-child { padding-inline-start: 0; }
th:last-child, td:last-child { padding-inline-end: 0; }
th {
  color: var(--muted); font-weight: 600; font-size: .72rem;
  text-transform: uppercase; letter-spacing: .06em;
}
td { font-variant-numeric: tabular-nums; }
tr:last-child th, tr:last-child td { border-block-end: 0; }
.plan-name { font-weight: 700; font-size: 1.05rem; margin: 0 0 .75rem; }

/* --- Lists ---------------------------------------------------------------- */

ul.plain { list-style: none; margin: 0; padding: 0; }
ul.plain li { padding-block: .55rem; border-block-end: 1px solid var(--line-soft); }
ul.plain li:last-child { border-block-end: 0; }

.refused, .empty { color: var(--muted); margin: 0; }

/* --- The enquiry inbox -----------------------------------------------------
 *
 * EVERY INSET IS LOGICAL. padding-inline-start, border-inline-start,
 * margin-inline-* -- never left or right. The whole console is built this
 * way because a physical inset on a right-to-left page pushes the layout
 * sideways, and this repository has a guard against exactly that after one did
 * it by ten thousand pixels.
 *
 * THE VISITOR'S MESSAGE KEEPS ITS LINE BREAKS. white-space: pre-wrap is what
 * makes a three-paragraph enquiry read as three paragraphs rather than as one
 * run-on sentence -- the message is the only thing on this screen the customer
 * is actually trying to read.
 */
ul.leads { list-style: none; margin: 0; padding: 0; display: grid; gap: 1rem; }
.lead {
  border: 1px solid var(--line); border-radius: .5rem; padding: .9rem 1.1rem;
  background: var(--card);
}
.lead-head { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem; }
.tag-warn { background: var(--stopped-soft); border-color: transparent; color: var(--stopped); }
.lead-facts {
  display: flex; flex-wrap: wrap; gap: .25rem 1rem;
  margin: .35rem 0 .6rem; color: var(--muted); font-size: .85rem;
}
.lead-message { margin: 0 0 .75rem; white-space: pre-wrap; overflow-wrap: anywhere; }
.lead-actions { display: flex; flex-wrap: wrap; gap: .5rem; align-items: center; }
.lead h4 { margin: .9rem 0 .3rem; font-size: .8rem; color: var(--muted); }
.lead-note { display: flex; gap: .5rem; margin-block-start: .75rem; flex-wrap: wrap; }
.lead-note input { flex: 1 1 12rem; min-width: 0; }

/* --- Status ----------------------------------------------------------------
 *
 * A pill and not a word in the corner, because a customer scanning a list of
 * websites is looking for the one that is live. Every state has a shape as
 * well as a colour: colour alone is unreadable to somebody who cannot see the
 * difference between the teal and the amber.
 */
.status {
  display: inline-block; white-space: nowrap;
  font-size: .75rem; font-weight: 700; line-height: 1.5;
  padding: .1rem .6rem; border-radius: 999px;
  background: var(--raised); border: 1px solid var(--line); color: var(--muted);
}
.status-published { background: var(--live-soft); border-color: transparent; color: var(--live); }
.status-suspended { background: var(--stopped-soft); border-color: transparent; color: var(--stopped); }

/**
 * The build, which the console shows ONLY when it names one.
 *
 * The image's Dockerfile defaults 'APP_REVISION' to the word "unassigned", and
 * a task started without it put that word on the page under the sign-in card,
 * in front of every visitor, as the last thing on the product's front door.
 * The value is not secret and an operator does need it, so it stays where an
 * operator already looks -- '/healthz' publishes it unauthenticated on every
 * task -- and stops being page furniture for everybody else.
 */
.revision {
  color: var(--muted); font-size: .72rem; letter-spacing: .04em;
  margin-block-start: 2.5rem; text-align: end;
}
.row { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; }
.row + .row { margin-block-start: .75rem; }
.spread { justify-content: space-between; }
.grow { flex: 1 1 auto; }

/* --- The site being edited, and what can be done to it --------------------
 *
 * THE TWO-COLUMN WORKBENCH IS THE POINT OF THE EDITOR'S REDESIGN. The form
 * and the preview used to be a single column, which put the rendered page
 * roughly two screens below the field being typed into: every check of "does
 * this look right" was a scroll down, a look, and a scroll back. Side by side,
 * the preview is simply in view.
 *
 * It collapses to one column below 64rem, and the preview goes LAST there
 * rather than first, because on a phone the thing you are editing has to be
 * within reach of your thumb and the thing you are checking does not.
 */
.workbench { display: grid; gap: 1rem; align-items: start; }
@media (min-width: 64rem) {
  .workbench { grid-template-columns: minmax(0, 1fr) minmax(0, 22rem); }
  .workbench-side { position: sticky; top: 1rem; }
}
.site-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: .5rem 1rem; }
.site-head h1 { margin: 0; }
.site-head .address {
  color: var(--muted); font-size: .9rem;
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  overflow-wrap: break-word; min-width: 0;
}

/* The toolbar sticks to the top of the working column, so Save is reachable
 * from anywhere in a long page rather than only from the top of one. */
.toolbar {
  position: sticky; top: 0; z-index: 2;
  display: flex; align-items: center; gap: .6rem; flex-wrap: wrap;
  padding: .75rem 1rem; margin-block-end: 1rem;
  background: var(--card); border: 1px solid var(--line); border-radius: 12px;
  box-shadow: var(--shadow-bar), var(--shadow);
}

/* --- Fields ---------------------------------------------------------------- */

.field { display: block; margin: 0 0 .9rem; }
.field:last-child { margin-block-end: 0; }
.field > span {
  display: block; color: var(--muted);
  font-size: .75rem; font-weight: 600; letter-spacing: .02em;
  margin-block-end: .25rem;
}
.field input, .field textarea, .field select {
  width: 100%; font: inherit; color: var(--ink); background: var(--raised);
  border: 1px solid var(--line); border-radius: 8px; padding: .5rem .65rem;
  transition: border-color 120ms ease-out, background-color 120ms ease-out;
}
.field input:hover, .field textarea:hover, .field select:hover { border-color: var(--muted); }
.field input:focus, .field textarea:focus, .field select:focus {
  background: var(--card); border-color: var(--accent);
}
.field textarea { min-height: 5rem; resize: vertical; line-height: 1.55; }
.field input:disabled {
  color: var(--muted); background: var(--line-soft); cursor: not-allowed;
}

/* --- The page tabs ---------------------------------------------------------
 *
 * They pick which page of the website is on screen, so they read as a set of
 * choices with one taken, and the taken one carries the fill rather than only
 * a heavier weight.
 */
.tabs {
  display: flex; flex-wrap: wrap; gap: .4rem;
  margin-block-end: 1rem; padding-block-end: 1rem;
  border-block-end: 1px solid var(--line-soft);
}
.tab {
  background: var(--card); border: 1px solid var(--line); border-radius: 999px;
  color: var(--ink); font: inherit; font-size: .85rem; font-weight: 600;
  padding: .3rem .85rem; cursor: pointer;
  transition: border-color 120ms ease-out, background-color 120ms ease-out;
}
.tab:hover { border-color: var(--accent); }
.tab[aria-current='true'] {
  background: var(--accent); color: var(--accent-ink); border-color: var(--accent);
}

/* --- A section of the customer's page --------------------------------------
 *
 * The block being edited, and the template boxes on the first screen. It reads
 * as a card inside a card: an inset ground, so a page with six sections on it
 * is six things rather than one long form.
 */
.section {
  background: var(--raised); border: 1px solid var(--line); border-radius: 10px;
  padding: .9rem 1rem; margin-block-end: .75rem;
}
.section:last-child { margin-block-end: 0; }
.section > .row:first-child { margin-block-end: .75rem; }
.section .field input, .section .field textarea, .section .field select { background: var(--card); }
.kind { font-weight: 700; font-size: .9rem; }
.state { color: var(--muted); font-size: .85rem; }

/* The template boxes on an empty site. The name leads, the line under it is
 * quiet, and the button is the only thing to press. */
.section .button { margin-block-start: .6rem; }

/* --- The preview ------------------------------------------------------------
 *
 * A device-like frame rather than a bare rectangle: it is somebody else's
 * page, rendered by the renderer, and the border says so. 'background: white'
 * and not 'var(--card)' -- a published site is what it is in both schemes, and
 * an empty frame tinted to match the console would misrepresent it.
 */
.preview-frame {
  display: block; width: 100%; height: 26rem;
  border: 1px solid var(--line); border-radius: 10px;
  background: #ffffff;
}
@media (min-width: 64rem) {
  .preview-frame { height: 34rem; }
}

/* --- One website in the list ------------------------------------------------ */

.site-row { display: flex; align-items: center; justify-content: space-between; gap: .75rem; flex-wrap: wrap; }
.site-row .name { font-weight: 600; }
.site-row .address {
  color: var(--muted); font-size: .82rem;
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  /* A hostname is one long word with no spaces in it, so on a phone it either
   * overflows the card or breaks. break-all broke it mid-syllable and left an
   * orphaned '.com' on its own line; overflow-wrap prefers the hyphen the
   * label already has and only breaks arbitrarily when nothing else fits. */
  overflow-wrap: break-word;
}
ul.plain li { padding-block: .7rem; }

/* --- One listing in the list, and its photographs --------------------------
 *
 * THE LISTING ROW IS THE WEBSITE ROW. Same shape, same alignment, same places
 * for the name, the quiet second line and the action -- because they are the
 * same kind of row and a customer should not have to learn two.
 *
 * THE GALLERY IS A GRID AND NOT A LIST, and that is the one real difference.
 * A photograph is recognised by looking at it, so a column of file names would
 * be unusable for the one question this screen exists to answer: which picture
 * is first. It fills the width it is given, so four fit on a laptop and one
 * fits on a phone, without a media query naming either.
 */
.gallery {
  display: grid; gap: .75rem;
  /* FOURTEEN REMS, AND THE NUMBER WAS MEASURED RATHER THAN CHOSEN. At ten the
   * tracks fitted eight across a laptop and the three buttons under each
   * photograph wrapped one per line, so every card was twice as tall as its
   * picture and the first card -- the only one carrying the cover badge --
   * lined up with neither of the others. At fourteen the badge and the three
   * buttons sit on one line and a phone still gets exactly one column. */
  grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
  margin-block-end: 1rem;
}
.photo {
  position: relative;
  background: var(--raised); border: 1px solid var(--line); border-radius: 10px;
  padding: .5rem; min-width: 0;
}
/* A fixed shape for every photograph, so the grid does not jump as portraits
 * and landscapes load. 'object-fit: cover' crops rather than distorting: a
 * squashed house is worse than a cropped one. The ground under it is a token,
 * so the empty frame before an image arrives belongs to the theme. */
.photo img {
  display: block; width: 100%; aspect-ratio: 4 / 3; object-fit: cover;
  border-radius: 6px; background: var(--line-soft);
}
.photo .caption {
  display: flex; align-items: center; gap: .4rem;
  flex-wrap: wrap; margin-block-start: .5rem;
}
.photo .field { margin-block: .5rem 0; }
.photo .field input { font-size: .85rem; }
/* The three actions are small because they are per-photograph and there are up
 * to forty of them on a screen. They keep the same words as the section
 * controls in the site editor, which is how a customer learns they are the
 * same gesture. */
.photo .quiet { font-size: .78rem; padding: .2rem .5rem; }
.photo .row { gap: .3rem; }
/* The badge on the first photograph. It carries the accent because WHICH one
 * is first is the decision this screen exists to let somebody make.
 *
 * IT SITS ON THE PICTURE AND NOT IN THE ROW BELOW IT, which is a change made
 * after looking at the screen. In the flow it was one more thing on a line
 * that was already wrapping, so the FIRST card -- the only one carrying it --
 * was a line taller than every other card and its controls lined up with
 * none of them. On the photograph it says what it means, in the place it
 * means it, and every card in the grid is the same height.
 *
 * It is pinned with inset-inline-start and never with a physical side: this
 * console is served dir="rtl" in Arabic, and a badge on the physical left
 * would sit on the wrong corner of every photograph in six languages. */
.photo .tag-cover {
  position: absolute;
  inset-block-start: .9rem; inset-inline-start: .9rem;
  background: var(--accent-soft); border-color: transparent; color: var(--accent-text);
}

/* The file picker. It is an action, so it sits apart from the grid above it
 * rather than continuing it. */
.add-photos { margin-block-start: 1rem; }
.add-photos input { font: inherit; color: var(--ink); max-width: 100%; }
/* The browser's own button inside the file input, which is otherwise the one
 * control on this console that belongs to somebody else's design. It cannot be
 * replaced without a hidden input and a script, and it can be dressed to match
 * the quiet buttons beside it, which is enough. */
.add-photos input::file-selector-button {
  font: inherit; font-size: .85rem; font-weight: 600;
  color: var(--ink); background: var(--card);
  border: 1px solid var(--line); border-radius: 8px;
  padding: .25rem .7rem; margin-inline-end: .6rem;
  cursor: pointer;
}
.add-photos input::file-selector-button:hover { border-color: var(--accent); color: var(--accent-text); }

/* The form that makes another website. It is an action and not a row, so it is
 * separated from the list above it rather than continuing it. */
.new-site { margin-block-start: 1rem; }
.field-row {
  display: grid; gap: 0 .75rem;
  grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
}
/* A checkbox and its sentence on one line, box first in reading order. */
.field.check { display: flex; flex-direction: row; align-items: center; gap: .6rem; }
.field.check input { width: auto; }
/* One development unit: its controls wrap as a grid, separated from the next. */
.unit {
  grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
  border-block-start: 1px solid var(--line); padding-block-start: .6rem; margin-block-start: .6rem;
}
.visits { margin-block-start: .4rem; }

/* --- The sections of 2026-09-17 --------------------------------------------
 *
 * An item of a list section, and a button row: separated from the next like a
 * development unit is, and stacked, because a quotation needs the width.
 */
.item {
  border-block-start: 1px solid var(--line);
  padding-block-start: .6rem; margin-block-start: .6rem;
}
.item .row { margin-block-start: .4rem; }
.actions-editor, .items-editor { margin-block-end: .9rem; }
.actions-editor h4 { margin-block: .75rem .25rem; }
.actions-editor .field-row { align-items: end; }

/* The photograph picker: a grid of thumbnails, each a labelled radio button.
 * The chosen one carries the accent, so which photograph is on the page is
 * readable at a glance and not only from a small dot. */
.stock-picker { border: 0; padding: 0; margin: 0 0 .9rem; min-inline-size: 0; }
.stock-picker legend {
  color: var(--muted); font-size: .75rem; font-weight: 600;
  letter-spacing: .02em; padding: 0; margin-block-end: .25rem;
}
.stock-grid {
  display: grid; gap: .5rem; margin-block-end: .6rem;
  grid-template-columns: repeat(auto-fill, minmax(7rem, 1fr));
}
.stock-choice {
  display: flex; flex-direction: column; gap: .3rem; min-width: 0;
  background: var(--card); border: 1px solid var(--line); border-radius: 8px;
  padding: .35rem; font-size: .78rem; cursor: pointer;
}
.stock-choice:hover { border-color: var(--muted); }
.stock-choice:has(input:checked) { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
.stock-choice img {
  display: block; width: 100%; aspect-ratio: 3 / 2; object-fit: cover;
  border-radius: 6px; background: var(--line-soft);
}
.stock-choice .row { gap: .35rem; flex-wrap: nowrap; }
.stock-choice input { margin: 0; }
