/* --------------------------------------------------------------------------------
   Night Shift product layer.

   Anything genuinely specific to this app lives here as --ns-* / .ns-*. Overriding a
   --gm-* value in place would create invisible drift across products, so we don't
   (SPEC §6.2).

   Phone first throughout: the most important screen is not a desk, it is a phone at
   03:00 — one-handed, no zoom, and never a horizontally scrolling page.
   -------------------------------------------------------------------------------- */

:root {
  --ns-max-width: 68rem;
  --ns-row-gap: var(--gm-space-3);
}

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

body {
  margin: 0;
  background: var(--gm-bg-base);
  color: var(--gm-text-primary);
  font-family: var(--gm-font-sans, Inter, system-ui, sans-serif);
  font-size: 16px;
  line-height: 1.5;
  -webkit-text-size-adjust: 100%;
}

/* Sticky footer: the shell is at least as tall as the viewport and lays out as a column, so
   the footer sits on the window's bottom edge on a short page and after the content on a long
   one. dvh first, because on mobile 100vh includes the browser chrome and would push the
   footer just out of view. */
.ns-shell {
  max-width: var(--ns-max-width); margin: 0 auto; padding: var(--gm-space-4);
  min-height: 100vh;
  min-height: 100dvh;
  display: flex; flex-direction: column;
}
/* Grows to fill whatever the header and footer leave, and is itself a column so a page can
   centre its content vertically (the login screen does). */
#main { flex: 1 0 auto; display: flex; flex-direction: column; }

/* ---- Header ---------------------------------------------------------------- */

/* One optical row. Baseline alignment was the bug: a padded button and bare text links have
   different box heights, so their baselines land at different heights and the links appear to
   float above the button. Everything in the bar is centred on a shared axis instead, and the
   items that are boxes (links, button) get the same height so their edges line up too. */
.ns-header {
  display: flex; flex-wrap: wrap; gap: var(--gm-space-3) var(--gm-space-5);
  align-items: center; justify-content: space-between;
  padding: var(--gm-space-3) 0; border-bottom: 1px solid var(--gm-border-subtle);
}
.ns-header__brand {
  /* The glow below is absolutely positioned against this box; without `relative` it resolves
     against an ancestor and lands nowhere near the moon. */
  position: relative;
  display: inline-flex; align-items: center; gap: var(--gm-space-2);
  min-height: 44px;
  font-weight: 650; letter-spacing: -0.01em; text-decoration: none; color: inherit;
}
/* The crescent's visual mass sits low-left inside its box, so a hair of optical nudging aligns
   it with the cap height of the wordmark instead of its bounding box. */
.ns-moon { flex: none; color: var(--gm-accent); translate: 0 -1px; }

/* Navigation. Real space between the items: they were one padding-width apart, which reads as one
   long word — the first thing anyone needs from a nav bar is where one label ends and the next
   begins. Each link is a 44px-high box, so the row has one shared centre line and every link is a
   proper touch target rather than a bare word. */
.ns-header__nav { display: flex; align-items: center; gap: var(--gm-space-6); flex-wrap: wrap; }
.ns-header__nav a {
  display: inline-flex; align-items: center; min-height: 44px;
  color: var(--gm-text-secondary); text-decoration: none; border-radius: var(--gm-radius-sm, 4px);
}
.ns-header__nav a:hover { color: var(--gm-text-primary); }
.ns-header__nav a[aria-current="page"] { color: var(--gm-text-primary); font-weight: 600; }

/* Home link hover: the moon comes up. Dim green at rest, full moonlight on hover.
   The glow is a round pseudo-element behind the icon, NOT a filter on the SVG: stacked
   drop-shadows on an inline SVG rendered as a hard-edged rectangle the size of the element box,
   which looked like a yellow background rather than moonlight. A radial gradient is round by
   construction and cannot be clipped to a box. */
