/*
 * thermals-demo styles, against docs/UX_STYLE_GUIDE.md.
 *
 * The tokens below are the estate's, copied rather than re-derived. Every role
 * colour in slapp's client/src/styles/base.css was MEASURED against the app's
 * real surfaces - four of them sit a step darker than the brand guide's
 * published hex because the published values did not clear 4.5:1 in the
 * direction they were pointing. Inventing approximations here would throw that
 * work away and produce a second, subtly different blue for a person who moves
 * between the two apps in one afternoon.
 *
 * DARK IS THE DEFAULT, as across the estate, and light is the
 * prefers-color-scheme branch. Both blocks define every colour: a token
 * declared in only one silently inherits the wrong theme's value, which is the
 * failure the both-blocks rule exists to prevent.
 */

/*
 * The brand typeface, self-hosted. The CSP here allows no remote origin at all
 * - not even fonts.googleapis.com - so vendoring is the only way to have it,
 * and font-display: swap means a slow font never blocks first paint. One
 * variable file covers 400-700, which is every weight this app uses.
 */
@font-face {
  font-family: "Rubik";
  src: url("/fonts/rubik-latin.woff2") format("woff2");
  font-display: swap;
  font-style: normal;
  font-weight: 400 700;
}

:root {
  /*
   * THE TYPE SCALE: six steps, which the guide says is enough and is right -
   * every size added is a decision every later change has to re-make. Lengths
   * rather than colours, so one declaration serves both themes.
   */
  --fs-meta: 11px;
  --fs-small: 12px;
  --fs-body: 13px;
  --fs-head: 15px;
  --fs-title: 18px;
  --fs-hero: 24px;

  --font: "Rubik", -apple-system, system-ui, "Segoe UI", sans-serif;
  --mono: ui-monospace, SFMono-Regular, Menlo, monospace;

  --radius: 10px;
  /* Panels, tables and the toolbar, as in thermals-service. Controls keep
   * --radius; the larger one is for the surfaces they sit on. */
  --radius-lg: 14px;

  /*
   * One reading measure, on the paragraph only. Cards, tables and the demo
   * grid get the whole column - a cap on a card is what makes a page look like
   * it failed to load.
   */
  --measure: 78ch;

  /* For menus only; panels and cards are flat, a border and no shadow. */
  --shadow-pop: 0 14px 34px rgb(4 14 22 / 32%);

  /* Dark, and the default. */
  --page: #101922;
  --surface: #182631;
  --surface-2: #1e2f3d;
  --ink: #ffffff;
  --muted: #a7bccd;
  --border: #2f4459;
  --accent: #4da3ff;
  --accent-ink: #0d1b26;
  --warning: #fab219;
  --danger: #ec7676;
  --success: #4faa72;

  /*
   * The -strong step: the same hue, for text sitting ON a wash of itself. A
   * chip filled with a 20% wash of a role and labelled in that same role
   * cannot reach 4.5:1 at any token value - measured, it lands around 3.7-4.0,
   * and the wash at which it would clear is invisible. A second step is the
   * fix; retuning the first is not. Distinct from --accent-ink, which is ink
   * on a SOLID fill: two concepts, two nouns.
   */
  --danger-strong: #ffb3b3;
  --success-strong: #a6e0bd;
  --warning-strong: #ffd37a;
  --accent-strong: #a9d1ff;
}

@media (prefers-color-scheme: light) {
  :root {
    --page: #f4f8fc;
    --surface: #ffffff;
    --surface-2: #f0f5fa;
    --ink: #15354d;
    --muted: #5b7185;
    --border: #d3e0ea;
    --accent: #2770c7;
    --accent-ink: #ffffff;
    --warning: #8f6300;
    --danger: #c23b3b;
    --success: #2b7c4c;

    --danger-strong: #8f1f1f;
    --success-strong: #1f5c37;
    --warning-strong: #5c4000;
    --accent-strong: #1b4f8f;
  }
}

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

