/* ===========================================================================
   Library admin — "Signal".

   Deliberately self-contained: the admin is a separate host serving IT staff
   and must not inherit a book site's per-site theme, so it does not load
   variables.css or any of the marketing stylesheets.

   GATING — every rule in this file is scoped under `.admin`, which is set on
   <body> by layouts/admin.html.erb. Nothing here can reach a book site even
   if both stylesheets are ever loaded on the same document: the custom
   properties are declared on `.admin` rather than `:root`, and the
   box-sizing reset is scoped too. Add nothing to this file that is not
   prefixed with `.admin`.

   DESIGN — this is an instrument panel, not a dashboard. There is no
   decorative accent colour: every chromatic value below carries state (live,
   attention, failed, idle), because an accent would compete with the alarms
   on a screen whose only job is helping an operator find the one thing that
   is wrong. Interaction is expressed with ink, weight, and underline.

   Identifiers — hostnames, slug prefixes, cache keys — are the substance of
   this application, so the monospace face is promoted to a heading face
   rather than reserved for code. Page titles here ARE identifiers.
   =========================================================================== */

.admin {
  /* Chrome. Deliberately desaturated so it never competes with --admin-live. */
  --admin-rail:      #5e6b65;
  --admin-rail-2:    #6b7872;
  --admin-rail-text: #edf0ee;  /* 4.7:1 on --admin-rail */

  /* Ink is type, primary buttons, and code blocks — not the chrome. */
  --admin-ink:       #12211d;
  --admin-ink-2:     #2a423b;

  --admin-paper:     #edf1ef;
  --admin-surface:   #ffffff;
  --admin-rule:      #d7deda;
  --admin-rule-2:    #b7c2bd;
  --admin-text:      #13201c;
  --admin-muted:     #67766f;
  --admin-muted-2:   #8b9993;

  /* State — the entire colour vocabulary of this design. */
  --admin-live:      #0f6b4f;
  --admin-live-bg:   #e7f1ec;
  --admin-warn:      #b4520e;
  --admin-warn-bg:   #fbeee3;
  --admin-fail:      #9e2b25;
  --admin-fail-bg:   #faeae8;
  --admin-idle:      #8b9993;

  --admin-radius:    2px;
  --admin-font:      "IBM Plex Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
  --admin-mono:      "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

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

body.admin {
  margin: 0;
  font-family: var(--admin-font);
  font-size: 15px;
  line-height: 1.5;
  color: var(--admin-text);
  background: var(--admin-paper);
  -webkit-font-smoothing: antialiased;
}

/* Links carry no accent colour here, so they are distinguished by context —
   position, weight, and the mono face — and underline on hover. Underlining
   every link at rest made the whole admin read as an unstyled document.

   :where() keeps this reset at zero specificity beyond the `.admin` gate, so
   anything that sets its own colour on an anchor — a primary button, a rail
   link, a muted label — still wins. A plain `.admin a` selector outranks a
   single-class rule like .admin-button--primary and silently repaints its
   text: that is how the Edit link ended up dark-on-dark. */
.admin :where(a) { color: inherit; text-decoration: none; }
.admin :where(a:hover) {
  text-decoration: underline;
  text-underline-offset: 2px;
  text-decoration-thickness: 1px;
}
.admin :focus-visible { outline: 2px solid var(--admin-ink); outline-offset: 2px; }

/* The exception: a link sitting inside a sentence has nothing but an
   underline to mark it, so prose links keep one at rest. */
.admin-attention__what a,
.admin-attention__fix a,
.admin-section__note a,
.admin-fieldset__note a,
.admin-field__hint a,
.admin-dns-record__prose a,
.admin-flash a,
.admin-dl dd a,
.admin-table__empty a {
  text-decoration: underline;
  text-underline-offset: 2px;
  text-decoration-thickness: 1px;
  text-decoration-color: var(--admin-rule-2);
}
.admin-attention__what a:hover,
.admin-attention__fix a:hover,
.admin-section__note a:hover,
.admin-fieldset__note a:hover,
.admin-field__hint a:hover,
.admin-dns-record__prose a:hover,
.admin-flash a:hover,
.admin-dl dd a:hover,
.admin-table__empty a:hover { text-decoration-color: currentColor; }

/* --- Type utilities ------------------------------------------------------- */
.admin-label {
  font-size: 10.5px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--admin-muted);
}
/* A label that is also a link has no colour to mark it, so it takes an arrow.
   Without one, "All sites" in a section bar looked like a caption. */
.admin a.admin-label { color: var(--admin-text); }
.admin a.admin-label::after { content: " \2192"; color: var(--admin-muted-2); }
.admin-mono { font-family: var(--admin-mono); font-size: 0.9em; letter-spacing: -0.01em; }
.admin-blank { color: var(--admin-muted); }
/* A Turbo target that is not a box. Wrapping something in a div to give Turbo an
   id to aim at otherwise inserts a new box into whatever layout it landed in —
   here it would take the state marker off the site name's baseline. With this,
   the wrapper exists for the DOM and not for the layout. */
.admin-contents { display: contents; }

/* --- State markers -------------------------------------------------------
   Small caps text in the state colour, not a pill. A pill makes "this cache
   key is empty" look exactly as loud as "this site returns 404 to the
   public"; these do not.
   ------------------------------------------------------------------------- */