:root { --ns-moonlight: #ffd45c; }
.ns-header__brand::before {
  content: "";
  position: absolute; left: -12px; top: 50%; translate: 0 -50%;
  width: 44px; height: 44px; border-radius: 50%;
  /* Long, gentle falloff: a short one leaves a visible circle edge, which reads as a disc rather
     than light. */
  background: radial-gradient(circle,
    rgba(255, 216, 110, 0.80) 0%,
    rgba(255, 202, 60, 0.46) 26%,
    rgba(255, 186, 20, 0.22) 44%,
    rgba(255, 176, 0, 0.08) 62%,
    rgba(255, 176, 0, 0.02) 78%,
    transparent 100%);
  /* A round box-shadow on top of the gradient carries the bloom further out than a gradient can
     without banding — and unlike a filter it cannot be clipped to a rectangle. */
  box-shadow: 0 0 26px 6px rgba(255, 198, 48, 0.22);
  /* Behind the icon, above the header background. */
  z-index: -1;
  opacity: 0; pointer-events: none;
  transition: opacity var(--gm-duration-base) var(--gm-ease-out);
}
.ns-header__brand:hover::before,
.ns-header__brand:focus-visible::before { opacity: 1; }
.ns-header__brand .ns-moon {
  transition: color var(--gm-duration-base) var(--gm-ease-out);
}
.ns-header__brand:hover .ns-moon,
.ns-header__brand:focus-visible .ns-moon { color: var(--ns-moonlight); }
/* The wordmark warms with it, so the glow reads as one gesture instead of a loose icon. */
.ns-header__brand span { transition: color var(--gm-duration-base) var(--gm-ease-out); }
.ns-header__brand:hover span,
.ns-header__brand:focus-visible span { color: var(--ns-moonlight); }
@media (prefers-reduced-motion: reduce) {
  /* Keep the state change — it carries focus information — but drop the fade. */
  .ns-header__brand::before, .ns-header__brand .ns-moon, .ns-header__brand span { transition: none; }
}

/* ---- Filters ----------------------------------------------------------------- */

.ns-filters { display: flex; gap: var(--gm-space-2); flex-wrap: wrap; margin: var(--gm-space-4) 0; }
.ns-filter {
  display: inline-flex; gap: var(--gm-space-2); align-items: center;
  padding: var(--gm-space-2) var(--gm-space-3);
  border: 1px solid var(--gm-border-subtle); border-radius: var(--gm-radius-full, 999px);
  color: var(--gm-text-secondary); text-decoration: none; font-size: 0.875rem;
  min-height: 44px; /* thumb target, web scope minimum */
}
.ns-filter:hover { color: var(--gm-text-primary); }
.ns-filter[aria-current="page"] {
  color: var(--gm-text-primary); border-color: var(--gm-accent); font-weight: 600;
}
/* An exhausted alert must never be something you have to go looking for. */
.ns-filter--exhausted[data-nonzero="true"] {
  border-color: var(--ns-severity-critical); color: var(--gm-text-primary);
}
.ns-filter__count { font-variant-numeric: tabular-nums; opacity: 0.8; }

/* ---- Alert list -------------------------------------------------------------- */

.ns-list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--ns-row-gap); }
.ns-row {
  display: grid; gap: var(--gm-space-2) var(--gm-space-3);
  grid-template-columns: auto 1fr auto;
  /* Centre, not start: a badge and a line of text have different heights, and aligning their tops
     leaves the badge visibly high. */
  align-items: center;
  padding: var(--gm-space-3);
  border: 1px solid var(--gm-border-subtle); border-radius: var(--gm-radius-md, 8px);
  background: var(--gm-bg-elevated, transparent);
}
.ns-row:hover { border-color: var(--gm-accent); }
.ns-row__link { color: inherit; text-decoration: none; font-weight: 600; }
.ns-row__link:hover { text-decoration: underline; }
.ns-row__meta {
  /* Starts under the title, not under the severity badge — the meta belongs to the title. */
  grid-column: 2 / -1; display: flex; flex-wrap: wrap; gap: var(--gm-space-3);
  color: var(--gm-text-secondary); font-size: 0.8125rem;
}
.ns-row__meta time { font-variant-numeric: tabular-nums; }

/* Severity and status are NEVER colour alone — the badge always carries text (SPEC §6.2).
   Colour is the redundant cue, not the message. */
.ns-badge {
  display: inline-flex; align-items: center; gap: var(--gm-space-1);
  padding: 2px var(--gm-space-2); border-radius: var(--gm-radius-sm, 4px);
  font-size: 0.6875rem; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase;
  border: 1px solid currentColor; white-space: nowrap;
}
:root {
  --ns-severity-critical: #c0392b;
  --ns-severity-error: #d35400;
  --ns-severity-warning: #b7950b;
  --ns-severity-info: #2874a6;
}
.ns-severity--critical { color: var(--ns-severity-critical); }
.ns-severity--error    { color: var(--ns-severity-error); }
.ns-severity--warning  { color: var(--ns-severity-warning); }
.ns-severity--info     { color: var(--ns-severity-info); }

/* ---- Alert detail ------------------------------------------------------------ */

/* A quiet kicker line above the title: severity and status as badges, the team as plain text, with
   room before the headline instead of crowding it. */
.ns-detail__kicker {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--gm-space-2);
  margin: var(--gm-space-5) 0 var(--gm-space-2);
  color: var(--gm-text-secondary); font-size: 0.875rem;
}
.ns-detail__title { font-size: 1.5rem; line-height: 1.25; margin: 0 0 var(--gm-space-4); }
.ns-detail__actions { display: flex; flex-wrap: wrap; gap: var(--gm-space-2); margin: var(--gm-space-4) 0; }
.ns-detail__actions form { margin: 0; }

/* Wide content scrolls inside its own container — the page never scrolls sideways. */
.ns-scroll-x { overflow-x: auto; }
.ns-table { width: 100%; border-collapse: collapse; font-size: 0.875rem; }
.ns-table th, .ns-table td {
  text-align: left; padding: var(--gm-space-2) var(--gm-space-3);
  border-bottom: 1px solid var(--gm-border-subtle); white-space: nowrap;
  vertical-align: middle; /* rows carry controls of different heights */
}
.ns-table th {
  font-size: 0.6875rem; font-weight: 700; letter-spacing: 0.05em; text-transform: uppercase;
  color: var(--gm-text-secondary); padding-bottom: var(--gm-space-2);
}
.ns-table tbody tr:last-child td { border-bottom: 0; }
/* First and last cells align with the section edge instead of floating inside padding. */
.ns-table th:first-child, .ns-table td:first-child { padding-left: 0; }
.ns-table th:last-child, .ns-table td:last-child { padding-right: 0; }
/* The action column hugs the right edge and shrinks to its content, so the columns that carry
   information get the width. */
.ns-table td:last-child.ns-cell-actions,
.ns-table th:last-child.ns-cell-actions { width: 1%; text-align: right; }
/* Numbers align on their digits and use tabular figures, so a column of them can be compared at a
   glance rather than read. */
.ns-num { font-variant-numeric: tabular-nums; }
/* Written with the table in the selector because `.ns-table th` would otherwise win on specificity
   and keep everything left-aligned. */
.ns-table th.ns-num, .ns-table td.ns-num { text-align: right; padding-right: var(--gm-space-5); }