body {
  margin: 0;
  padding: 0 0 2.5rem;
  background: var(--page);
  color: var(--ink);
  font: var(--fs-body)/1.55 var(--font);
  -webkit-text-size-adjust: 100%;
}

main { max-width: 60rem; margin: 0 auto; padding: 0 1rem; }

/* ---------------------------------------------------------------- shell --- */

/*
 * The estate's bar, after thermals-id: thin, full width, the word mark on the
 * left and one account menu on the right. Pages under it are one centred
 * column - .shell - with the page title in a .page-head rather than a hero.
 */
.bar {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.35rem 1rem;
  min-height: 2.75rem;
  border-bottom: 1px solid var(--border);
  background: var(--surface);
}
.bar__grow { flex: 1; }
.bar__mark {
  display: inline-flex;
  align-items: baseline;
  gap: 0.45rem;
  color: var(--ink);
  text-decoration: none;
  min-height: 24px;
}
.bar__brand { font-weight: 650; letter-spacing: -0.01em; font-size: var(--fs-head); }
.bar__app {
  color: var(--muted);
  font-weight: 500;
  padding-left: 0.45rem;
  border-left: 1px solid var(--border);
}
.bar__nav { display: flex; gap: 0.15rem; margin-left: 0.75rem; }
.bar__nav a {
  color: var(--muted);
  text-decoration: none;
  padding: 0.3rem 0.6rem;
  border-radius: var(--radius);
  min-height: 24px;
}
.bar__nav a:hover { color: var(--ink); background: var(--surface-2); }
.bar__nav a[aria-current="page"] { color: var(--ink); background: var(--surface-2); font-weight: 600; }

.account { position: relative; }
.account__trigger {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.2rem 0.45rem;
  border: 1px solid transparent;
  border-radius: var(--radius);
  cursor: pointer;
  list-style: none;
  font-size: var(--fs-small);
  min-height: 2rem;
}
.account__trigger::-webkit-details-marker { display: none; }
.account__trigger:hover, .account[open] .account__trigger { border-color: var(--border); background: var(--surface-2); }
.account__trigger:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.account__caret { color: var(--muted); }
.avatar {
  display: inline-grid;
  place-items: center;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--accent);
  color: var(--accent-ink);
  font-size: var(--fs-meta);
  font-weight: 600;
}
.account__menu {
  position: absolute;
  right: 0;
  top: calc(100% + 0.3rem);
  z-index: 20;
  min-width: 14rem;
  padding: 0.25rem;
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-pop);
}
.account__menu a, .account__menu button {
  display: block;
  width: 100%;
  text-align: left;
  font: inherit;
  font-size: var(--fs-small);
  padding: 0.4rem 0.55rem;
  border: 0;
  border-radius: var(--radius);
  background: none;
  color: var(--ink);
  text-decoration: none;
  cursor: pointer;
}
.account__menu a:hover, .account__menu button:hover { background: var(--surface-2); }
.account__who {
  margin: 0 0 0.25rem;
  padding: 0.4rem 0.55rem 0.5rem;
  border-bottom: 1px solid var(--border);
  font-size: var(--fs-small);
}
.account__who span { display: block; color: var(--muted); }
/* Sign out: last, behind a rule, and not red - leaving is not destructive. */
.account__sep { margin: 0.25rem 0 0; padding-top: 0.25rem; border-top: 1px solid var(--border); }

/* The admin tabs: links with aria-current, not role=tab - they navigate. */
.subbar { border-bottom: 1px solid var(--border); background: var(--surface); }
.tabs {
  display: flex;
  gap: 0.25rem;
  max-width: 76rem;
  margin: 0 auto;
  padding: 0 1rem;
  overflow-x: auto;
}
.tabs__tab {
  padding: 0.55rem 0.75rem;
  border-bottom: 2px solid transparent;
  color: var(--muted);
  text-decoration: none;
  white-space: nowrap;
}
.tabs__tab:hover { color: var(--ink); }
.tabs__tab[aria-current="page"] { border-bottom-color: var(--accent); color: var(--ink); font-weight: 600; }