.admin-state {
  display: inline-flex;
  align-items: center;
  gap: 0.4em;
  font-size: 10.5px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  white-space: nowrap;
}
.admin-state::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
  flex: none;
}
.admin-state--live { color: var(--admin-live); }
.admin-state--warn { color: var(--admin-warn); }
.admin-state--fail { color: var(--admin-fail); }
.admin-state--idle { color: var(--admin-idle); }
.admin-state--idle::before { background: transparent; box-shadow: inset 0 0 0 1.5px var(--admin-idle); }
/* Work in progress. Ink rather than a verdict colour — nothing is wrong yet,
   and green would call it done. The pulsing dot is what says "in motion". */
.admin-state--busy { color: var(--admin-ink-2); }
.admin-state--busy::before { animation: admin-pulse 1.1s ease-in-out infinite; }
@keyframes admin-pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.25; }
}

/* The same marker with the caption taken away, for the sites index, where the
   state has no column and the site name should be the only word in the cell.
   The label lives in .admin-sr-only text and a title attribute instead. */
.admin-dot {
  display: inline-block;
  flex: none;
  width: 7px;
  height: 7px;
  margin-top: 6px;
  border-radius: 50%;
  background: currentColor;
}
.admin-dot--live { color: var(--admin-live); }
.admin-dot--warn { color: var(--admin-warn); }
.admin-dot--fail { color: var(--admin-fail); }
.admin-dot--idle { color: var(--admin-idle); background: transparent; box-shadow: inset 0 0 0 1.5px currentColor; }

/* --- Shell --------------------------------------------------------------- */
.admin-shell {
  display: grid;
  grid-template-columns: 236px minmax(0, 1fr);
  min-height: 100vh;
}