.ns-timeline { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--gm-space-2); }
.ns-timeline li {
  display: grid; grid-template-columns: 10rem 1fr; gap: var(--gm-space-3);
  padding-bottom: var(--gm-space-2); border-bottom: 1px solid var(--gm-border-subtle);
  font-size: 0.875rem;
}
.ns-timeline time { color: var(--gm-text-secondary); font-variant-numeric: tabular-nums; }

/* ---- Language switch ---------------------------------------------------------
   Two links rather than a dropdown: with two languages a select costs a click and hides the current
   state. Sits in the footer because it is a preference, not navigation. */

.ns-lang { display: flex; align-items: center; gap: var(--gm-space-1); }
.ns-lang__form { margin: 0; }
/* Both states share one box, or the active language sits a couple of pixels above the other and
   the pair reads as broken rather than as a choice. */
.ns-lang__current, .gm .ns-lang__link {
  display: inline-flex; align-items: center; justify-content: center;
  height: 32px; min-height: 32px; padding: 0 var(--gm-space-2); margin: 0;
  font-family: inherit; font-size: 0.75rem; font-weight: 600; letter-spacing: 0.04em; line-height: 1;
  border: 1px solid transparent; background: transparent; border-radius: var(--gm-radius-sm, 4px);
}
.ns-lang__current { border-color: var(--gm-border-subtle); }
.ns-lang__current { color: var(--gm-text-primary); }
.gm .ns-lang__link { color: var(--gm-text-secondary); cursor: pointer; }
.gm .ns-lang__link:hover { color: var(--gm-accent); }

/* ---- Hover never moves anything ---------------------------------------------
   Not implemented here, and deliberately so: the `transform: translateY(-1px)` hover lifts were
   REMOVED from the vendored design-system CSS instead of being overridden
   (app/assets/stylesheets/gm/components/buttons.css). An override would have meant fighting the
   system's specificity forever and re-losing that fight with every new variant. spec/vendored_css_spec.rb
   fails if a re-copy from the style guide brings them back. */

/* ---- Login ------------------------------------------------------------------
   One action on the whole page, so it is centred in the viewport instead of stuck
   under the top edge. */

.ns-login {
  /* Fills <main>, then centres inside it — so "centred" stays centred whatever the window height. */
  position: relative;
  flex: 1 0 auto;
  display: flex; flex-direction: column; justify-content: center; align-items: center;
  gap: var(--gm-space-5); text-align: center;
}
.ns-login__title {
  display: flex; align-items: center; gap: var(--gm-space-3);
  margin: 0; font-size: 2rem; letter-spacing: -0.02em;
}
/* The sky sits behind the panel; the content keeps its own layer so the form is never under a
   canvas. pointer-events: none, so the one control on this page stays clickable. */
.ns-login__sky { position: absolute; inset: 0; pointer-events: none; z-index: 0; }
.ns-login > :not(.ns-login__sky) { position: relative; z-index: 1; }

.ns-login__form { display: grid; gap: var(--gm-space-3); justify-items: center; }
.ns-login__button { min-width: 18rem; min-height: 52px; }
/* Checked by default, so the row reads as a statement rather than a question. */
.ns-login__remember {
  display: inline-flex; align-items: center; gap: var(--gm-space-2);
  color: var(--gm-text-secondary); font-size: 0.875rem; cursor: pointer;
}
.ns-login__remember input { accent-color: var(--gm-accent-fill, var(--gm-accent)); }

@media (max-width: 40rem) {
  .ns-login__title { font-size: 1.625rem; }
  .ns-login__button { min-width: 0; width: 100%; }
}

/* ---- Ack page (the 03:00 screen) ------------------------------------------- */

.ns-ack { max-width: 34rem; margin: 0 auto; padding: var(--gm-space-5) var(--gm-space-4); }
.ns-ack__severity { font-size: 0.75rem; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; }
.ns-ack__title { font-size: 1.5rem; line-height: 1.25; margin: var(--gm-space-3) 0 var(--gm-space-4); }
/* One label column for every fact list in the app (--ns-label-w), so labels and values line up
   down the page instead of shifting per section. */
:root { --ns-label-w: 11rem; }
.ns-ack__meta, .ns-facts {
  display: grid; grid-template-columns: var(--ns-label-w) 1fr;
  gap: var(--gm-space-2) var(--gm-space-4);
  font-size: 0.9375rem; margin: 0 0 var(--gm-space-4);
}
.ns-ack__meta dt, .ns-facts dt { color: var(--gm-text-secondary); }
.ns-ack__meta dd, .ns-facts dd {
  margin: 0; display: flex; align-items: center; gap: var(--gm-space-2); flex-wrap: wrap;
}
/* Thumb-sized, full width, hard to miss half-awake. */
.ns-ack__button, .ns-ack__button button { width: 100%; min-height: 56px; font-size: 1.0625rem; }
.ns-ack__hint, .ns-ack__done { color: var(--gm-text-secondary); font-size: 0.875rem; }

/* ---- Page head --------------------------------------------------------------
   Title and its actions on one line, so the actions read as belonging to the title rather than
   floating in the gap below it. */

.ns-page-head {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: var(--gm-space-3);
  margin: var(--gm-space-5) 0 var(--gm-space-6);
}
.ns-page-head h1 { margin: 0; font-size: 1.5rem; letter-spacing: -0.01em; }
.ns-page-head { margin-bottom: var(--gm-space-5); }