main.shell { max-width: 68rem; padding-top: 1.1rem; }
main.shell--wide { max-width: 76rem; }

.page-head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 0.75rem;
  margin: 0 0 1rem;
}
.page-head__text { flex: 1 1 20rem; min-width: 0; }
.page-head h1 { font-size: var(--fs-title); letter-spacing: -0.01em; }
.page-head__lede { margin: 0.25rem 0 0; color: var(--muted); }
.page-head__actions { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.page-head__actions:empty { display: none; }
.crumb {
  display: inline-block;
  margin-bottom: 0.3rem;
  color: var(--muted);
  font-size: var(--fs-small);
  text-decoration: none;
}
.crumb:hover { color: var(--ink); text-decoration: underline; }

/* An 11px kicker above a panel, as thermals-id heads its sections. */
.section-title {
  margin: 1.5rem 0 0.45rem;
  color: var(--muted);
  font-size: var(--fs-meta);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

.panel {
  padding: 0.85rem 1rem 1rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface);
}
.panel + .form-actions, .table-wrap + .disclosure { margin-top: 1rem; }

.callout {
  margin: 0 0 1rem;
  padding: 0.6rem 0.8rem;
  border: 1px solid var(--border);
  border-left: 3px solid var(--accent);
  border-radius: var(--radius);
  background: var(--surface-2);
  color: var(--muted);
}
.callout strong { color: var(--ink); font-weight: 600; }

.meta { display: flex; flex-wrap: wrap; align-items: center; gap: 0.35rem 0.9rem; color: var(--muted); margin: 0 0 1rem; }

.empty { color: var(--muted); margin: 0.4rem 0; }
.empty-state {
  display: grid;
  gap: 0.35rem;
  justify-items: start;
  padding: 1.5rem 1rem;
  border: 1px dashed var(--border);
  border-radius: var(--radius-lg);
}

.mono { font-family: var(--mono); font-size: var(--fs-small); }
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

a { color: var(--accent); }

p { margin: 0 0 0.75rem; max-width: var(--measure); }
p:last-child { margin-bottom: 0; }

h1, h2, h3 { margin: 0; font-weight: 600; line-height: 1.25; }
/* A section heading owns the gap under it. Set here rather than per-section so
 * a new panel cannot ship with its title welded to its first field. */
.table-wrap h2 { font-size: var(--fs-head); margin-bottom: 0.75rem; }

/* ------------------------------------------------------------- notices --- */

/*
 * Colour never carries the meaning alone - each of these is introduced by a
 * word ("could not be sent", "is ready") and carries role="alert" or "status".
 * The wash-plus--strong-ink pairing is what keeps the label readable on its
 * own tint; see the token block.
 */
.notice {
  border-radius: var(--radius);
  padding: 0.7rem 0.9rem;
  margin: 0 0 1rem;
  border: 1px solid;
}
.notice--bad {
  background: color-mix(in srgb, var(--danger) 18%, transparent);
  border-color: var(--danger);
  color: var(--danger-strong);
}
.notice--good {
  background: color-mix(in srgb, var(--success) 18%, transparent);
  border-color: var(--success);
  color: var(--success-strong);
}

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

/*
 * The directory: compact tiles in a grid, after thermals-lobby's. auto-fill
 * with a minimum track means the column count comes from the viewport rather
 * than a breakpoint list - one column on a phone, three in the .shell column
 * on a laptop - and comparing demos side by side is the whole job of this
 * screen. Nineteen demos at prose length were a page you scrolled; at the
 * app's own type scale, as tiles, they are a set you scan.
 */
.cards {
  display: grid;
  gap: 0.6rem;
  grid-template-columns: repeat(auto-fill, minmax(19rem, 1fr));
}
.cards > .empty-state { grid-column: 1 / -1; }
/* The filter hides tiles with [hidden]; nothing here may give a tile a display
 * value that would outrank it. */
.cards > [hidden] { display: none; }

/*
 * The spine: one 4px accent bar down the left edge, as the lobby marks its
 * tiles. An inset shadow rather than a pseudo-element, so it follows the
 * corner radius for free and cannot collide with the link overlay below,
 * which is this card's ::after. One hue for every tile - a colour that varied
 * per card would invite readers to decode a meaning that does not exist. A
 * tile that opens nothing yet gets the spine in --border: it is on the page,
 * it is not a door.
 */
.card {
  padding: 0.7rem 0.85rem 0.75rem 1.1rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: inset 4px 0 0 var(--border);
}
.card--go { box-shadow: inset 4px 0 0 color-mix(in srgb, var(--accent) 55%, transparent); }

.card__name {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.2rem 0.4rem;
  font-size: var(--fs-head);
  letter-spacing: -0.01em;
}
/*
 * The screenshot leads the card: a person scanning twenty demos recognises a
 * screen faster than a sentence. Cropped from the top, because the top of an
 * app - its bar and its first rows - is the part that says what it is. The
 * logo sits in the name's line at the name's height.
 */
.card__shot {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 10;
  object-fit: cover;
  object-position: top;
  margin: 0 0 0.6rem;
  border: 1px solid var(--border);
  border-radius: calc(var(--radius) - 2px);
  background: var(--surface-2);
}
.card__logo {
  flex: none;
  width: 1.4rem;
  height: 1.4rem;
  object-fit: contain;
}
.card__link { text-decoration: none; }
/* Fires from anywhere on the card, not just over the name: the pointer is over
 * the anchor's own overlay below, and :hover matches the element that
 * generated it. The name underlines wherever the pointer sits, which is an
 * accurate statement of what a click will do. */
.card__link:hover { text-decoration: underline; }
.card__url {
  margin: 0.1rem 0 0;
  color: var(--muted);
  font-size: var(--fs-meta);
  font-family: var(--mono);
  overflow-wrap: anywhere;
}
.card__tagline { margin: 0.45rem 0 0.3rem; color: var(--ink); font-weight: 500; }
.card__body { margin: 0 0 0.5rem; color: var(--muted); font-size: var(--fs-small); line-height: 1.5; }
/* The warning is the last thing read before the click, set off by a hairline
 * so it reads as a note about the demo rather than more of its description. */
.card__notes {
  margin: 0;
  padding-top: 0.45rem;
  border-top: 1px solid color-mix(in srgb, var(--border) 60%, transparent);
  color: var(--muted);
  font-size: var(--fs-small);
  line-height: 1.5;
}
.card__notes strong { color: var(--ink); font-weight: 600; }

/* ------------------------------------ the card is the link's hit area --- */

/*
 * The whole card opens the demo, and the LINK is still the app's name: the
 * anchor grows a hit area over the card rather than the card becoming an
 * anchor. Wrapping the card in an <a> was the obvious move and is the wrong
 * one - it makes the accessible name all four paragraphs inside, including the
 * "Before you click" warning, so a screen reader's link list becomes nineteen
 * paragraph-length entries. Because the overlay is part of the anchor rather
 * than a stand-in for it, middle-click, cmd-click and "copy link address" work
 * across the whole card exactly as they do across the words.
 *
 * A modifier from the template, not :has(.card__link). The card function
 * already branches on app.live, so another branch on the same fact is cheaper
 * than asking the stylesheet to re-derive it. Scoping the overlay to .card--go
 * is also the safety property: the pseudo-element cannot exist without the
 * positioned ancestor it measures itself against, and an inset: 0 resolved
 * against the page would lay an invisible link over every pixel of it.
 *
 * TEXT SELECTION INSIDE A LIVE CARD IS GONE, and that is accepted rather than
 * overlooked. The usual mitigation is to lift .card__url out with a z-index,
 * but that line is the most link-looking text on the card - mono, an address,
 * under the name - and lifting it turns the one thing a person is most likely
 * to click expecting navigation into a dead strip. A passive absence beats an
 * active broken promise. Right-click anywhere on the card still offers "copy
 * link address", so the address remains obtainable from any point.
 *
 * No cursor: pointer is declared and none is needed - cursor inherits, the
 * browser sets it on the anchor, and the overlay inherits it from there. A
 * second copy of that fact is what eventually drifts onto the card with no
 * link.
 */
.card--go { position: relative; }
.card--go .card__link::after {
  content: "";
  position: absolute;
  inset: 0;
}

/*
 * Hover reinforces; it never informs. The card already says it is a link with
 * no pointer anywhere near it - the name is in --accent because it IS one, and
 * the address underneath states where it goes - so this is a second telling
 * rather than the only one. Gated on a real pointer because a touchscreen
 * resolves :hover into a sticky state after the tap, lighting up the card you
 * have already left.
 */
@media (hover: hover) {
  .card--go:hover {
    border-color: var(--accent);
    background: var(--surface-2);
    box-shadow: inset 4px 0 0 var(--accent);
  }
}

/*
 * Focus stays on the anchor and so does its ring, because the anchor is what
 * Enter activates. The card then repeats the statement with a border, a full
 * spine and a wash - not a second outline drawn two pixels outside the first,
 * and not a ring removed on the promise of one drawn elsewhere, which is the
 * trade the textbook version of this pattern makes and this stylesheet does
 * not. A browser too old for :has() loses the wash and keeps the ring, which
 * is the right way round to degrade.
 */
.card--go:has(.card__link:focus-visible) {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 8%, var(--surface));
  box-shadow: inset 4px 0 0 var(--accent);
}