.admin-rail {
  background: var(--admin-rail);
  color: #fff;
  padding: 22px 0;
  display: flex;
  flex-direction: column;
  position: sticky;
  top: 0;
  height: 100vh;
}
.admin-rail__brand {
  display: block;
  padding: 0 22px 24px;
  font-family: var(--admin-mono);
  font-size: 15px;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: #fff;
  text-decoration: none;
}
/* Rail links get a background on hover, not an underline. */
.admin-rail a:hover { text-decoration: none; }
.admin-rail__nav { display: flex; flex-direction: column; }
.admin-rail__link {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 8px 22px;
  border-left: 2px solid transparent;
  font-size: 14px;
  color: var(--admin-rail-text);
  text-decoration: none;
}
.admin-rail__link:hover { background: rgba(255, 255, 255, 0.12); color: #fff; text-decoration: none; }
.admin-rail__link--active {
  background: rgba(255, 255, 255, 0.2);
  border-left-color: #fff;
  color: #fff;
  font-weight: 600;
}
/* The one place a state colour appears in the chrome: it says something is
   wrong somewhere else in the app. */
.admin-rail__count {
  font-family: var(--admin-mono);
  font-size: 11px;
  font-weight: 500;
  padding: 0 5px;
  border-radius: var(--admin-radius);
  background: var(--admin-warn);
  color: #fff;
}
.admin-rail__count--fail { background: var(--admin-fail); }
.admin-rail__foot {
  margin-top: auto;
  padding: 18px 22px 0;
  border-top: 1px solid rgba(255, 255, 255, 0.16);
}
/* Truncated rather than broken: a long address was splitting mid-word across
   two lines. The full value stays available as a tooltip. */
.admin-rail__user {
  margin-bottom: 8px;
  font-size: 12.5px;
  color: var(--admin-rail-text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.admin-main { padding: 30px 34px 90px; max-width: 1120px; }

/* --- Page header --------------------------------------------------------- */
.admin-head {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
  align-items: flex-end;
  justify-content: space-between;
  margin-bottom: 26px;
}
/* No rule of its own: every page's first section already opens with the heavy
   ink rule, and having both drew two lines 20px apart. Pages that put a bare
   table straight after the head borrow that rule instead. */
.admin-head + .admin-table-wrap { border-top: 2px solid var(--admin-ink); padding-top: 14px; }
.admin-head__text { min-width: 0; }
.admin-head__eyebrow { margin: 0 0 6px; }
.admin-head h1 {
  margin: 0;
  font-family: var(--admin-mono);
  font-size: 25px;
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1.15;
  overflow-wrap: anywhere;
}
/* A name and its state marker, on one baseline. */
.admin-head__name { display: flex; flex-wrap: wrap; align-items: baseline; gap: 12px; }
.admin-head__sub {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  align-items: center;
  margin: 10px 0 0;
  font-size: 14px;
  color: var(--admin-muted);
  max-width: 68ch;
}
.admin-head__actions { display: flex; flex-wrap: wrap; gap: 8px; }

/* --- Sections: no cards. A heavy top rule and a label carry the structure. */
.admin-section { margin-bottom: 40px; }
.admin-section:last-child { margin-bottom: 0; }
.admin-section__bar {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  align-items: baseline;
  justify-content: space-between;
  border-top: 2px solid var(--admin-ink);
  padding-top: 10px;
}
.admin-section__title { margin: 0; font-size: 14px; font-weight: 600; }
.admin-section__title code { margin-left: 6px; }
.admin-section__note { margin: 8px 0 0; font-size: 13px; color: var(--admin-muted); max-width: 76ch; }
.admin-section__body { margin-top: 16px; }
/* A bar with a control in it. Baseline alignment is right for a title and a
   caption but wrong for a title and a button, whose box is taller than its
   text — so the tools variant centres, and the title keeps its own baseline
   group so the count still sits on the title's line. */
.admin-section__bar--tools { align-items: center; padding-top: 7px; }
.admin-section__heading { display: flex; flex-wrap: wrap; gap: 12px; align-items: baseline; }
.admin-section--danger .admin-section__bar { border-top-color: var(--admin-fail); }
.admin-section--danger .admin-section__title { color: var(--admin-fail); }

/* --- Attention register -------------------------------------------------- */
.admin-attention { list-style: none; margin: 0; padding: 0; }
.admin-attention__item {
  display: grid;
  grid-template-columns: 118px minmax(0, 1fr);
  gap: 18px;
  padding: 13px 16px;
  margin-bottom: 2px;
  background: var(--admin-surface);
  box-shadow: inset 3px 0 0 var(--admin-rule-2);
}
.admin-attention__item:last-child { margin-bottom: 0; }
.admin-attention__item--warn { background: var(--admin-warn-bg); box-shadow: inset 3px 0 0 var(--admin-warn); }
.admin-attention__item--fail { background: var(--admin-fail-bg); box-shadow: inset 3px 0 0 var(--admin-fail); }
.admin-attention__what { margin: 0; font-size: 14px; }
.admin-attention__what strong { font-family: var(--admin-mono); font-size: 13.5px; font-weight: 600; }
.admin-attention__fix { margin: 4px 0 0; font-size: 13px; color: var(--admin-muted); }
.admin-attention__fix a { color: var(--admin-text); }

/* --- Tables -------------------------------------------------------------- */
.admin-table-wrap { overflow-x: auto; }

.admin-table {
  width: 100%;
  border-collapse: separate;
  border-spacing: 0 2px;
  font-size: 14px;
}
.admin-table th {
  padding: 0 14px 8px;
  text-align: left;
  font-size: 10.5px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--admin-muted);
  white-space: nowrap;
}
.admin-table td {
  padding: 12px 14px;
  background: var(--admin-surface);
  vertical-align: top;
}
/* The status spine. Scan a column of rows and the broken ones come forward
   without reading a word. */
.admin-table tbody tr td:first-child { box-shadow: inset 3px 0 0 var(--admin-rule); }
.admin-table tbody tr.admin-row--live td:first-child { box-shadow: inset 3px 0 0 var(--admin-live); }
.admin-table tbody tr.admin-row--warn td:first-child { box-shadow: inset 3px 0 0 var(--admin-warn); }
.admin-table tbody tr.admin-row--fail td:first-child { box-shadow: inset 3px 0 0 var(--admin-fail); }
.admin-table__name {
  font-family: var(--admin-mono);
  font-size: 14px;
  font-weight: 600;
  letter-spacing: -0.01em;
}
/* Dot, then name with whatever belongs to the name stacked under it. */
.admin-table__title { display: flex; align-items: flex-start; gap: 9px; }
.admin-table__sub { display: block; margin-top: 3px; font-size: 12.5px; color: var(--admin-muted); }
a.admin-table__sub { text-decoration-color: var(--admin-muted-2); }
.admin-table td.admin-table__actions { text-align: right; white-space: nowrap; }
.admin-table__actions form { display: inline-block; margin: 0; }
.admin-table__stack { display: flex; flex-direction: column; gap: 5px; align-items: flex-start; }
.admin-table__empty {
  padding: 34px 14px;
  text-align: center;
  color: var(--admin-muted);
  background: var(--admin-surface);
}

/* --- SIGNATURE: hostname topology ---------------------------------------
   The canonical hostname is what this whole application exists to serve, and
   the redirect fan-in is its shape. Set it large in mono with the aliases
   stacked beneath, so the topology is legible before it is read.
   ------------------------------------------------------------------------- */
.admin-topology { font-family: var(--admin-mono); }
/* The canonical hostname and, in the corner beside it, the switch that decides
   whether it answers. Top-aligned rather than centred: the hostname is allowed
   to wrap and the switch stays in the corner when it does. */
.admin-topology__head { display: flex; gap: 18px; align-items: flex-start; }
/* min-width so a long hostname wraps instead of pushing the switch off the
   panel — a flex item will not shrink below its content otherwise. */
.admin-topology__head > :first-child { flex: 1; min-width: 0; }
.admin-topology__canonical {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 10px;
  font-size: 21px;
  font-weight: 600;
  letter-spacing: -0.025em;
  line-height: 1.2;
  overflow-wrap: anywhere;
}
/* Each alias is a row plus, when a setup run has something to say, a ledger
   beneath it — the item is that pair, and carries the stack spacing. */
.admin-topology__item { margin-top: 7px; }
.admin-topology__alias {
  display: flex;
  align-items: baseline;
  gap: 8px;
  font-size: 13px;
  color: var(--admin-muted);
  overflow-wrap: anywhere;
}
.admin-topology__arrow { color: var(--admin-muted-2); flex: none; }
.admin-topology__code { font-size: 10.5px; letter-spacing: 0.06em; color: var(--admin-muted-2); flex: none; }
.admin-topology--panel {
  padding: 20px 18px;
  background: var(--admin-surface);
  box-shadow: inset 3px 0 0 var(--admin-live);
}
.admin-topology--panel.admin-topology--idle { box-shadow: inset 3px 0 0 var(--admin-idle); }
.admin-topology--empty { box-shadow: inset 3px 0 0 var(--admin-fail); }
.admin-topology__none { margin: 0; font-family: var(--admin-font); font-size: 14px; }

/* Manageable aliases. Each one carries its own controls, and the controls
   appearing is the whole of the hover state — no row tint. This panel is the
   signature element and its aliases are set as bare lines on the surface; a
   band of paper sliding in behind one of them broke both the left spine and
   the quiet of the block, for no information the buttons do not already give.
   The hostname darkening is what ties them to their row. */
.admin-topology__alias--actionable { flex-wrap: wrap; }

.admin-topology__aliasname {
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
  overflow-wrap: anywhere;
}
.admin-topology__alias--actionable:hover .admin-topology__aliasname,
.admin-topology__alias--actionable[data-open] .admin-topology__aliasname { color: var(--admin-text); }

/* Hidden with visibility rather than display: the box stays, so revealing the
   actions never moves the row, and the controls stay out of the tab order while
   collapsed — which is what the toggle's aria-expanded already says. Focus
   reaches them through the toggle, which is always visible.

   Sized down from --small: these sit on a 13px line inside the topology, and at
   the standard small size they set the row height rather than fitting it. */
.admin-topology__actions {
  display: flex;
  flex: none;
  gap: 6px;
  margin-left: auto;
  padding-left: 14px;
  visibility: hidden;
}
.admin-topology__actions form { margin: 0; }
.admin-topology__actions .admin-button {
  padding: 1px 8px;
  font-size: 11.5px;
  white-space: nowrap;
}
.admin-topology__alias--actionable:focus-within .admin-topology__actions,
.admin-topology__alias--actionable[data-open] .admin-topology__actions { visibility: visible; }
@media (hover: hover) {
  .admin-topology__alias--actionable:hover .admin-topology__actions { visibility: visible; }
}

/* --- Live switch ---------------------------------------------------------
   Rectangular, not a pill. Nothing else in this admin is rounded, and the
   register that fits a control deciding whether a domain answers is a breaker
   switch, not a phone setting. The track is the only place the live green is
   used as a fill rather than a marker, which is the point: it is the one
   control on the page whose state is the site's state.
   ------------------------------------------------------------------------- */
.admin-switch-form { flex: none; margin: 2px 0 0; }
.admin-switch {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  padding: 0;
  border: 0;
  background: none;
  color: var(--admin-muted);
  font-family: var(--admin-font);
  cursor: pointer;
}
.admin-switch__label {
  font-size: 10.5px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.1em;
}
.admin-switch__track {
  position: relative;
  flex: none;
  width: 40px;
  height: 20px;
  border: 1px solid var(--admin-rule-2);
  border-radius: var(--admin-radius);
  background: var(--admin-paper);
  transition: background-color 120ms ease, border-color 120ms ease;
}
.admin-switch__knob {
  position: absolute;
  top: 2px;
  left: 2px;
  width: 14px;
  height: 14px;
  border-radius: 1px;
  background: var(--admin-rule-2);
  transition: transform 120ms ease, background-color 120ms ease;
}
.admin-switch[aria-checked="true"] { color: var(--admin-live); }
.admin-switch[aria-checked="true"] .admin-switch__track {
  border-color: var(--admin-live);
  background: var(--admin-live);
}
.admin-switch[aria-checked="true"] .admin-switch__knob {
  background: var(--admin-surface);
  transform: translateX(20px);
}
.admin-switch:hover:not(:disabled) { color: var(--admin-text); }
.admin-switch:hover:not(:disabled) .admin-switch__track { border-color: var(--admin-ink); }
.admin-switch[aria-checked="true"]:hover .admin-switch__track { border-color: var(--admin-ink); }
/* A site with no hostname cannot go live. The panel beside it says so, so the
   switch only has to look unavailable. */
.admin-switch:disabled { cursor: not-allowed; color: var(--admin-muted-2); }
.admin-switch:disabled .admin-switch__track { background: var(--admin-surface); }

/* --- Buttons ------------------------------------------------------------- */
.admin-button {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 7px 13px;
  border: 1px solid var(--admin-rule-2);
  border-radius: var(--admin-radius);
  background: var(--admin-surface);
  color: var(--admin-text);
  font: inherit;
  font-size: 13.5px;
  font-weight: 500;
  text-decoration: none;
  cursor: pointer;
}
.admin-button:hover { border-color: var(--admin-ink); text-decoration: none; }
.admin-button:disabled { opacity: 0.45; cursor: default; }
.admin-button:disabled:hover { border-color: var(--admin-rule-2); }
.admin-button--primary { background: var(--admin-ink); border-color: var(--admin-ink); color: #fff; }
.admin-button--primary:hover { background: var(--admin-ink-2); border-color: var(--admin-ink-2); }
.admin-button--danger { color: var(--admin-fail); border-color: #dcc4c1; }
.admin-button--danger:hover { background: var(--admin-fail); border-color: var(--admin-fail); color: #fff; }
.admin-button--plain { background: transparent; border-color: rgba(255, 255, 255, 0.34); color: #fff; }
.admin-button--plain:hover { background: rgba(255, 255, 255, 0.14); border-color: rgba(255, 255, 255, 0.6); }
.admin-button--small { padding: 4px 9px; font-size: 12.5px; }
.admin-button--block { width: 100%; justify-content: center; padding: 11px; font-size: 15px; }

/* --- Definition rows ----------------------------------------------------- */
.admin-dl { margin: 0; display: grid; grid-template-columns: 190px minmax(0, 1fr); }
.admin-dl dt {
  padding: 11px 14px;
  background: var(--admin-surface);
  border-bottom: 1px solid var(--admin-paper);
  font-size: 10.5px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  color: var(--admin-muted);
}
.admin-dl dd {
  margin: 0;
  padding: 11px 14px;
  background: var(--admin-surface);
  border-bottom: 1px solid var(--admin-paper);
  font-size: 14px;
  overflow-wrap: anywhere;
}
.admin-dl__error { font-family: var(--admin-mono); font-size: 0.9em; color: var(--admin-fail); }

/* Brand colours, read-only. A hex string is not a colour you can judge, so the
   chip carries the value and the hex stays alongside it for copying. The border
   is what keeps a white background from disappearing into the surface. */
.admin-swatches { display: flex; flex-wrap: wrap; gap: 6px 16px; margin: 0; padding: 0; list-style: none; }
.admin-swatch { display: flex; gap: 7px; align-items: center; font-size: 12.5px; }
.admin-swatch__chip {
  width: 20px;
  height: 20px;
  flex: none;
  border: 1px solid var(--admin-rule-2);
  border-radius: var(--admin-radius);
}
.admin-swatch__label { color: var(--admin-muted); }
.admin-swatch__hex { font-family: var(--admin-mono); font-size: 12px; }

/* --- Domain setup ---------------------------------------------------------
   The Set up control on a hostname's line, and the ledger beneath it. The
   setup job appends a line as each step lands, so the panel reads as a
   running record: what has been done, what is happening now, what stopped
   it. Once verified, the same lines become the setup record, folded away
   behind the Verified marker — which is the toggle that reopens them.
   ------------------------------------------------------------------------- */
.admin-setup__form { display: inline-flex; margin: 0; }

/* Setup Now: the control for a hostname that has never been set up, worded
   and coloured as the condition it fixes — red on purpose, because a hostname
   nobody has set up is a thing wrong with the site, and this is the control
   that makes it right. Set in the state markers' small caps so it sits on the
   same line as them; the arrow is the only ornament, and it leans into the
   hover instead of the label growing a box. */
.admin-setup__cta {
  display: inline-flex;
  align-items: baseline;
  gap: 5px;
  padding: 0;
  border: 0;
  background: none;
  color: var(--admin-fail);
  font-family: var(--admin-font);
  font-size: 10.5px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  white-space: nowrap;
  cursor: pointer;
}
.admin-setup__cta:hover .admin-setup__cta-label {
  text-decoration: underline;
  text-underline-offset: 2px;
}
.admin-setup__cta-arrow { transition: transform 120ms ease; }
.admin-setup__cta:hover .admin-setup__cta-arrow { transform: translateX(2px); }

/* Sized like the alias-row actions: Retry sits on a 13px line inside the
   error sentence and must not set the row's height. */
.admin-setup__button {
  padding: 1px 8px;
  border: 1px solid var(--admin-ink);
  border-radius: var(--admin-radius);
  background: var(--admin-ink);
  color: #fff;
  font-family: var(--admin-font);
  font-size: 11.5px;
  font-weight: 500;
  white-space: nowrap;
  cursor: pointer;
}
.admin-setup__button:hover { background: var(--admin-ink-2); border-color: var(--admin-ink-2); }

.admin-setup { font-family: var(--admin-font); }
/* Under an alias row the ledger indents to the hostname, past the arrow. */
.admin-topology__item .admin-setup { padding-left: 22px; }

.admin-ledger { margin: 8px 0 4px; font-family: var(--admin-mono); font-size: 12px; }
.admin-ledger__line { display: flex; align-items: baseline; gap: 10px; padding: 1.5px 0; }
.admin-ledger__time { flex: none; width: 62px; font-size: 11px; color: var(--admin-muted-2); }
.admin-ledger__glyph { flex: none; width: 12px; text-align: center; }
.admin-ledger__line--done { color: var(--admin-muted); }
.admin-ledger__line--done .admin-ledger__glyph { color: var(--admin-live); }
.admin-ledger__line--retried { color: var(--admin-muted-2); }
.admin-ledger__line--fail { color: var(--admin-fail); font-weight: 500; }
.admin-ledger__line--now { color: var(--admin-text); font-weight: 500; }
.admin-ledger__line--now .admin-ledger__glyph::before {
  content: "";
  display: inline-block;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--admin-ink-2);
  animation: admin-pulse 1.1s ease-in-out infinite;
}
/* What the live line is waiting on, aligned under its text (62px time column
   + 12px glyph + two 10px gaps). */
.admin-ledger__wait {
  margin: 3px 0 0;
  padding-left: 94px;
  font-family: var(--admin-font);
  font-size: 12.5px;
  color: var(--admin-muted);
  max-width: 60ch;
}

/* The record spelled out for a person: one row per field a DNS form asks for,
   each its own copy control, and the whole card copyable as one message for
   whoever actually holds the zone. Indented to the ledger's text column. */
.admin-dns-record { margin: 4px 0 2px 94px; max-width: 520px; font-family: var(--admin-font); }
/* Under a failure sentence rather than inside the ledger, the card gives up
   the ledger's indent and lines up with the sentence that introduced it. */
.admin-dns-record--flush { margin-left: 0; }
.admin-dns-record__prose,
.admin-dns-record__note { margin: 0; font-size: 12.5px; color: var(--admin-muted); }
.admin-dns-record__rows { margin: 10px 0; }
.admin-dns-record__row { display: flex; align-items: center; gap: 8px; margin-bottom: 5px; }
.admin-dns-record__row:last-child { margin-bottom: 0; }
.admin-dns-record__key { flex: none; width: 44px; }
.admin-dns-record__value {
  padding: 2px 8px;
  border: 1px solid var(--admin-rule);
  border-radius: var(--admin-radius);
  background: var(--admin-paper);
  font-family: var(--admin-mono);
  font-size: 12px;
  overflow-wrap: anywhere;
}
.admin-dns-record__actions { display: flex; align-items: center; gap: 10px; margin: 10px 0; }

/* A captioned button's copy confirmation: the caption swaps on the same
   data-copied flag the glyph buttons use, so nothing moves and nothing is
   rebuilt in JS. */
.admin-copy-swap__rest,
.admin-copy-swap__done { display: inline-flex; align-items: center; gap: 6px; }
.admin-copy-swap__done { display: none; color: var(--admin-live); }
.admin-button[data-copied] .admin-copy-swap__rest { display: none; }
.admin-button[data-copied] .admin-copy-swap__done { display: inline-flex; }

@media (max-width: 640px) {
  .admin-ledger__wait { padding-left: 0; }
  .admin-dns-record { margin-left: 0; }
}

.admin-setup__error {
  margin: 6px 0 2px;
  font-size: 12.5px;
  color: var(--admin-muted);
  max-width: 66ch;
}
.admin-setup__error strong { color: var(--admin-fail); font-weight: 600; }
.admin-setup__error .admin-setup__form { vertical-align: baseline; }

/* The Verified marker doubles as the record's toggle, so it carries a plus —
   without one it reads as a plain state marker with nothing behind it. */
.admin-setup__record-toggle {
  padding: 0;
  border: 0;
  background: none;
  font-family: var(--admin-font);
  cursor: pointer;
}
.admin-setup__record-toggle::after { content: "+"; color: var(--admin-muted-2); font-size: 12px; }
.admin-setup__record-toggle[aria-expanded="true"]::after { content: "\2212"; }
.admin-setup__record-toggle:hover { text-decoration: underline; text-underline-offset: 2px; }
.admin-setup--record { margin-top: 6px; }

/* --- Preview link -------------------------------------------------------
   A site's other address, so it sits under the name rather than in a section of
   its own. A link, not a field: the only things anyone does with a preview URL
   are open it and copy it, and a URL in a read-only input looks editable.

   The URL truncates because its token is 32 characters nobody reads, and no
   header lockup survives an h1 followed by three wrapped lines of mono. The
   host is the informative part and it is what survives; the title attribute and
   the copy button carry the whole value.
   ------------------------------------------------------------------------- */
.admin-preview { display: flex; align-items: center; gap: 9px; margin: 10px 0 0; min-width: 0; }
.admin-preview__label { flex: none; }
.admin-preview__link { min-width: 0; font-family: var(--admin-mono); font-size: 12.5px; color: var(--admin-muted); }
.admin-preview__link:hover { color: var(--admin-text); }
.admin-preview__url {
  display: block;
  max-width: 46ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Subtle by the brief: no border and no box, sized to the line it sits on, so
   adding it to the header costs the lockup nothing. It still has to confirm
   itself — a glyph-only button that does nothing visible is indistinguishable
   from a broken one — so it keeps the tick swap and the live region. */
.admin-glyph-button {
  display: inline-flex;
  flex: none;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  padding: 0;
  border: 0;
  border-radius: var(--admin-radius);
  background: none;
  color: var(--admin-muted-2);
  cursor: pointer;
}
.admin-glyph-button:hover { background: #e2e8e5; color: var(--admin-text); }
.admin-glyph-button[data-copied] { color: var(--admin-live); }
/* One button, two glyphs, swapped on the copied flag — so the confirmation costs
   no layout shift and needs nothing rebuilt in JS. */
.admin-glyph-button__done { display: none; }
.admin-glyph-button[data-copied] .admin-glyph-button__rest { display: none; }
.admin-glyph-button[data-copied] .admin-glyph-button__done { display: inline-flex; }
.admin-icon { display: block; }

.admin-sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}
.admin code.admin-inline-code {
  padding: 1px 4px;
  border-radius: var(--admin-radius);
  background: #e2e8e5;
  font-family: var(--admin-mono);
  font-size: 0.86em;
  overflow-wrap: anywhere;
}

/* --- Small shared utilities ---------------------------------------------
   Named rather than set inline, so the spacing stays adjustable in one place.
   ------------------------------------------------------------------------- */
.admin-note { margin: 22px 0 0; font-size: 14px; }
.admin-subhead { margin: 22px 0 10px; }
.admin-hint-inline { font-size: 12.5px; }
.admin-field--narrow { max-width: 380px; }
.admin-field--tiny { max-width: 220px; }
.admin-stack-end { margin-bottom: 26px; }
/* A standalone attention item, used outside the dashboard's list. Two classes
   so the margin survives .admin-attention__item:last-child. */
.admin-attention__item.admin-attention__item--solo { grid-template-columns: 1fr; margin-bottom: 34px; }

/* Butter slug -> public path map. */
.admin-slug-map { list-style: none; margin: 0; padding: 0; font-family: var(--admin-mono); font-size: 12.5px; }
.admin-slug-map li {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: baseline;
  padding: 7px 14px;
  margin-bottom: 2px;
  background: var(--admin-surface);
  overflow-wrap: anywhere;
}
.admin-slug-map li:last-child { margin-bottom: 0; }

/* --- Forms --------------------------------------------------------------- */
.admin-fieldset { margin: 0 0 34px; padding: 0; border: 0; }
.admin-fieldset__legend {
  width: 100%;
  padding: 10px 0 0;
  margin: 0 0 4px;
  border-top: 2px solid var(--admin-ink);
  font-size: 14px;
  font-weight: 600;
}
.admin-fieldset__note { margin: 0 0 18px; font-size: 13px; color: var(--admin-muted); max-width: 76ch; }
.admin-field { margin-bottom: 18px; }
.admin-field__label { display: block; margin-bottom: 5px; font-size: 12.5px; font-weight: 600; }
.admin-field__hint { margin: 5px 0 0; font-size: 12.5px; color: var(--admin-muted); max-width: 72ch; }

.admin-field input[type="text"],
.admin-field input[type="email"],
.admin-field input[type="url"],
.admin-field input[type="number"],
.admin-field select,
.admin-field textarea {
  width: 100%;
  padding: 8px 10px;
  border: 1px solid var(--admin-rule-2);
  border-radius: var(--admin-radius);
  background: var(--admin-surface);
  color: var(--admin-text);
  font: inherit;
  font-size: 14px;
}
.admin-field input:disabled { background: #e4e9e7; color: var(--admin-muted); }
.admin-field textarea { min-height: 6rem; resize: vertical; }
.admin-field textarea.admin-code { min-height: 200px; font-family: var(--admin-mono); font-size: 12.5px; }

.admin-field--checkbox { display: flex; align-items: flex-start; gap: 9px; }
.admin-field--checkbox input { width: auto; margin-top: 3px; }
.admin-field--checkbox .admin-field__label { margin-bottom: 2px; }

.admin-colour-row { display: flex; gap: 8px; align-items: center; }
.admin-colour-row input[type="color"] {
  width: 34px;
  height: 34px;
  padding: 0;
  border: 1px solid var(--admin-rule-2);
  border-radius: var(--admin-radius);
  background: var(--admin-surface);
  flex: none;
}

.admin-grid { display: grid; gap: 18px; }
@media (min-width: 760px) { .admin-grid--2 { grid-template-columns: 1fr 1fr; } }

.admin-form__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
  margin-top: 26px;
  padding-top: 6px;
  border-top: 1px solid var(--admin-rule-2);
}
.admin-form__actions--bare { margin-top: 18px; padding-top: 0; border-top: 0; }

/* --- Modal ---------------------------------------------------------------
   A native <dialog>, which is doing the real work: the top layer, Escape, focus
   containment, an inert background, and focus handed back to the trigger all
   come from showModal() rather than from script.

   The tie to the rest of the system is the 6px spine the sign-in card already
   uses to scale the row marker up to architecture — and only that. A section's
   2px ink rule belongs to a section: on a panel this small it sat a few pixels
   under the top edge and read as a second border rather than as structure. The
   danger variant moves the spine and the title to the failure colour, because
   deleting a site is the one thing on this page that cannot be undone.
   ------------------------------------------------------------------------- */
.admin-modal {
  width: min(440px, calc(100vw - 32px));
  max-height: calc(100vh - 48px);
  /* Zero padding, so a click that lands on the dialog element rather than on the
     panel inside it is unambiguously a click on the backdrop. */
  padding: 0;
  border: 0;
  border-radius: var(--admin-radius);
  background: transparent;
  color: var(--admin-text);
  overflow: auto;
}
.admin-modal::backdrop { background: rgba(19, 32, 28, 0.55); }
.admin-modal__panel {
  padding: 20px 24px 24px;
  background: var(--admin-surface);
  border-left: 6px solid var(--admin-ink);
  border-radius: var(--admin-radius);
}
.admin-modal--danger .admin-modal__panel { border-left-color: var(--admin-fail); }
.admin-modal__title {
  margin: 0;
  font-size: 14px;
  font-weight: 600;
  overflow-wrap: anywhere;
}
.admin-modal--danger .admin-modal__title { color: var(--admin-fail); }
.admin-modal__note { margin: 8px 0 20px; font-size: 13px; color: var(--admin-muted); }
.admin-modal__actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 22px; }
.admin-modal .admin-field:last-of-type { margin-bottom: 0; }

/* Short enough to read as the panel arriving rather than as an effect. The
   reduced-motion block at the end of this file switches it off. */
@media (prefers-reduced-motion: no-preference) {
  .admin-modal[open] { animation: admin-modal-in 120ms ease-out; }
  @keyframes admin-modal-in {
    from { opacity: 0; transform: translateY(-6px); }
  }
}

/* --- Action rows ---------------------------------------------------------
   A control that does something consequential, with what it does stated beside
   it rather than in a hint underneath. Where two of these sit together the
   question is never "what does this control do" but "which of these am I about
   to do", and a description on the same line as its button answers that in one
   movement of the eye.

   The controls take a fixed column and fill it, so they read as a matched set
   and every description starts at the same place — including down the page,
   between one section and the next.
   ------------------------------------------------------------------------- */
.admin-action-list { list-style: none; margin: 0; padding: 0; }
/* A list that opens a section body needs no gap above it; one that follows
   something does. */
.admin-action-list:not(:first-child) { margin-top: 22px; }
.admin-action-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 18px;
  align-items: center;
  padding: 12px 0;
  border-bottom: 1px solid var(--admin-rule);
}
.admin-action-row:first-child { padding-top: 0; }
.admin-action-row:last-child { padding-bottom: 0; border-bottom: 0; }
.admin-action-row__control { flex: none; width: 190px; }
.admin-action-row__control form { margin: 0; }
.admin-action-row__control .admin-button,
.admin-action-row__control input[type="submit"] { width: 100%; justify-content: center; }
/* A container rather than a paragraph, so an action with a caveat can state it
   in a second line that still starts in the description column. Left of that
   column it would read as a footnote to the section rather than to the button. */
.admin-action-row__what { font-size: 13px; color: var(--admin-muted); max-width: 68ch; }
.admin-action-row__what p { margin: 0; }
.admin-action-row__what p + p { margin-top: 7px; }

/* --- Flash --------------------------------------------------------------- */
.admin-flash {
  margin-bottom: 22px;
  padding: 11px 15px;
  font-size: 14px;
  background: var(--admin-live-bg);
  box-shadow: inset 3px 0 0 var(--admin-live);
}
.admin-flash--notice { background: var(--admin-live-bg); box-shadow: inset 3px 0 0 var(--admin-live); }
.admin-flash--alert { background: var(--admin-fail-bg); box-shadow: inset 3px 0 0 var(--admin-fail); }
.admin-flash p { margin: 0; }
.admin-flash ul { margin: 0.5rem 0 0; padding-left: 20px; }

/* --- Pagination ---------------------------------------------------------- */
.admin-pagination { display: flex; gap: 10px; align-items: center; margin-top: 18px; }
.admin-pagination__info { font-family: var(--admin-mono); font-size: 12.5px; color: var(--admin-muted); }

/* --- Sign in ------------------------------------------------------------
   You are outside the instrument, so the working surface is gone. The page
   names the application and offers one action; it says nothing about what
   the application is for.
   ------------------------------------------------------------------------- */
.admin-login {
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 40px 24px;

  /* Faint graph paper. The admin is an instrument panel, so the one texture it
     gets is ruled paper — 4% white on the stone ground, present but never
     something you look at. */
  background-color: var(--admin-rail);
  background-image:
    linear-gradient(to right, rgba(255, 255, 255, 0.045) 1px, transparent 1px),
    linear-gradient(to bottom, rgba(255, 255, 255, 0.045) 1px, transparent 1px);
  background-size: 34px 34px;
}
/* Narrow on purpose. At a wider measure the mark left a dead gap to its right
   and the card read as unresolved; at this width the logo fills about half the
   column and the remaining space reads as margin. */
.admin-login__stack { width: 100%; max-width: 312px; }

/* The card takes the row spine and scales it up to architecture: the same
   device that marks a broken row becomes the left edge of the whole page. Ink,
   not a state colour — nothing here is reporting a status. No 1px border: on
   the stone ground it was invisible and only softened the edge. */
.admin-login__card {
  padding: 30px 26px;
  background: var(--admin-surface);
  border-left: 6px solid var(--admin-ink);
  border-radius: var(--admin-radius);
}
.admin-login__logo { display: block; width: 122px; height: auto; margin: 0 0 28px; }

/* Same 2px ink rule that opens every section elsewhere. Here it separates who
   owns this from what it is. */
.admin-login__plate { border-top: 2px solid var(--admin-ink); padding-top: 16px; }
.admin-login__title {
  margin: 0 0 28px;
  font-family: var(--admin-mono);
  font-size: 28px;
  font-weight: 600;
  letter-spacing: -0.03em;
  line-height: 1.1;
}
.admin-login .admin-button--primary { transition: background-color 120ms ease; }

/* --- Narrow ------------------------------------------------------------- */
@media (max-width: 860px) {
  .admin-shell { grid-template-columns: 1fr; }
  .admin-rail { position: static; height: auto; padding-bottom: 14px; }
  .admin-rail__brand { padding-bottom: 14px; }
  .admin-rail__nav { flex-direction: row; flex-wrap: wrap; gap: 2px; padding: 0 14px; }
  .admin-rail__link { padding: 6px 10px; border-left: 0; border-bottom: 2px solid transparent; }
  .admin-rail__link--active { border-left: 0; border-bottom-color: #fff; }
  .admin-rail__foot { margin-top: 14px; }
  .admin-main { padding: 22px 18px 60px; }
}

@media (max-width: 680px) {
  .admin-attention__item { grid-template-columns: 1fr; gap: 6px; }
  .admin-dl { grid-template-columns: 1fr; }
  .admin-dl dt { padding-bottom: 2px; border-bottom: 0; }

  /* Hostname actions take a line of their own: beside a hostname there is not
     enough width left for them, and shrinking them broke "Remove" across two
     lines. Reserving that line on every row would leave a column of tall empty
     rows, so here they are removed from the flow until the row is tapped —
     which is the only way in on a device with no hover anyway. */
  .admin-topology__actions {
    display: none;
    flex-basis: 100%;
    margin: 7px 0 0;
    padding-left: 22px;
  }
  .admin-topology__alias--actionable[data-open] .admin-topology__actions { display: flex; }
}

@media (prefers-reduced-motion: reduce) {
  .admin *, .admin *::before, .admin *::after { transition: none !important; animation: none !important; }
}