/* ---- Groups on a configuration page ------------------------------------------
   Three groups of unequal importance, so they get unequal weight. The old page was four sections
   under four identical uppercase eyebrows, which made the escalation ladder look exactly like the
   ingest-token inventory. Air before a heading is what marks a boundary; a rule is only needed
   where the content changes kind. */

.ns-group { margin: 0 0 var(--gm-space-12); }
.ns-group:last-of-type { margin-bottom: 0; }
.ns-group__heading {
  margin: 0 0 var(--gm-space-4);
  font-size: 1.0625rem; letter-spacing: -0.005em;
}
/* The lead group carries the page's purpose, so it gets the larger heading and more air after. */
.ns-group--lead .ns-group__heading { font-size: 1.25rem; }
.ns-group--lead { margin-bottom: var(--gm-space-16, 4rem); }
/* Plumbing: same structure, less voice. */
.ns-group--quiet .ns-group__heading { font-size: 1rem; color: var(--gm-text-secondary); }

/* ---- Escalation ladder --------------------------------------------------------
   A sequence, not a table. In the table the step number sat ~300px from its own offset — two facts
   about one instant, visually divorced — and every rung looked equally important. Here the offset
   leads each rung, the target follows it, channels stay quiet, and a connecting rule makes the
   progression read downward. */

.ns-ladder {
  list-style: none; margin: 0 0 var(--gm-space-6); padding: 0 0 0 var(--gm-space-5);
  border-left: 1px solid var(--gm-border-subtle);
  display: grid; gap: var(--gm-space-1);
}
.ns-ladder__rung {
  display: grid; grid-template-columns: 7.5rem 1fr auto;
  align-items: baseline; gap: var(--gm-space-2) var(--gm-space-4);
  padding: var(--gm-space-3) 0;
  border-bottom: 1px solid var(--gm-border-subtle);
  position: relative;
}
.ns-ladder__rung:last-child { border-bottom: 0; }
/* A tick on the rail at each rung, so the eye can count the sequence without reading it. */
.ns-ladder__rung::before {
  content: ""; position: absolute; left: calc(var(--gm-space-5) * -1 - 1px); top: 1.55em;
  width: var(--gm-space-4); height: 1px; background: var(--gm-border-subtle);
}
.ns-ladder__when {
  font-variant-numeric: tabular-nums; font-weight: 650;
  color: var(--gm-text-primary); white-space: nowrap;
}
.ns-ladder__what { display: grid; gap: var(--gm-space-1); min-width: 0; }
.ns-ladder__target { color: var(--gm-text-primary); }
.ns-ladder__channels { color: var(--gm-text-secondary); font-size: 0.8125rem; }
.ns-ladder__act { justify-self: end; }
/* The bounded repeat closes the sequence; it is a note about the ladder, not another rung. */
.ns-ladder__rung--tail { padding-top: var(--gm-space-4); }
.ns-ladder__rung--tail::before { display: none; }
.ns-ladder__rung--tail .ns-ladder__when { color: var(--gm-text-secondary); font-weight: 400; }
.ns-ladder__rung--tail .ns-ladder__channels { max-width: 62ch; }

/* ---- Roster ------------------------------------------------------------------
   Members belong with the shifts, not in a section of their own: one topic, one group. A label
   column keeps the list from reading as body text. */

.ns-roster {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--gm-space-2) var(--gm-space-4);
  margin-bottom: var(--gm-space-5);
}
.ns-roster__label {
  flex: 0 0 auto; min-width: 6rem;
  font-size: 0.8125rem; color: var(--gm-text-secondary);
}
/* One scale inside the group: the names were a step larger than the shift table beneath them. */
.ns-roster .ns-people { font-size: 0.9375rem; }
.ns-empty--inline { padding: 0; }

/* ---- Sections --------------------------------------------------------------- */

/* One rhythm for the whole page: equal air between sections, a tight bond between a
   heading and the thing it labels. Uneven gaps are most of what reads as "noise". */
.ns-section { margin: 0 0 var(--gm-space-7, 3rem); }
/* The bottom margin above already separates sections; adding a top margin as well stacked them to
   six rem of dead air. Only the cases that genuinely lacked air get it: a section following the
   page head, or following a row of buttons. */
.ns-page-head + .ns-section { margin-top: var(--gm-space-5); }
.ns-detail__actions + .ns-section { margin-top: var(--gm-space-6); }
.ns-section > h2 {
  font-size: 0.8125rem; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--gm-text-secondary);
  margin: 0 0 var(--gm-space-3);
}

/* ---- Action rows ------------------------------------------------------------
   Rails' button_to renders a <form>, which is block-level — that is why page actions
   stacked instead of sitting side by side. One row, one gap, forms stripped of margin. */

.ns-actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--gm-space-2); }
.ns-actions form { margin: 0; }
.ns-actions--end { justify-content: flex-end; }
/* Inside a table cell the actions never wrap: a second line doubles the row height and breaks
   the horizontal rhythm of the whole table. The cell scrolls with the table instead. */
.ns-cell-actions .ns-actions { flex-wrap: nowrap; justify-content: flex-end; }

/* Row-level actions are secondary by definition: they belong to one line of a table, not to
   the page. Full-size buttons in every row made each row 60px tall and gave a "Remove" the
   same visual weight as the primary action. Quiet text controls instead — scoped with .gm to
   win against the style guide's own `.gm .gm-web-btn`. */
.gm .ns-btn-quiet {
  min-height: 32px; height: auto; padding: 0 var(--gm-space-2);
  background: transparent; border-color: transparent;
  color: var(--gm-text-secondary); font-size: 0.8125rem; font-weight: 500;
}
.gm .ns-btn-quiet:hover { color: var(--gm-text-primary); border-color: var(--gm-border-subtle); }