/* The filter, in the page head's action slot; revealed by directory.js. */
.directory__filter { display: block; width: 16rem; }
.directory__count {
  margin-left: 0.35rem;
  color: var(--muted);
  font-weight: 500;
  letter-spacing: 0;
  font-variant-numeric: tabular-nums;
}
/* .empty-state sets display: grid, which outranks the UA's [hidden] rule. */
.empty-state[hidden] { display: none; }
#demos + .empty-state { margin-top: 0.6rem; }

/* The way to a person: one panel under the grid, text left and the button
 * right, wrapping under it when the column is narrow. */
.cta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem 1.5rem;
}
.cta__text { flex: 1 1 22rem; min-width: 0; }
.cta__title { font-size: var(--fs-head); margin-bottom: 0.25rem; }

/* --------------------------------------------------------------- forms --- */

.field { display: block; margin: 0 0 0.9rem; }
.field__label { display: block; font-weight: 500; margin-bottom: 0.3rem; }
.field__hint { display: block; color: var(--muted); font-size: var(--fs-small); margin-top: 0.3rem; }
.opt { font-weight: 400; color: var(--muted); }

input, select, textarea {
  width: 100%;
  font: inherit;
  color: var(--ink);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 0.4rem 0.6rem;
  /* WCAG 2.2 target size: padding buys the height, not font-size. */
  min-height: 2rem;
}
textarea { resize: vertical; min-height: 6rem; }

