/* Management interface stylesheet.
 *
 * Plain CSS, no build step, no framework. Layout uses flexbox and grid so the
 * interface stays usable from a tablet width upwards; wide tables scroll inside
 * their own container rather than making the page scroll sideways.
 */

:root {
  --colour-text: #16202b;
  --colour-muted: #5b6b7c;
  --colour-border: #ccd6e0;
  --colour-surface: #ffffff;
  --colour-page: #f4f6f9;
  --colour-accent: #1f4e79;
  --colour-accent-text: #ffffff;
  --colour-danger: #9c2222;
  --colour-danger-surface: #fdf0f0;
  --colour-warning-surface: #fdf6e3;
  --colour-warning-border: #d9b25a;
  --colour-ok: #1c6b3c;
  --colour-ok-surface: #e9f5ee;
  --space: 1rem;
  --radius: 4px;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--colour-page);
  color: var(--colour-text);
  font: 16px/1.5 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}

a { color: var(--colour-accent); }
a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible {
  outline: 3px solid #ffb000;
  outline-offset: 2px;
}

code { font-family: ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace; }

.skip-link {
  position: absolute;
  left: -9999px;
}
.skip-link:focus {
  left: var(--space);
  top: var(--space);
  z-index: 10;
  background: var(--colour-surface);
  padding: 0.5rem 1rem;
  border: 2px solid var(--colour-accent);
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

/* Shell: a sidebar beside the content, not a bar above it ------------------
   Eleven destinations stopped fitting one row, and the set grows with every
   slice. The grid puts the navigation to the left on a wide screen and above
   the content on a narrow one, with no scripting either way. */

.site-shell {
  display: grid;
  grid-template-columns: 14.5rem minmax(0, 1fr);
  align-items: start;
  min-height: calc(100vh - 4rem);
}
@media (max-width: 60rem) {
  .site-shell { grid-template-columns: minmax(0, 1fr); }
}

.site-header {
  background: var(--colour-accent);
  color: var(--colour-accent-text);
  padding: 0.75rem 0.75rem 1.5rem;
  height: 100%;
}
.site-title {
  margin: 0 0 1.1rem;
  padding: 0 0.5rem;
  font-weight: 600;
}

.nav-group-label {
  margin: 1.15rem 0 0.3rem;
  padding: 0 0.5rem;
  font-size: 0.85rem;
  font-weight: 600;
  opacity: 0.7;
}
.nav-group {
  margin: 0;
  padding: 0;
  list-style: none;
}
.nav-group a {
  display: block;
  padding: 0.3rem 0.5rem;
  border-radius: var(--radius);
  color: var(--colour-accent-text);
  text-decoration: none;
}
.nav-group a:hover { text-decoration: underline; }
/* The current page is marked by weight and a rule as well as by tint, so it is
   not carried by colour alone. */
.nav-group a[aria-current="page"] {
  font-weight: 600;
  background: rgba(255, 255, 255, 0.14);
  box-shadow: inset 3px 0 0 currentColor;
}
/* The two singles carry no label, so they need the space the label gave. */
.nav-single { margin-top: 1.6rem; }

.site-main {
  max-width: 76rem;
  padding: var(--space);
}

.site-footer {
  max-width: 76rem;
  margin: 0 auto;
  padding: var(--space);
  color: var(--colour-muted);
  font-size: 0.875rem;
}

/* Page structure ----------------------------------------------------------- */

.page-heading {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space);
}
h1 { font-size: 1.5rem; }
h2 { font-size: 1.125rem; }

.breadcrumb {
  font-size: 0.875rem;
  color: var(--colour-muted);
  margin-bottom: 0.5rem;
}
.breadcrumb span { margin: 0 0.35rem; }

section { margin-top: 1.5rem; }

.action-group { display: flex; flex-wrap: wrap; gap: 0.5rem; }

.muted { color: var(--colour-muted); }
.hint { color: var(--colour-muted); font-size: 0.875rem; margin: 0.25rem 0 0; }

/* Buttons ------------------------------------------------------------------ */