/* ---- Forms ------------------------------------------------------------------
   The style guide styles text/number/url/select but not the date-time inputs, so those
   rendered as raw browser widgets — white boxes in a dark UI. Same treatment here, from the
   same tokens. (Worth upstreaming; noted in TODO.md.) */

.gm input[type="datetime-local"],
.gm input[type="date"],
.gm input[type="time"] {
  font: inherit; color: var(--gm-text-primary);
  background: var(--gm-bg-elevated, transparent);
  border: 1px solid var(--gm-border-subtle); border-radius: var(--gm-radius-sm, 4px);
  padding: var(--gm-space-2) var(--gm-space-3);
  min-height: 40px;
}
.gm input[type="datetime-local"]:focus,
.gm input[type="date"]:focus,
.gm input[type="time"]:focus { outline: 2px solid var(--gm-accent); outline-offset: 1px; }
/* The native calendar glyph is near-black on our dark surface without this. */
.gm input[type="datetime-local"]::-webkit-calendar-picker-indicator,
.gm input[type="date"]::-webkit-calendar-picker-indicator { filter: invert(1) opacity(0.6); }

/* An add-row form: labels on one line, controls on the next, everything sharing both.
   Replaces the old flex row, where a five-line checkbox stack dragged the other labels out
   of alignment. */
.ns-form-row {
  display: flex; flex-wrap: wrap; align-items: flex-end; gap: var(--gm-space-3);
  margin-top: var(--gm-space-4); padding-top: var(--gm-space-4);
  border-top: 1px dashed var(--gm-border-subtle);
}
.ns-form-row .gm-field { margin: 0; }
.ns-form-row .gm-field-label,
.ns-form-row label { display: block; margin-bottom: var(--gm-space-1);
  font-size: 0.75rem; color: var(--gm-text-secondary); }
/* Same box height for every control, borders included — the labels sit on top of them, so a
   4px difference in height is a 4px difference in label baseline. 44px is the design system's
   own control height; forcing a smaller box clipped the select's descenders. */
.ns-form-row input, .ns-form-row select, .ns-form-row .gm-web-btn {
  height: 44px; min-height: 44px; padding-top: 0; padding-bottom: 0; box-sizing: border-box;
}
/* Selects size to their longest option instead of stretching to 100% of the row. */
.ns-form-row select { width: auto; min-width: 13rem; padding-inline: var(--gm-space-3); }
/* A minute count needs a few characters, not a third of the row — but enough that its label
   fits above it without dictating the column width. */
.ns-form-row input[type="number"] { width: 8rem; }
.ns-form-row .gm-field { gap: var(--gm-space-1); }
.ns-form-row__grow { flex: 1 1 12rem; max-width: 24rem; }
/* One explanatory line for the whole row, so no single label has to carry it. */
.ns-form-row__hint {
  flex: 1 0 100%; margin: 0; font-size: 0.75rem; color: var(--gm-text-secondary);
}

/* Checkboxes in a row, each label inline with its box — as a column it was taller than every
   other control and broke the row. */
.ns-checks { display: flex; flex-wrap: wrap; gap: var(--gm-space-1) var(--gm-space-3); }
.ns-checks label {
  display: inline-flex; align-items: center; gap: var(--gm-space-1);
  margin: 0; min-height: 40px;
  font-size: 0.875rem; color: var(--gm-text-primary);
}
.ns-checks input { margin: 0; accent-color: var(--gm-accent-fill, var(--gm-accent)); }
.ns-token {
  font-family: var(--gm-font-mono, ui-monospace, monospace); word-break: break-all;
  padding: var(--gm-space-3); border: 1px dashed var(--gm-accent); border-radius: var(--gm-radius-sm, 4px);
}
.ns-empty { color: var(--gm-text-secondary); padding: var(--gm-space-5) 0; }

/* ---- Footer ----------------------------------------------------------------- */

.ns-footer {
  /* auto is what pins it down: it absorbs the leftover space above the footer. */
  margin-top: auto;
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: var(--gm-space-3);
  /* Extra bottom room so a flash bar — which is fixed to the window — cannot sit on top of
     the colophon. */
  padding: var(--gm-space-6, 2.5rem) 0 var(--gm-space-7, 3rem);
  border-top: 1px solid var(--gm-border-subtle);
}
.ns-colophon { color: var(--gm-text-secondary); font-size: 0.75rem; }

/* ---- Record list (teams) -----------------------------------------------------
   Its own two-column shape rather than the alert grid: identity on the left, counts on the
   right, and the whole row is the target — a name-sized click area is a needless miss at 03:00. */

.ns-cards { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--gm-space-2); }
.ns-card-row {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: var(--gm-space-2) var(--gm-space-4);
  padding: var(--gm-space-3) var(--gm-space-4);
  border: 1px solid var(--gm-border-subtle); border-radius: var(--gm-radius-md, 8px);
  background: var(--gm-bg-elevated, transparent);
}
.ns-card-row:hover { border-color: var(--gm-accent); }
.ns-card-row__name {
  display: inline-flex; align-items: center; gap: var(--gm-space-2);
  font-weight: 600; color: inherit; text-decoration: none;
}
.ns-card-row__name::after { content: ""; position: absolute; inset: 0; } /* whole row clickable */
.ns-card-row { position: relative; }
.ns-card-row__meta {
  display: flex; flex-wrap: wrap; gap: var(--gm-space-4);
  color: var(--gm-text-secondary); font-size: 0.8125rem; font-variant-numeric: tabular-nums;
}