/* The focus ring is always visible - never removed, never hover-only. */
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
button:focus-visible,
a:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/*
 * The filled primary. --accent carries --accent-ink, which is the measured
 * pairing; the brand's Light Blue with white type on top is the tempting fill
 * that fails an audit at 3.64:1, and it is not reachable from these tokens.
 */
.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  font: inherit;
  font-weight: 600;
  background: var(--accent);
  color: var(--accent-ink);
  border: 1px solid var(--accent);
  border-radius: var(--radius);
  padding: 0.35rem 0.9rem;
  min-height: 2rem;
  cursor: pointer;
  text-decoration: none;
}
.button:hover { filter: brightness(1.08); }

.linkish {
  font: inherit;
  background: none;
  border: 0;
  color: var(--muted);
  text-decoration: underline;
  cursor: pointer;
  padding: 0.25rem 0;
}
.linkish:hover { color: var(--ink); }
.linkish--danger { color: var(--danger); }
.linkish--danger:hover { color: var(--danger-strong); }

/* The outlined default, after thermals-service's .btn; one filled primary per
 * screen is .button on its own. */
.button--secondary {
  background: var(--surface-2);
  color: var(--ink);
  border: 1px solid var(--border);
}
.button--secondary:hover { border-color: var(--accent); filter: none; }
.button--ghost { background: transparent; color: var(--ink); border: 1px solid transparent; }
.button--ghost:hover { border-color: var(--border); filter: none; }
.button--danger { background: transparent; color: var(--danger); border: 1px solid var(--danger); }
.button--danger:hover { background: color-mix(in srgb, var(--danger) 12%, transparent); filter: none; }
.button--small { min-height: 1.75rem; padding: 0.2rem 0.65rem; font-size: var(--fs-small); white-space: nowrap; }
.button:disabled { opacity: 0.55; cursor: progress; }