.button {
  display: inline-block;
  padding: 0.45rem 0.9rem;
  border: 1px solid var(--colour-border);
  border-radius: var(--radius);
  background: var(--colour-surface);
  color: var(--colour-accent);
  font: inherit;
  text-decoration: none;
  cursor: pointer;
}
.button:hover { border-color: var(--colour-accent); }
.button-primary {
  background: var(--colour-accent);
  border-color: var(--colour-accent);
  color: var(--colour-accent-text);
}
.button-danger {
  background: var(--colour-danger);
  border-color: var(--colour-danger);
  color: #fff;
}
.button-quiet { background: transparent; }
.button-disabled {
  color: var(--colour-muted);
  background: transparent;
  cursor: not-allowed;
}

/* Tables ------------------------------------------------------------------- */

.table-scroll { overflow-x: auto; margin-top: var(--space); }

table {
  width: 100%;
  border-collapse: collapse;
  background: var(--colour-surface);
  border: 1px solid var(--colour-border);
}
caption { text-align: left; }
th, td {
  padding: 0.5rem 0.75rem;
  border-bottom: 1px solid var(--colour-border);
  text-align: left;
  vertical-align: top;
}
thead th { background: #eef2f6; font-size: 0.875rem; }
tbody tr:last-child th, tbody tr:last-child td { border-bottom: none; }
.numeric { text-align: right; font-variant-numeric: tabular-nums; }

/* A short note at the head of a screen, for the sentence that is about the
   whole screen rather than about one field. */
.page-note { margin: 0 0 var(--space); max-width: 60ch; }

.record {
  display: grid;
  grid-template-columns: minmax(12rem, 18rem) 1fr;
  gap: 0.35rem 1rem;
  background: var(--colour-surface);
  border: 1px solid var(--colour-border);
  padding: var(--space);
  margin: 0;
}
.record dt { color: var(--colour-muted); }
.record dd { margin: 0; }

@media (max-width: 40rem) {
  .record { grid-template-columns: 1fr; }
  .record dd { margin-bottom: 0.5rem; }
}

/* Badges, alerts, callouts -------------------------------------------------- */

.badge {
  display: inline-block;
  padding: 0.1rem 0.5rem;
  border-radius: 999px;
  font-size: 0.8125rem;
  border: 1px solid transparent;
}
.badge-active { background: var(--colour-ok-surface); color: var(--colour-ok); border-color: var(--colour-ok); }
.badge-disabled { background: #eceff2; color: var(--colour-muted); border-color: var(--colour-border); }

.alert {
  margin: var(--space) 0;
  padding: 0.75rem var(--space);
  border: 1px solid var(--colour-border);
  border-left-width: 4px;
  border-radius: var(--radius);
  background: var(--colour-surface);
}
.alert h2 { margin-top: 0; }
.alert ul { margin: 0.5rem 0 0; padding-left: 1.25rem; }
.alert-error { border-left-color: var(--colour-danger); background: var(--colour-danger-surface); }
.alert-success { border-left-color: var(--colour-ok); background: var(--colour-ok-surface); }
.alert-warning { border-left-color: #b07d00; background: var(--colour-warning-surface); }

.callout {
  padding: 0.75rem var(--space);
  border: 1px solid var(--colour-border);
  border-radius: var(--radius);
  background: var(--colour-surface);
}
.callout-danger { border-color: var(--colour-danger); background: var(--colour-danger-surface); }
.callout h2 { margin-top: 0; }

.warnings { margin: 0; padding-left: 1.1rem; font-size: 0.875rem; }
.effects { padding-left: 1.1rem; }

.empty-state {
  margin-top: var(--space);
  padding: 2rem var(--space);
  background: var(--colour-surface);
  border: 1px dashed var(--colour-border);
  border-radius: var(--radius);
  text-align: center;
}
.empty-state h2 { margin-top: 0; }

/* Forms -------------------------------------------------------------------- */

.filters,
.record-form {
  margin-top: var(--space);
  padding: var(--space);
  background: var(--colour-surface);
  border: 1px solid var(--colour-border);
  border-radius: var(--radius);
}
.filters {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
  gap: var(--space);
  align-items: end;
}

.field { margin-bottom: var(--space); }
.filters .field { margin-bottom: 0; }
.field label { display: block; font-weight: 600; margin-bottom: 0.25rem; }

input[type="text"],
input[type="search"],
select,
textarea {
  width: 100%;
  padding: 0.45rem 0.6rem;
  border: 1px solid var(--colour-border);
  border-radius: var(--radius);
  font: inherit;
  background: #fff;
  color: inherit;
}
input[readonly] { background: #eceff2; color: var(--colour-muted); }
textarea { resize: vertical; }

.field-invalid input,
.field-invalid select,
.field-invalid textarea { border-color: var(--colour-danger); }
.field-error { color: var(--colour-danger); font-size: 0.875rem; margin: 0.25rem 0 0; }
.required { color: var(--colour-danger); }

.field-check { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.5rem; margin-bottom: 0.5rem; }
.field-check label { font-weight: 400; margin: 0; }
.field-check input { width: auto; }
.field-check .hint { flex-basis: 100%; }

fieldset {
  border: 1px solid var(--colour-border);
  border-radius: var(--radius);
  padding: 0.75rem;
}
legend { font-weight: 600; padding: 0 0.35rem; }

.field-actions,
.form-actions { display: flex; flex-wrap: wrap; gap: 0.5rem; }

.pagination {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space);
  margin-top: var(--space);
}

/* Relationship summary --------------------------------------------------- */
/* One line per relationship: what it is, what is there now, and the ways in.
   The layout is the same for every kind; the VERB is what tells the operator
   whether they are about to claim a number, attach an edge or edit a field. */
.relations {
  border: 1px solid var(--colour-border);
  border-radius: var(--radius);
  margin-bottom: var(--space);
}
.relation {
  display: grid;
  grid-template-columns: 11rem minmax(0, 1fr) auto;
  gap: 0.75rem;
  align-items: baseline;
  padding: 0.55rem 0.75rem;
  border-bottom: 1px solid var(--colour-border);
}
.relation:last-child { border-bottom: 0; }
.relation-what { font-weight: 600; }
.relation-do { display: flex; gap: 0.4rem; flex-wrap: wrap; }
@media (max-width: 46rem) {
  .relation { grid-template-columns: minmax(0, 1fr); }
}

/* Tabs, with no scripting: a radio holds the state and its label is the tab. */
.tabs { margin-top: var(--space); }
.tabs > input { position: absolute; opacity: 0; pointer-events: none; }
.tabstrip {
  display: flex;
  gap: 0.25rem;
  border-bottom: 1px solid var(--colour-border);
}
.tabstrip label {
  padding: 0.4rem 0.85rem;
  border: 1px solid transparent;
  border-bottom: 0;
  border-radius: var(--radius) var(--radius) 0 0;
  cursor: pointer;
  color: var(--colour-muted);
}
.tabs > input:focus-visible + label { outline: 3px solid #ffb000; outline-offset: 2px; }
.tabpanel { display: none; padding: 0.9rem 0.2rem 0; }
#tab-overview:checked ~ .tabstrip label[for="tab-overview"],
#tab-settings:checked ~ .tabstrip label[for="tab-settings"] {
  background: var(--colour-surface);
  border-color: var(--colour-border);
  color: var(--colour-text);
  font-weight: 600;
  margin-bottom: -1px;
}
#tab-overview:checked ~ #panel-overview,
#tab-settings:checked ~ #panel-settings { display: block; }

/* Panels ------------------------------------------------------------------ */
/* The dialog look without the stacking. A document has one fragment, so at most
   one element can match :target -- an error inside an error inside a third
   window cannot be built out of this even by mistake. */
.panel {
  position: fixed;
  inset: 0;
  display: none;
  place-items: center;
  padding: var(--space);
  background: rgba(12, 22, 32, 0.55);
  z-index: 20;
}
.panel:target { display: grid; }
.panel-card {
  background: var(--colour-surface);
  border: 1px solid var(--colour-border);
  border-radius: var(--radius);
  width: min(34rem, 100%);
  max-height: 85vh;
  overflow: auto;
}
.panel-head,
.panel-foot {
  display: flex;
  align-items: baseline;
  gap: var(--space);
  padding: 0.75rem var(--space);
}
.panel-head { justify-content: space-between; border-bottom: 1px solid var(--colour-border); }
.panel-head h2 { margin: 0; font-size: 1.1rem; }
.panel-foot { justify-content: flex-end; border-top: 1px solid var(--colour-border); }
.panel-body { padding: 0.9rem var(--space); }

/* Saved-versus-live strip -------------------------------------------------
   It appears only when the saved configuration and the live generation differ,
   or when that could not be determined. An absent strip therefore means one
   thing and one thing only: what you are reading is what the switch is
   running. */
.livebar {
  margin: 0 0 var(--space);
  padding: 0.6rem 0.75rem;
  border: 1px solid var(--colour-warning-border);
  border-left-width: 4px;
  border-radius: var(--radius);
  background: var(--colour-warning-surface, #fdf5e3);
  color: var(--colour-text);
}
/* "Could not be read" is not the same as "out of date", and it must not borrow
   the colour that means it. */
.livebar-unknown {
  border-color: var(--colour-border);
  background: var(--colour-surface);
}

/* A panel the server forced open.
   With :target alone, a POST that fails validation returns to a page with the
   panel closed, and to the operator that looks like the action vanished rather
   than like it was refused. The server says which panel to reopen, and this is
   how it says it. */
.panel-open { display: grid; }

/* Authentication surface -----------------------------------------------
   Signing in and changing one's own password render a bare, centred card,
   not the shell: the sidebar links to screens a signed-out visitor cannot
   use, and /signin is reached while signed out. */
.auth-page {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space);
}
.auth-card {
  width: 100%;
  max-width: 22rem;
  background: var(--colour-surface);
  border: 1px solid var(--colour-border);
  border-radius: var(--radius);
  padding: 1.5rem var(--space);
}
.auth-card h1 { margin-top: 0; }
.auth-card .field:last-of-type { margin-bottom: 1.25rem; }

/* A help disclosure: the explanation a field needs the first few times and
   never again. It is `<details>` and nothing else -- no scripting, because the
   gate asserts on rendered HTML, and no `title=` tooltip, which never appears on
   touch, cannot be reached by keyboard, and is read inconsistently aloud.

   There is deliberately no `!` counterpart. A marker that folds a warning away
   would make the portal look tidier and operate more dangerously in the same
   edit; consequences stay on the screen, uncollapsed. */
.help {
  display: inline-block;
  margin-top: 0.25rem;
}

.help > summary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.35rem;
  height: 1.35rem;
  border: 1px solid var(--border, #c7ccd4);
  border-radius: 50%;
  color: var(--muted, #5b6270);
  font-size: 0.8rem;
  line-height: 1;
  cursor: pointer;
  list-style: none;
  user-select: none;
}

.help > summary::-webkit-details-marker {
  display: none;
}

.help > summary:hover {
  border-color: var(--accent, #3b6ea5);
  color: var(--accent, #3b6ea5);
}

.help[open] > summary {
  margin-bottom: 0.35rem;
}

.help .help-body {
  max-width: 62ch;
  margin: 0;
  padding: 0.5rem 0.75rem;
  border-left: 2px solid var(--border, #c7ccd4);
  color: var(--muted, #5b6270);
  font-size: 0.9rem;
}

/* A picker's search box, under the select it narrows. It is a separate GET form
   attached by form=, so it sits beside the field it belongs to without nesting
   one form inside another. */
.picker-search {
  display: flex;
  gap: 0.5rem;
  align-items: center;
  margin-top: 0.4rem;
}

.picker-search input[type="search"] {
  flex: 1 1 18rem;
  min-width: 0;
}


/* ---------------------------------------------------------------------------
   Compact record layout

   The portal's screens read like prose because every control owns a full row and
   its label owns the row above it: `.field label` is a block element, so thirteen
   fields on the Gateway form cost twenty-six lines before any help or error.

   The label moves beside the control, and fields share rows. This is opt-in --
   `.record-form` is used on 25 screens including confirmations, reorder screens
   and the operator password form, and none of those asked for a grid.

   Grid lays out direct children only. A form whose fields sit inside a
   `<fieldset>` needs the class on the fieldset too, or the outer grid arranges
   the fieldsets and nothing inside them.
   --------------------------------------------------------------------------- */
.record-form-compact,
.fieldset-compact {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(22rem, 1fr));
  gap: 0.35rem 1.75rem;
  align-items: start;
}

/* 22rem rather than something narrower on purpose. Measured at 1440px, dropping
   the minimum to 17rem changed the rendered height of every screen by exactly
   zero: a field carrying an obligation takes the whole row, and those rows break
   the runs of plain fields, so extra columns have nothing to fill them with.
   The wider minimum keeps label and control from being cramped and costs
   nothing. */

/* A field is one atomic grid item: its label, control, help, error and any
   obligation travel together. Splitting them across cells is how a warning ends
   up beside a control it does not describe. */
.record-form-compact > .field,
.fieldset-compact > .field {
  display: grid;
  grid-template-columns: minmax(7rem, 11rem) minmax(0, 1fr);
  gap: 0.15rem 0.75rem;
  align-items: baseline;
  margin-bottom: 0.35rem;
}

.record-form-compact > .field > label,
.fieldset-compact > .field > label {
  margin-bottom: 0;
  text-align: left;
}

/* Everything after the label sits in the value column, under the control. */
.record-form-compact > .field > :not(label),
.fieldset-compact > .field > :not(label) {
  grid-column: 2;
}

/* A field carrying a safety obligation takes the whole row and shares it with
   nothing. A full-width warning printed under a row of two controls can still be
   read as belonging to either one; being alone on the row is what removes the
   ambiguity. */
.record-form-compact > .field-has-obligation,
.fieldset-compact > .field-has-obligation {
  grid-column: 1 / -1;
}

/* Width follows the value, not the column: a port is not as wide as an FQDN.
   Note that number inputs were never full-width to begin with. */
.record-form-compact .field-narrow input,
.record-form-compact .field-narrow select,
.fieldset-compact .field-narrow input,
.fieldset-compact .field-narrow select { max-width: 9rem; }

.record-form-compact .field-medium input,
.record-form-compact .field-medium select,
.fieldset-compact .field-medium input,
.fieldset-compact .field-medium select { max-width: 20rem; }

/* Textareas, action rows, search rows and anything explicitly wide span. */
.record-form-compact > .field-wide,
.record-form-compact > .form-actions,
.record-form-compact > fieldset,
.fieldset-compact > .field-wide { grid-column: 1 / -1; }

/* Detail screens: a grid of pairs rather than a grid of labels and values.
   Each pair is wrapped so it is one grid item -- a bare grid over alternating
   dt and dd lets a label in one column be read against a value from another. */
.record-pairs {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
  gap: 0.6rem 1.75rem;
  align-items: start;
}

.record-pairs > .record-pair { display: block; }
.record-pairs > .record-pair > dt { color: var(--colour-muted); margin-bottom: 0.1rem; }
.record-pairs > .record-pair > dd { margin: 0; }
.record-pairs > .record-pair-wide { grid-column: 1 / -1; }

/* Below the point where the content column is genuinely narrow, everything
   returns to one per row. The sidebar holds 14.5rem until 60rem, so this is
   measured on the content column rather than the viewport alone. */
@media (max-width: 75rem) {
  .record-form-compact,
  .fieldset-compact,
  .record-pairs { grid-template-columns: 1fr; }
}

@media (max-width: 40rem) {
  .record-form-compact > .field,
  .fieldset-compact > .field { grid-template-columns: 1fr; }

  .record-form-compact > .field > :not(label),
  .fieldset-compact > .field > :not(label) { grid-column: 1; }
}

/* ---------------------------------------------------------------------------
   Configuration form track system (docs/development/portal-form-layout-spec.md)

   A second, opt-in layout for screens where something is being edited: the
   label sits above its control rather than beside it, and a field spans the
   number of columns its longest real value needs. Detail and summary screens
   keep `.record` / `.record-pairs` above; this is a distinct system, not a
   variant of `.record-form-compact`, and touches nothing that class defines.

   Everything is scoped under `.config-form` / `.config-*`, per the layout
   contract's §3: the stylesheet backs 25 other screens and none of them opt
   into a twelve-column grid.
   --------------------------------------------------------------------------- */
/* The card chrome comes from `.record-form`, which this class is used beside
   rather than instead of. Duplicating the surface, border, radius and padding
   here dropped `.record-form` from the element, and `cmd/layout-measure` waits
   for `form.record-form` -- so the screen the change exists to shorten became
   the one screen that could no longer be measured. */
.config-form {
  container-type: inline-size;
  container-name: config-form;
  /* A configuration form is read, not filled edge to edge. Without a cap the
     grid divides whatever the window happens to be: measured at a 1440px
     viewport the endpoint, media and direction selects each rendered 1127px
     wide, and the host input 554px, for values that need a fraction of that.
     Twelve tracks of a capped width is what makes a span mean a real size. */
  max-width: 62rem;
}

/* Each group is a real fieldset and its own twelve-column grid, so its fields
   line up on the same tracks and a group holding one full-width field is
   simply a grid with one row. A nested grid per fieldset, rather than one grid
   over the whole form with subgrid, is what keeps this working with no
   assumption about subgrid support. */
.config-fieldset {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  gap: 0 1.25rem;
  /* No border, background, radius or shadow (spec §7): the plain `fieldset`
     rule earlier in this file adds all four, so they are removed here,
     scoped to this form only. */
  border: 0;
  border-radius: 0;
  background: none;
  padding: 0;
  margin: 0 0 1rem;
}
.config-fieldset:last-of-type { margin-bottom: 0; }

/* The hairline is the only separation between groups: a rule under the
   legend, not a border around the group. */
/* Two implicit row tracks per row of fields: the label track and the control
   track, which `.config-field`'s subgrid then adopts. */
.config-fieldset { grid-auto-rows: auto; }

/* A form with one group has no grouping to show. `<fieldset><legend>Account</legend>`
   on a page already headed "Create account" is a label repeating its own page,
   and it cost 40px doing it, so a short form carries the grid itself and skips
   both. Grouping stays where it is real -- the Gateway form's four sections. */
.config-form-flat {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  gap: 0 1.25rem;
  grid-auto-rows: auto;
}

/* The action row is a child of this grid too, and without a span it took one
   of the twelve columns and wrapped its buttons -- which is why removing the
   fieldset made the form taller rather than shorter. */
.config-form-flat > .form-actions { grid-column: 1 / -1; }

.config-fieldset > legend {
  grid-column: 1 / -1;
  width: 100%;
  padding: 0 0 0.3rem;
  margin-bottom: 0.6rem;
  border-bottom: 1px solid var(--colour-border);
  font-weight: 600;
}

/* Type scale for the configuration forms (spec §3), which had never been
   applied: every element was inheriting the portal's 16px body size, so a form
   built to be dense was set at reading size. The owner saw it before it was
   measured -- "có vẻ do chữ to quá chăng". Scoped here and nowhere else: the
   lists, confirmations and detail screens are read rather than operated and
   keep 16px. */
.config-form {
  --config-label:   0.8125rem;  /* 13px */
  --config-control: 0.875rem;   /* 14px */
  --config-legend:  0.875rem;   /* 14px */
  --config-error:   0.75rem;    /* 12px */
  --config-control-height: 2.25rem; /* 36px */
}

.config-fieldset > legend { font-size: var(--config-legend); }
.config-field > label,
.config-field-check > label { font-size: var(--config-label); }
.config-field > :is(input, select, textarea),
.config-field-check > input { font-size: var(--config-control); }
.config-field > :is(input, select) { height: var(--config-control-height); }
.config-field > .field-error { font-size: var(--config-error); }
.config-form .form-actions .button { font-size: var(--config-control); }

/* A field is one grid item: a label area and a control line. The label area is
   two lines tall for every field, whether or not this label needs both.
   Truncating instead was measured to be wrong -- `Source port (blank = any)`,
   `Failover priority (lower first)` and `Weight (not used yet)` all rendered
   with an ellipsis, so the consequence just moved out of a red panel and into
   the label became the part the ellipsis ate. A fixed area lets a long label
   wrap without its control dropping below its neighbours'. */
.config-field {
  display: grid;
  /* `subgrid` is what makes a two-line label cost nothing on the fields beside
     it: every field in a row shares the row's label track, so the track is as
     tall as the tallest label there and every control still starts on one
     line. Reserving two lines on each label instead was measured and rejected
     -- it grew the account form from 297px to 447px, paying on every field for
     what two of them needed. Where subgrid is unsupported the rows fall back to
     `auto`: labels of different heights then offset their own controls, which
     is untidy but not broken. */
  grid-template-rows: subgrid;
  grid-row: span 2;
  gap: 0.15rem;
  min-width: 0;
  margin: 0 0 0.55rem;
}

.config-field > label {
  display: block;
  font-weight: 600;
  line-height: 1.25;
  align-self: end;
  overflow-wrap: break-word;
}

.config-field > input,
.config-field > select,
.config-field > textarea {
  width: 100%;
  box-sizing: border-box;
}

/* The error slot: always in the DOM (spec §4), so `aria-describedby` always
   resolves. Nothing here reserves height for it -- an empty paragraph has no
   text and no line box, so a clean render costs nothing and an invalid one
   grows by exactly its own message. */
.config-field > .field-error { margin: 0.2rem 0 0; }

/* The slot is always in the HTML so `aria-describedby` always resolves, but it
   must cost nothing when it is empty. Without this each of the fourteen empty
   paragraphs still contributed its top margin, which is reserving space for an
   error by accident -- the thing this layout deliberately does not do. */
.config-field > .field-error:empty { display: none; }

/* A checkbox reads on one line: the box beside its label, not stacked above
   it the way every other control is. */
.config-field-check {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  margin: 0 0 0.55rem;
}
.config-field-check > label { font-weight: 400; margin: 0; }
.config-field-check > input { width: auto; }

/* Monospace only on values a machine parses -- host, socket, port and other
   numeric fields (spec §2). Names and descriptions stay in the text face; it
   is a signal, not decoration. */
.config-value-mono {
  font-family: ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace;
  font-variant-numeric: tabular-nums;
}

/* A control never grows past the value it holds, even when its cell is wider.
   The span decides where a field sits; this decides how wide the control is.
   Without it the grid stretched every control to its cell and produced the
   1127px selects. */
.config-field[data-size="short"] > :is(input, select) { max-width: 9rem; }
.config-field[data-size="medium"] > :is(input, select) { max-width: 18rem; }
.config-field[data-size="long"] > :is(input, select) { max-width: 100%; }

/* A radio group is a field whose control is several controls. It keeps its own
   fieldset and legend for the grouping semantics a radio set needs, and takes
   its place on the tracks like any other field. */
.config-choices {
  border: 0;
  border-radius: 0;
  background: none;
  padding: 0;
  margin: 0 0 0.55rem;
  min-width: 0;
}
.config-choices > legend {
  padding: 0;
  margin-bottom: 0.25rem;
  font-size: var(--config-label);
  font-weight: 600;
}
.config-choices .choice { display: flex; align-items: baseline; gap: 0.4rem; }
.config-choices .choice + .choice { margin-top: 0.15rem; }
.config-choices .choice label { font-size: var(--config-control); font-weight: 400; }
.config-choices > .field-error { font-size: var(--config-error); margin: 0.2rem 0 0; }
.config-choices > .field-error:empty { display: none; }

/* The rule matrix spans the tracks like a field, and its own rules are earlier
   in this file. */
.config-form .rule-matrix { margin: 0 0 0.7rem; }

/* Span classes: a field occupies the number of columns its longest real
   value needs, not how important it feels (spec §2's table). */
.config-span-2  { grid-column: span 2; }
.config-span-3  { grid-column: span 3; }
.config-span-4  { grid-column: span 4; }
.config-span-6  { grid-column: span 6; }
.config-span-8  { grid-column: span 8; }
.config-span-12 { grid-column: span 12; }

/* Below a form width of 50rem the tracks are not a wide-screen affordance
   any more: everything collapses to one column (spec §2). This reads the
   form's own rendered width via a container query rather than the viewport,
   because the sidebar shell means the two are not the same number. */
@container config-form (max-width: 50rem) {
  .config-fieldset,
  .config-form-flat { grid-template-columns: 1fr; }
  .config-span-2,
  .config-span-3,
  .config-span-4,
  .config-span-6,
  .config-span-8,
  .config-span-12 { grid-column: 1 / -1; }
}


/* The rule matrix: the one table in the portal's forms, because Caller and
   Callee genuinely carry the same two attributes. Elsewhere a form is one
   record's attributes and a table would assert a relationship that is not
   there. */
.rule-matrix {
  border-collapse: collapse;
  width: 100%;
  margin: 0.5rem 0 1rem;
}

.rule-matrix > caption {
  text-align: left;
  color: var(--colour-muted);
  padding-bottom: 0.4rem;
}

.rule-matrix th,
.rule-matrix td {
  text-align: left;
  vertical-align: top;
  padding: 0.35rem 0.75rem 0.35rem 0;
}

.rule-matrix thead th { font-weight: 600; }
.rule-matrix tbody th { font-weight: 600; white-space: nowrap; padding-top: 0.75rem; }
.rule-matrix td input { width: 100%; }
.rule-matrix .cell-invalid input { border-color: var(--colour-danger); }

@media (max-width: 55rem) {
  /* Below this the two columns cannot both hold a usable field, so each cell
     becomes its own row and carries the header word it lost. */
  .rule-matrix, .rule-matrix tbody, .rule-matrix tr, .rule-matrix td { display: block; width: 100%; }
  .rule-matrix thead { display: none; }
  .rule-matrix tbody th { display: block; padding-top: 1rem; }
  .rule-matrix td::before { content: attr(data-column); display: block; color: var(--colour-muted); font-size: 0.9rem; }
}