/* ---- Single-record form ------------------------------------------------------
   A field as wide as the page is unreadable and implies the content should be long. One
   column at a comfortable measure, consistent vertical rhythm, and an explicit way out. */

.ns-form { max-width: 34rem; }
.ns-form .gm-field { margin-bottom: var(--gm-space-5); gap: var(--gm-space-1); }
.ns-form input, .ns-form select, .ns-form textarea { width: 100%; }
/* A submit is not a field: full column width made "Save" look like an input. */
.ns-form input[type="submit"], .ns-form__actions input[type="submit"] { width: auto; }
.ns-form__hint {
  margin: var(--gm-space-1) 0 0; font-size: 0.75rem; color: var(--gm-text-secondary);
}
.ns-form__actions {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--gm-space-3);
  margin-top: var(--gm-space-5);
}
.ns-form__actions form { margin: 0; }

/* ---- Profile layout ----------------------------------------------------------
   Two groups, not five equal slabs: the channels (the only thing here with unfinished work) and the
   account reference beside them. Rhythm is deliberate — tight inside a channel, generous between
   groups, and more space above a heading than below it, so the eye lands on group boundaries.

   Sequence on narrow screens is the task order: channels first, account after. */

.ns-profile { display: grid; gap: var(--gm-space-10); align-items: start; }
@media (min-width: 62rem) {
  .ns-profile {
    /* The task column gets the width; the aside gets just enough to stay readable. */
    grid-template-columns: minmax(0, 1.7fr) minmax(15rem, 0.8fr);
    gap: var(--gm-space-12) var(--gm-space-12);
  }
}
/* min-width: 0 is load-bearing: without it the 60-character connection link stretches this grid
   track and pushes the aside off the viewport. */
.ns-profile__main { min-width: 0; }
/* A real type step instead of another uppercase eyebrow: this heading leads the page. */
.ns-profile__heading { margin: 0 0 var(--gm-space-2); font-size: 1.125rem; letter-spacing: -0.005em; }
/* 65–75ch measure, so the explanation reads as prose rather than as a banner. */
.ns-lede { margin: 0 0 var(--gm-space-6); max-width: 68ch; color: var(--gm-text-secondary); }

/* One hairline per channel, no card: proximity carries the grouping, the rule only marks where one
   channel ends. Cards here would turn three unequal things into three equal-looking boxes. */
.ns-channel { padding: var(--gm-space-6) 0 var(--gm-space-5); border-top: 1px solid var(--gm-border-subtle); }
.ns-channel:first-of-type { padding-top: 0; border-top: 0; }
.ns-channel:last-of-type { padding-bottom: 0; }
.ns-channel__head {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--gm-space-3);
  margin-bottom: var(--gm-space-3);
}
.ns-channel__head h3 { margin: 0; font-size: 1rem; letter-spacing: -0.005em; }
.ns-channel > p { margin: 0 0 var(--gm-space-2); max-width: 68ch; }
.ns-channel .ns-steps { margin-bottom: var(--gm-space-5); }
.ns-channel .gm-alert { margin-bottom: var(--gm-space-4); }

/* The aside is reference material: quieter headings, tighter blocks, separated by a rule so it does
   not read as one long list. */
.ns-profile__aside { display: grid; gap: var(--gm-space-8); align-content: start; }
.ns-subhead {
  margin: 0 0 var(--gm-space-2);
  font-size: 0.9375rem; font-weight: 600; color: var(--gm-text-primary);
}
.ns-aside__block + .ns-aside__block {
  padding-top: var(--gm-space-6); border-top: 1px solid var(--gm-border-subtle);
}
.ns-aside__block .ns-form__hint { margin-top: var(--gm-space-2); }
/* Label above value: at aside width a 11rem label column would leave no room for the value. */
.ns-facts--stacked { grid-template-columns: 1fr; gap: 0; font-size: 0.9375rem; }
.ns-facts--stacked dt { margin-top: var(--gm-space-2); font-size: 0.8125rem; }
.ns-facts--stacked dt:first-child { margin-top: 0; }
.ns-facts--stacked dd { word-break: break-word; }

/* A single-field form does not need a column of its own. */
.ns-form--inline {
  display: flex; flex-wrap: wrap; align-items: flex-end; gap: var(--gm-space-3); max-width: none;
}
.ns-form--inline .gm-field { margin: 0; flex: 0 1 22rem; }
.ns-form--inline .gm-web-btn { height: 44px; }

/* ---- Telegram binding --------------------------------------------------------
   The one page where the content has to leave the screen: the link belongs on the phone in
   someone's pocket. QR beside the link, both given room. */

.ns-telegram-bind {
  display: flex; flex-wrap: wrap; gap: var(--gm-space-5);
  align-items: flex-start; margin-top: var(--gm-space-4);
}
.ns-telegram-bind__link { flex: 1 1 22rem; min-width: 0; }
.ns-qr { margin: 0; flex: 0 0 auto; text-align: center; }
.ns-qr svg { width: 168px; height: 168px; display: block; border-radius: var(--gm-radius-sm, 4px); }
.ns-qr figcaption {
  margin-top: var(--gm-space-2); font-size: 0.75rem; color: var(--gm-text-secondary);
}
/* Numbered because it is a sequence someone follows on two devices. */
.ns-steps { margin: 0 0 var(--gm-space-4); padding-left: 1.4em; display: grid; gap: var(--gm-space-2); }
.ns-steps li { padding-left: var(--gm-space-1); }
/* The link stays selectable and wraps instead of overflowing — it is 60+ characters. */
.ns-token__link { color: var(--gm-accent); word-break: break-all; }