fieldset.field {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 0.7rem 0.85rem;
  margin-inline: 0;
}
.check { display: block; margin: 0.25rem 0; font-weight: 400; }
.check input { width: auto; margin-right: 0.4rem; min-height: 0; }

/* ------------------------------------------------------------- the gate --- */

/*
 * Every page outside the gate - and the few dead ends inside it - is one
 * narrow panel centred under the bar, after thermals-id's login card: the
 * title inside the panel, both ways in stacked full width, nothing else on
 * the page competing with them.
 */
main.shell--narrow { max-width: 27rem; padding-top: 3rem; }
/* The request form and its receipt: wider than a login, narrower than a grid. */
main.shell--form { max-width: 44rem; }

.entry h1 { font-size: var(--fs-title); letter-spacing: -0.01em; }
.entry__kicker {
  margin: 0 0 0.2rem;
  color: var(--muted);
  font-size: var(--fs-meta);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.entry__sub { margin: 0.3rem 0 1rem; color: var(--muted); }
.entry .notice { margin-top: 0.75rem; }
.entry__actions { margin-top: 1rem; }
.entry > .entry__sub:last-child { margin-bottom: 0; }
.shell--narrow > .aside { text-align: center; }

.button--block { display: flex; width: 100%; }

.divider {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  max-width: none;
  color: var(--muted);
  font-size: var(--fs-small);
  margin: 1rem 0;
}
.divider::before, .divider::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--border);
}

/* --------------------------------------------------------------- admin --- */

/* Wide content scrolls inside its own bordered box; the page never pans. */
.table-wrap {
  overflow-x: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface);
}
.table {
  width: 100%;
  border-collapse: collapse;
  /* Columns of dates and counts must not jitter between rows. */
  font-variant-numeric: tabular-nums;
}
.table th, .table td {
  text-align: left;
  padding: 0.55rem 0.75rem;
  vertical-align: top;
}
.table thead th {
  border-bottom: 1px solid var(--border);
  color: var(--muted);
  font-size: var(--fs-small);
  font-weight: 600;
  white-space: nowrap;
}
.table tbody tr + tr { border-top: 1px solid color-mix(in srgb, var(--border) 55%, transparent); }
@media (hover: hover) {
  .table tbody tr:hover { background: var(--surface-2); }
}
.row-title { display: inline; font-weight: 600; }
a.row-title { text-decoration: none; }
a.row-title:hover { text-decoration: underline; }
.row-sub { display: block; color: var(--muted); font-size: var(--fs-small); margin-top: 0.1rem; }
.row--dim { color: var(--muted); }

/* Edit, revoke and restore sit together in one cell; each is its own form. */
.actions { display: flex; gap: 0.9rem; align-items: baseline; justify-content: flex-end; flex-wrap: wrap; }
.actions form { margin: 0; }

/*
 * Chips name things (the demos a code opens); a .status says a state. Both
 * spell out a word - colour only reinforces it - and each tinted one is
 * labelled in its role's -strong step so the text clears 4.5:1 on its wash.
 */
.chips { display: inline-flex; flex-wrap: wrap; gap: 0.3rem; }
.chip, .status {
  display: inline-flex;
  align-items: center;
  padding: 0.05rem 0.5rem;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--surface-2);
  color: var(--ink);
  font-size: var(--fs-meta);
  font-weight: 500;
  white-space: nowrap;
}
.chip--accent, .status--accent {
  border-color: color-mix(in srgb, var(--accent) 45%, transparent);
  background: color-mix(in srgb, var(--accent) 15%, transparent);
  color: var(--accent-strong);
}
.chip--warn, .status--warn {
  border-color: color-mix(in srgb, var(--warning) 45%, transparent);
  background: color-mix(in srgb, var(--warning) 15%, transparent);
  color: var(--warning-strong);
}
.status { text-transform: uppercase; letter-spacing: 0.05em; color: var(--muted); }
.status--good {
  border-color: color-mix(in srgb, var(--success) 45%, transparent);
  background: color-mix(in srgb, var(--success) 15%, transparent);
  color: var(--success-strong);
}
.status--bad {
  border-color: color-mix(in srgb, var(--danger) 45%, transparent);
  background: color-mix(in srgb, var(--danger) 15%, transparent);
  color: var(--danger-strong);
}
.row-title + .status, .row-title + .status + .status { margin-left: 0.35rem; vertical-align: 1px; }

/* -------------------------------------------------------- access matrix --- */

.toolbar {
  position: sticky;
  top: 0;
  z-index: 5;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6rem;
  padding: 0.5rem 0;
  margin-bottom: 0.5rem;
  background: var(--page);
}
.toolbar__search { max-width: 18rem; }
.toolbar__status { flex: 1; text-align: right; }
.is-dirty .toolbar__status { color: var(--warning); font-weight: 500; }

.matrix th, .matrix td { padding: 0.4rem 0.6rem; }
.matrix thead th { position: sticky; top: 0; background: var(--surface); }
.matrix__who {
  position: sticky;
  left: 0;
  z-index: 1;
  min-width: 13rem;
  background: var(--surface);
  font-weight: 600;
}
.matrix thead .matrix__who { z-index: 3; }
.matrix__who a { color: var(--ink); text-decoration: none; }
.matrix__who a:hover { color: var(--accent); text-decoration: underline; }
.matrix__sub { display: block; color: var(--muted); font-weight: 400; font-size: var(--fs-small); }
.matrix__demo {
  min-width: 6.5rem;
  text-align: center;
  vertical-align: bottom;
}
.matrix__demo a { color: var(--ink); text-decoration: none; white-space: normal; }
.matrix__demo a:hover { color: var(--accent); text-decoration: underline; }
.matrix__note { display: block; color: var(--muted); font-weight: 400; font-size: var(--fs-meta); }
.matrix__cell { text-align: center; vertical-align: middle; }
.matrix__cell input { width: 1.05rem; height: 1.05rem; min-height: 0; margin: 0; accent-color: var(--accent); cursor: pointer; }
.matrix__cell.is-changed, .check.is-changed { background: color-mix(in srgb, var(--warning) 14%, transparent); }
.matrix__group th {
  padding-top: 0.9rem;
  background: var(--surface-2);
  color: var(--muted);
  font-size: var(--fs-meta);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  text-align: left;
}
.matrix__group th > span { position: sticky; left: 0.6rem; }
.matrix__add { margin-left: 0.9rem; text-transform: none; letter-spacing: 0; font-weight: 500; font-size: var(--fs-small); }
.matrix__fixed th, .matrix__fixed td { color: var(--muted); }
.matrix__fixed .matrix__who { font-weight: 500; }
.tick { color: var(--success); font-weight: 600; }
.dash { color: var(--border); }
.matrix tr[hidden] { display: none; }

/* A phone: the bar keeps the mark, the sections and the avatar, and the grid's
 * name column gives up width to the ticks it is there to label. */
@media (max-width: 640px) {
  .bar { padding-inline: 0.75rem; gap: 0.35rem; }
  .bar__app, .account__name, .account__caret { display: none; }
  .bar__nav { margin-left: 0.25rem; }
  .bar__nav a { padding-inline: 0.45rem; }
  .matrix__who { min-width: 9.5rem; }
  .toolbar__search { max-width: none; flex: 1 1 100%; }
  .toolbar__status { text-align: left; }
}