/* The style guide's alert is a row container; our alert boxes carry a title, a body and a hint,
   which ended up as three columns reading like a sidebar. One column, normal flow. */
.gm .gm-alert { display: block; }
.gm .gm-alert > * + * { margin-top: var(--gm-space-2); }
.gm .gm-alert p { margin: 0; }

/* ---- Small pieces ----------------------------------------------------------- */

/* Names as a wrapped row of their own, each with room to carry a state badge. */
.ns-people { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: var(--gm-space-2) var(--gm-space-4); }
.ns-people li { display: inline-flex; align-items: center; gap: var(--gm-space-2); }

/* Timestamps and identifiers: tabular so columns of them line up character by character. */
.ns-time { font-variant-numeric: tabular-nums; color: var(--gm-text-secondary); white-space: nowrap; }
.ns-table td.ns-time, .ns-table th.ns-time { text-align: right; }
.ns-code {
  font-family: var(--gm-font-mono, ui-monospace, monospace); font-size: 0.8125rem;
  color: var(--gm-text-secondary);
}

/* The one-time token stays put and stays legible — it exists to be copied. */
.ns-token-reveal { margin-bottom: var(--gm-space-6); }
.ns-token-reveal__url { font-size: 0.8125rem; color: var(--gm-text-secondary); word-break: break-all; }

/* Available to screen readers, invisible on screen — for the header of an action column, which
   needs no visible label but must not be an empty cell. */
.ns-visually-hidden {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
}

/* ---- Flashes ---------------------------------------------------------------
   A fixed bar at the bottom, like rollmonkey and deployed: it can auto-dismiss
   without shifting the layout, and on a phone it sits where the thumb already is.
   Colours are chosen apart from the severity palette above, so a green flash can
   never be mistaken for an alert severity. */

.ns-flash-region {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 40;
  display: flex; flex-direction: column;
}
.ns-flash {
  padding: var(--gm-space-3) var(--gm-space-4);
  text-align: center; font-size: 0.875rem; font-weight: 600;
  color: #fff; cursor: pointer;
  animation: ns-flash-in 260ms cubic-bezier(0.22, 1, 0.36, 1);
}
.ns-flash--notice { background: #256029; }
.ns-flash--alert  { background: #a02622; }
@keyframes ns-flash-in { from { transform: translateY(100%); } to { transform: translateY(0); } }
@media (prefers-reduced-motion: reduce) { .ns-flash { animation: none; } }

@media (max-width: 40rem) {
  /* The ladder reflows rather than squeezing: the offset and its action share the top line, the
     target and channels sit beneath. A 7.5rem offset column would leave ~180px for everything else. */
  .ns-ladder { padding-left: var(--gm-space-4); }
  .ns-ladder__rung { grid-template-columns: 1fr auto; }
  .ns-ladder__when { grid-column: 1; }
  .ns-ladder__act { grid-column: 2; grid-row: 1; }
  .ns-ladder__what { grid-column: 1 / -1; }
  .ns-ladder__rung::before { left: calc(var(--gm-space-4) * -1 - 1px); width: var(--gm-space-3); }
  /* The roster label above the names, not beside them. */
  .ns-roster { display: grid; gap: var(--gm-space-1); }

  /* A fixed label column would take nearly half a phone's width; stack instead. */
  .ns-ack__meta, .ns-facts { grid-template-columns: 1fr; gap: 0; }
  .ns-ack__meta dt, .ns-facts dt { margin-top: var(--gm-space-3); font-size: 0.8125rem; }
  .ns-ack__meta dt:first-child, .ns-facts dt:first-child { margin-top: 0; }
  /* Stacked, left-aligned: a wrapped nav pushed to the right edge reads as debris. */
  .ns-header { justify-content: flex-start; row-gap: 0; }
  .ns-row { grid-template-columns: auto 1fr; }
  .ns-timeline li { grid-template-columns: 1fr; gap: 0; }
  .ns-ack { padding: var(--gm-space-4) var(--gm-space-3); }
}

/* ---- Rota: who is on duty, as a picture -----------------------------------------
   Lifted from planner's board, minus what does not apply here: one lane per person, a track
   with a hairline per day, bars placed by percent of the window (nothing measured, nothing to
   drift), the current instant as a line through every row. On a phone the panel scrolls
   sideways inside .ns-scroll-x; the page never does. Colour and hatching are redundant cues —
   every gap and overlap also carries a title and is counted in the summary line above. */

.ns-rota-nav {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--gm-space-2) var(--gm-space-4);
  margin-bottom: var(--gm-space-2);
}
.ns-rota-nav__range { color: var(--gm-text-secondary); font-size: 0.8125rem; font-variant-numeric: tabular-nums; }
.ns-rota-nav__window { margin-left: auto; display: flex; gap: var(--gm-space-1); }
.ns-rota-nav__days {
  display: inline-flex; align-items: center; min-height: 32px; padding: 0 var(--gm-space-2);
  border: 1px solid transparent; border-radius: var(--gm-radius-sm, 4px);
  color: var(--gm-text-secondary); text-decoration: none; font-size: 0.8125rem;
}
.ns-rota-nav__days:hover { color: var(--gm-text-primary); }
.ns-rota-nav__days[aria-current="page"] {
  color: var(--gm-text-primary); border-color: var(--gm-border-subtle); font-weight: 600;
}
.ns-rota__summary { margin: 0 0 var(--gm-space-3); font-size: 0.875rem; color: var(--gm-text-secondary); }

.ns-rota {
  /* Fourteen day columns have a floor; below it the panel scrolls, the page does not. */
  min-width: 44rem;
  margin-bottom: var(--gm-space-5);
  border: 1px solid var(--gm-border-subtle); border-radius: var(--gm-radius-md, 8px);
  background: var(--gm-bg-elevated, transparent);
  font-size: 0.8125rem;
  overflow: hidden; /* the radius clips the row backgrounds */
}
.ns-rota__row { display: flex; align-items: stretch; min-height: 2.75rem; border-top: 1px solid var(--gm-border-subtle); }
.ns-rota__row--head { min-height: 1.75rem; border-top: 0; }
.ns-rota__row--coverage { min-height: 2.25rem; }
.ns-rota__label {
  flex: 0 0 11rem; min-width: 0;
  display: flex; align-items: center; gap: var(--gm-space-2);
  padding: 0 var(--gm-space-3);
  border-right: 1px solid var(--gm-border-subtle);
  color: var(--gm-text-primary);
}
.ns-rota__row--coverage .ns-rota__label {
  font-size: 0.6875rem; font-weight: 700; letter-spacing: 0.05em; text-transform: uppercase;
  color: var(--gm-text-secondary);
}
.ns-rota__name { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; font-weight: 600; }
/* The row under the pointer answers, so a bar can be traced back to its name across a wide track.
   A wash only — hover never moves anything. */
.ns-rota__lane:hover { background: rgba(255, 255, 255, 0.04); }
.ns-rota__track {
  flex: 1; position: relative; min-width: 0;
  /* One hairline per day column, drawn by the background so it costs no elements. */
  background-image: linear-gradient(90deg, var(--gm-border-subtle) 1px, transparent 1px);
  background-size: calc(100% / var(--ns-rota-days, 14)) 100%;
}
.ns-rota__track--head { display: flex; background: none; }
.ns-rota__day {
  flex: 1; min-width: 0; padding: var(--gm-space-1) 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center; line-height: 1.2;
  text-align: center; overflow: hidden; white-space: nowrap;
  font-size: 0.625rem; font-weight: 700; letter-spacing: 0.02em; text-transform: uppercase;
  color: var(--gm-text-secondary);
}
.ns-rota__row--head { min-height: 2.25rem; }
.ns-rota__day--today { color: var(--gm-text-primary); }
/* Weekends are what an on-call rota is about, so they are visible in every row, not only in
   the header. Under everything else. */
.ns-rota__day--weekend, .ns-rota__weekend { background: rgba(0, 0, 0, 0.18); }
.ns-rota__weekend { position: absolute; top: 0; bottom: 0; pointer-events: none; }

/* The current instant: a line through every row, named once in the header. */
.ns-rota__now {
  position: absolute; top: 0; bottom: 0; width: 0;
  border-left: 2px solid var(--ns-moonlight); pointer-events: none; z-index: 3;
}
.ns-rota__now--head { top: auto; height: 60%; }
.ns-rota__nowlabel {
  position: absolute; bottom: 100%; left: 0; translate: -50% 0;
  font-size: 0.625rem; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--ns-moonlight);
}