/* ---------------------------------------------------------------- forms --- */

.form { display: grid; gap: 0.25rem; max-width: 46rem; }
.form--inline { max-width: 32rem; }
.form-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
  gap: 0 1rem;
  align-items: start;
}
.form-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 0.6rem; justify-content: flex-end; margin-top: 0.5rem; }
.form-actions .toolbar__status { text-align: left; }
.field__hint--top { margin: 0 0 0.4rem; }
.checkgrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr)); gap: 0.1rem 1rem; }
.check--field { margin-top: 1.7rem; }
.check--row {
  display: flex;
  gap: 0.5rem;
  align-items: flex-start;
  padding: 0.45rem 0.4rem;
  border-radius: var(--radius);
}
.check--row + .check--row { border-top: 1px solid color-mix(in srgb, var(--border) 55%, transparent); }
.check--row input { margin-top: 0.2rem; }
.check span .muted { font-size: var(--fs-small); }

.disclosure { margin: 0.4rem 0 0.6rem; }
.disclosure > summary { cursor: pointer; color: var(--accent); padding: 0.25rem 0; width: fit-content; }
.disclosure[open] > summary { margin-bottom: 0.6rem; }

.danger-zone {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  margin-top: 1.5rem;
  padding: 0.85rem 1rem;
  border: 1px solid color-mix(in srgb, var(--danger) 40%, var(--border));
  border-radius: var(--radius-lg);
}
.danger-zone p { margin: 0.15rem 0 0; }
.danger-zone form { margin: 0; }

.reveal { display: flex; flex-wrap: wrap; align-items: center; gap: 0.75rem; }

/*
 * The one-time code. The only place the six-size scale gives way to a figure,
 * and for the same reason the estate's scale makes that exception for a metric
 * readout: this is read aloud, retyped, and never shown again.
 */
.code {
  font-family: var(--mono);
  font-size: var(--fs-hero);
  letter-spacing: 0.06em;
  user-select: all;
  margin: 0.4rem 0;
  color: var(--ink);
}

/* --------------------------------------------------------------- chrome --- */

.scope { margin: 0.5rem 0 0; color: var(--muted); font-size: var(--fs-small); }
.muted { color: var(--muted); }
.aside { margin: 1rem 0 0; color: var(--muted); font-size: var(--fs-small); }

/* Turnstile renders into a fixed-size iframe; reserve the height so the submit
 * button does not jump when the challenge appears. */
.cf-turnstile { margin: 0 0 0.9rem; min-height: 65px; }

.skip { position: absolute; left: -9999px; }
.skip:focus {
  left: 1rem;
  top: 1rem;
  z-index: 10;
  background: var(--surface);
  padding: 0.5rem 0.8rem;
  border-radius: var(--radius);
}

/* -------------------------------------------------------- small screens --- */

/*
 * 640 only. The breakpoint ladder is a CLOSED vocabulary - 1180, 980, 900,
 * 760, 640, 480 - and a seventh value is an amendment to that table rather
 * than a per-view invention. The card grid needs no query at all, because
 * auto-fit collapses it on its own; what a phone needs is the filter taking
 * the full width under the title, and the entry panel sitting higher.
 */
@media (max-width: 640px) {
  .page-head__actions:has(.directory__filter:not([hidden])) { flex-basis: 100%; }
  .directory__filter, .directory__filter input { width: 100%; }
  main.shell--narrow { padding-top: 1rem; }
  .cta .button { width: 100%; }
}

/*
 * iOS zooms into any field under 16px and leaves the person to pinch their way
 * back out after every one. The floor is on the INPUT, and it is keyed to the
 * finger rather than the width - a tablet is 1024px wide and still a
 * touchscreen.
 */
@media (pointer: coarse) {
  input, select, textarea { font-size: 16px; }
  .button { min-height: 2.75rem; }
  .linkish { min-height: 1.5rem; }
  .check input { min-height: 1.25rem; }
}

@media (prefers-reduced-motion: reduce) {
  * { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
}