/* A shift. The bar's own text shows up only when the bar is wide enough to carry it; the title
   always has the full times. A bar cut by the window edge loses its rounding on that side, so it
   visibly runs on rather than ending. */
.ns-rota__bar {
  position: absolute; top: 7px; bottom: 7px; z-index: 1;
  display: flex; align-items: center; padding: 0 var(--gm-space-2);
  border-radius: var(--gm-radius-sm, 4px);
  background: var(--gm-accent-fill, var(--gm-accent)); color: var(--gm-text-inverse, #fff);
  font-size: 0.75rem; font-variant-numeric: tabular-nums;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.ns-rota__bar--cut-left { border-top-left-radius: 0; border-bottom-left-radius: 0; }
.ns-rota__bar--cut-right { border-top-right-radius: 0; border-bottom-right-radius: 0; }

/* Coverage: how many people are on duty. Covered is quiet; a gap is hatched in the warning
   colour, an overlap tinted blue — and both say what they are, in the segment when it is wide
   enough and in the title always. */
.ns-rota__cover {
  position: absolute; top: 9px; bottom: 9px; z-index: 1;
  display: flex; align-items: center; justify-content: center;
  border-radius: 3px; overflow: hidden; white-space: nowrap;
  font-size: 0.625rem; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase;
}
.ns-rota__cover--ok { background: rgba(93, 202, 165, 0.28); }
.ns-rota__cover--gap {
  background: repeating-linear-gradient(135deg, var(--ns-severity-warning) 0 3px, transparent 3px 8px);
  box-shadow: inset 0 0 0 1px var(--ns-severity-warning);
  color: var(--gm-text-primary);
}
.ns-rota__cover--overlap {
  background: rgba(93, 185, 232, 0.35);
  box-shadow: inset 0 0 0 1px var(--gm-info, #5db9e8);
  color: var(--gm-text-primary);
}
/* The word sits on a small plate so it stays legible over the hatching. */
.ns-rota__cover--gap span, .ns-rota__cover--overlap span {
  padding: 0 var(--gm-space-1); border-radius: 2px; background: var(--gm-bg-elevated, #0a4a3c);
}
.ns-rota__empty { margin: 0; padding: var(--gm-space-4) var(--gm-space-3); color: var(--gm-text-secondary); }

@media (max-width: 40rem) {
  /* The window picker under the arrows, not squeezed beside them. */
  .ns-rota-nav__window { margin-left: 0; flex-basis: 100%; }
  .ns-rota__label { flex-basis: 8rem; }
}
