/*
 * The shell's own CSS: layout, and the focus/label baseline. Nothing else.
 *
 * C9. COPIED into portal-ui/ and admin-ui/ (engineering.md §4.3).
 *
 * ⚠ THIS FILE HARDCODES NO DESIGN VALUE, AND THAT IS A CHECKED RULE, NOT A
 * PREFERENCE. C7 copies the website's tokens, fonts and glass surfaces from
 * ~/MHF once the website ships (gates.md RULING portal-c7-ordering). If this
 * file carried its own colours or type, C7 would be a second pass over these
 * lines and the two would disagree in the meantime. So the shell names tokens
 * and never defines them:
 *
 *     grep -nE '#[0-9a-fA-F]{3,6}|font-family:|[0-9]+px' portal-ui/shell/shell.css
 *
 * returns nothing, and that grep is part of C9's gate. Every custom property
 * used below was checked to exist in ~/MHF's glass.css at BOTH the shipped tip
 * (72b90db) and the handed-back tip (5e68aa2), so C7 lights this up whichever
 * one ships. Until then the fallbacks in var() keep the page legible rather
 * than unreadable — a fallback is a value of last resort, not a design.
 *
 * Lengths are rem, em and ch. Not px: a school opens this on whatever they
 * have, and a px layout ignores a user who has set a larger default text size —
 * which is the accessibility setting people actually use.
 */

/* ------------------------------------------------- the portal's own layer */
/*
 * ⚠ THE ONLY FIVE VALUES THIS REPO OWNS, each declared in its job's
 * decisions.md (the first two at C9, the last three at D2 of the portal design
 * round). Everything else above still names a token and defines nothing.
 *
 * They live HERE, not in tokens.css or door.css, on the Engineer's ruling at
 * engineering.md 9.4w: both of those files are GENERATED and are replaced
 * wholesale on the next website ship or design round. An override edited into
 * a generated file does not survive its next regeneration, and it fails
 * silently - the value simply reverts and the page still renders.
 *
 *   --g-maxw   the shell's page max-width. C9 listed it as a website token and
 *              it never was one: it appears nowhere in glass.css at 72b90db,
 *              5e68aa2 or 605bf19. The C9 check passed because it compared the
 *              name against a hand-typed list rather than against the website,
 *              so it asserted a fact it could not see was false.
 *   --bg-image the background, re-pointed at this repo's own copy. The website
 *              value is url('/assets/img/...'), and on the portal the ingress
 *              routes /assets to the API, so the website's own URL would ask
 *              the API for an image. tokens.css keeps the website value; this
 *              overrides it, and a test asserts the override actually wins.
 *   --scrim    how much of the ground veils the photograph. The website's .46
 *              is tuned for a hero; direction A ("on the ground", PICKed
 *              2026-09-21) sets .52 for working screens, where the cards
 *              carry text and the photograph is behind them all day.
 *   --pad-card, --gap-card, --gap-section, --edge, --measure  R-2 and R-3's
 *              scale, design.md §2, IN PX AS DRAWN - a declared exception to
 *              this file's no-px rule (decisions.md, D5). D3 wrote them in rem
 *              believing rem = the drawing's px; it is not: door.css sets
 *              html { font-size: var(--fs-body) }, 14px on a phone, so every
 *              rem here was 12.5% under the drawing (gap 21 vs 22 at 375).
 *              The rule they encode is Nicolas's sentence: the space BETWEEN
 *              two cards is always larger than the padding INSIDE them.
 *   --fs-micro the smallest type step, 13px - design.md §6's binding phone
 *              floor (the drawing's own micro step bottoms out at 12px; the
 *              stated minimum wins, D5). tokens.css has no micro step.
 *   --fs-small, --fs-eyebrow  the website's steps, FLOORED at 13px: on a
 *              phone they computed to 10.08px (.72rem of a 14px root).
 *   --tap      44px, the smallest thing you tap (design.md §6, R-6).
 *              ⚠ Floors are PHYSICAL, so they are px: in rem they shrink with
 *              the 14px phone root to 38.5px, which is how D5 first measured.
 *   --glass-bg, --glass-border  direction A's card glass, from the drawing
 *              (rounds/mhf/2026-09-21-portal-round, [data-dir='a'] .card). The
 *              website's .58 fill is for one hero panel; at .58 a working card
 *              measured 1.17-1.67:1 against the ground, under the round's 1.34.
 */
:root {
  --g-maxw: 75rem;
  /* ⚠ ROOT-ABSOLUTE, NOT '../img/'. A relative url() inside a custom property
     resolves against the stylesheet that USES var(--bg-image) - door.css at
     /_ui/door.css - not this one, so '../img/' asked for /img/…, which exists
     nowhere. Found by scripts/design-checks/ground.mjs on its first run (D2). */
  --bg-image: url('/_ui/img/bg-placeholder.webp');
  --scrim: .52;
  --glass-bg: rgba(255, 255, 255, .72);
  --glass-border: rgba(255, 255, 255, .66);
  --pad-card: clamp(16px, 4.2vw, 24px);
  --gap-card: clamp(22px, 5.6vw, 34px);
  --gap-section: clamp(30px, 7vw, 46px);
  --edge: clamp(14px, 4vw, 28px);
  --measure: 65ch;
  --fs-micro: 13px;
  --fs-small: max(13px, clamp(.72rem, 1.2vw, .8125rem));
  --fs-eyebrow: max(13px, clamp(.72rem, 1.25vw, .9rem));
  --tap: 44px;
}

/* ---------------------------------------------------- R-2 and R-3, the rhythm */
/*
 * ⚠ THE GAP GOES ON THE BOX THAT HOLDS THE CARDS. Every view emits its cards
 * into a fill-target wrapper (`.s-stack` on the staff surface, `.s-loading` on
 * the school one - the name is historical; nothing else styles it) whose
 * display was `block`, so a gap set one level up never reached them: 0px
 * between cards holding 18px inside. The wrapper cannot be deleted - it is
 * what the loader fills - so it becomes the grid (engineering.md §1.3).
 *
 * Lists of cards lose the browser's 40px indent (R-3): a list card started 40px
 * in and was 40px narrower than the section card below it. `:has(> .card)`
 * rather than a list of class names, so a list added later is covered too.
 */
.card { padding: var(--pad-card); }
.main { gap: var(--gap-section); }
.s-stack,
.s-loading {
  display: grid;
  gap: var(--gap-card);
  align-content: start;
  min-width: 0;
}
:is(ul, ol):has(> .card) {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--gap-card);
}
.s-main p { max-width: var(--measure); }

/* ------------------------------------------------------- R-4, the Schedule */
/*
 * design.md §4: CSS ONLY, drawn against the markup the view ships, which is
 *   li.stop > div.top(span.when, span.pill) · h3 · div.meta(span.seg > button×4)
 *           · div.meta(label, select) · p.stamp? · p.s-problem
 * The defect: door.css's `.stop` is a 44px + 1fr grid for a `.dot` + `.body`
 * pair this view never emits, so six flat children were auto-placed two to a
 * row - dates over titles in a 44px column - and `.path::before` painted the
 * spine at left:21px, through the words. 6 overlapping pairs at every size.
 *
 * The 09-17 round's 19 `.path`/`.stop` rules are REMOVED from door.css by the
 * extractor's stated rule (engineering.md §3.2), not overridden here - an
 * override has to neutralise every property it does not want, and one it
 * misses keeps drawing where no overlap arm looks. So this block is the whole
 * of the Schedule's styling, including what those rules used to supply. The
 * spine is each card's own left edge; the `--done` rail is gone with them.
 */
ol.path {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--gap-card);
}
li.stop {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: .625rem;
  padding: var(--pad-card) var(--pad-card) var(--pad-card) calc(var(--pad-card) + .875rem);
  border-radius: var(--r-card);
  background: var(--glass-bg);
  border: 0.0625rem solid var(--glass-border);
  box-shadow: var(--glass-shadow);
  backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
}
li.stop::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: .3125rem;
  border-radius: .3125rem 0 0 .3125rem;
  background: var(--tag-grey);
}
li.stop[data-status="done"]::before { background: var(--green); }
li.stop[data-status="in_progress"]::before { background: var(--blue); }
li.stop[data-status="skipped"]::before {
  background: repeating-linear-gradient(180deg, var(--tag-grey) 0 .375rem, transparent .375rem .75rem);
}
li.stop .top { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem .75rem; }
li.stop .when {
  font-size: var(--fs-small);
  font-weight: 600;
  color: var(--blue);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
li.stop h3 {
  font-size: clamp(1.05rem, 2.6vw, 1.2rem);
  font-weight: 600;
  letter-spacing: -.012em;
  overflow-wrap: anywhere;
}
li.stop .meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .625rem;
  font-size: var(--fs-small);
  color: var(--ink-soft);
}
li.stop .stamp { margin: 0; font-size: var(--fs-micro); color: var(--ink-soft); }
li.stop select.assignee { min-height: 2.25rem; width: auto; max-width: 100%; padding-left: .875rem; }
li.stop .seg { display: flex; flex-wrap: wrap; gap: .375rem; padding: .25rem; }
li.stop .seg button { min-height: 2.25rem; padding: 0 .875rem; font-size: var(--fs-small); }
@media (max-width: 26.25em) {
  li.stop .seg { width: 100%; }
  li.stop .seg button { flex: 1 0 44%; }
}

/* ------------------------------------------- R-5 and R-6, the frame and touch */
/*
 * ONE PAGE EDGE. `.s-app` owns the page margin (--edge). The 09-17 app shell
 * nested inside it added its own (--gutter) on `.main`, `.topbar`, `.tabbar`, `.rail`,
 * and the door on `.door`, so every screen sat two edges in: cards at x=31 on a phone against the
 * drawing's 16 (found at D4; touch.mjs asserts it).
 */
.app-shell .main { padding-inline: 0; }
.app-shell .topbar { margin-inline: 0; }
.app-shell .rail { margin-left: 0; }
.app-shell .tabbar { margin-inline: 0; }
.s-main > .door { padding-inline: 0; }
/* With `.main`'s own side padding gone, the rail and the main column would
   touch on a wide screen; the drawing separates them by a section gap. */
.app-shell { column-gap: var(--gap-section); }

/* The staff seat list is a list of CARDS in the drawing (its row card), and a
   bulleted, indented list here: `li.seat` is not `.card`, so D3's rules and
   its arm both missed it. CSS only - the view's markup is drawn against:
   li.seat > b · span.pill · span.hint · (div.s-actions | p.hint) · p.s-problem */
ul.seats {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--gap-card);
}
li.seat {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: .375rem 1rem;
  padding: var(--pad-card);
  border-radius: var(--r-card);
  background: var(--glass-bg);
  border: 0.0625rem solid var(--glass-border);
  box-shadow: var(--glass-shadow);
  backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
}
li.seat > b { overflow-wrap: anywhere; }
li.seat > * { grid-column: 1; }
li.seat > .pill { justify-self: start; }
li.seat > .s-actions { grid-column: 2; grid-row: 1 / span 3; display: flex; flex-wrap: wrap; gap: .5rem; margin: 0; }
li.seat > p.hint,
li.seat > .s-problem { grid-column: 1 / -1; }
li.seat > .s-problem:empty { display: none; }
@media (max-width: 35em) {
  li.seat { grid-template-columns: minmax(0, 1fr); }
  li.seat > .s-actions { grid-column: 1; grid-row: auto; }
}

/* R-5, the Foundation's mark: 34 CSS pixels in the rail, 30 in a phone's top bar,
   where the words truncate and the image never shrinks. */
.brand-mark img { height: 2.125rem; width: auto; flex: 0 0 auto; }
.brand-mark span { display: grid; min-width: 0; }
.topbar .mark-sm { flex: 0 0 auto; align-items: center; min-height: var(--tap); }
.topbar .mark-sm img { height: 1.875rem; }
.topbar .org { min-width: 0; flex: 1 1 auto; }

/* R-6, every control you tap clears --tap; the phone type floor is --fs-micro. */
.btn,
.navitem,
.brand-mark,
.input,
.s-field > input,
.s-field > select,
li.stop .seg button,
li.stop select.assignee { min-height: var(--tap); }
.btn[data-style="quiet"] { min-height: var(--tap); padding-block: 0; }
.tabbar .navitem { font-size: var(--fs-micro); }
/* A radio or checkbox is tapped through its label, so the LABEL clears --tap. */
label:has(> input[type="radio"]),
label:has(> input[type="checkbox"]) { display: flex; align-items: center; gap: .5rem; min-height: var(--tap); }
/* The phone type floor, where the 09-17 components state a smaller literal
   size (pills, the seat role, the material's note) or inherit .875rem of the
   phone root (14 CSS pixels). max() so nothing SHRINKS on a wide screen. */
.pill,
.topbar .who span,
.mat span,
.mat .file { font-size: max(var(--fs-micro), .74rem); }
.s-skip,
.s-product,
.s-secret,
.hint,
.topbar .who,
.seat b,
.attachments li,
.s-field > legend { font-size: max(var(--fs-micro), .875rem); }
/* Destructive is distinguishable from quiet: a wine outline, filling on hover. */
.btn[data-style="danger"] {
  background: transparent;
  color: var(--wine);
  border-color: var(--wine);
  font-weight: 600;
}
.btn[data-style="danger"]:hover,
.btn[data-style="danger"]:focus-visible { background: var(--wine); color: var(--ground); }

/* ------------------------------------------ text standing on the photograph */
/*
 * Bar item 4's second half: text ≥ 4.5:1 EVERYWHERE. Inside a card the glass
 * carries it; standing on the ground it does not - with the photograph behind
 * it, --ink-soft read 4.11-4.37:1 on every door lede and 4.28:1 on a quiet
 * "Back to …" link (visual-check.mjs, 39 of 145 readings, D6). The drawing uses
 * the same grey there; it was never swept. So text ON the ground is --ink.
 */
.door-say .lede,
.s-stack > .btn[data-style="quiet"],
.s-loading > .btn[data-style="quiet"] { color: var(--ink); }

/* --------------------------------------------------------------- the ground */
/*
 * R-1 of the portal design round. The backdrop (.bg / .bg-logo / .bg-scrim) is
 * styled by door.css and marked up in index.html, outside .s-app, so it exists
 * before any script runs. It is position:fixed at z-index 0, and a positioned
 * box paints over an unpositioned one — so the frame is lifted above it here,
 * or the photograph would cover every screen.
 */
.s-app {
  position: relative;
  z-index: 1;
}

/* ---------------------------------------------------------------- the frame */

.s-app {
  min-height: 100vh;
  display: grid;
  grid-template-rows: auto 1fr auto;
  gap: var(--edge, 1.25rem);
  padding: var(--gutter, 1.25rem);
  max-width: var(--g-maxw, 75rem);
  margin-inline: auto;
}

.s-main {
  display: flex;
  flex-direction: column;
  gap: var(--card-pad, 1.5rem);
  min-width: 0;
}

.s-header,
.s-footer {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--edge, 1.25rem);
  min-width: 0;
}

.s-footer {
  font-size: var(--fs-small, 0.8125rem);
  color: var(--ink-soft, inherit);
}

/* ------------------------------------------------------- skip link and focus */

/*
 * The skip link is the first focusable thing on the page and is invisible until
 * it is focused. Without it a keyboard user tabs through the whole header on
 * every view; with it they reach the content in one key.
 */
.s-skip {
  position: absolute;
  left: -100vw;
  top: 0;
  z-index: 100;
  padding: 0.5em 1em;
  border-radius: var(--r-label, 0.75rem);
  background: var(--ground, Canvas);
  color: var(--ink, CanvasText);
}

.s-skip:focus {
  left: var(--gutter, 1.25rem);
  top: var(--gutter, 1.25rem);
}

/*
 * THE VISIBLE-FOCUS BASELINE, and the reason it is :focus-visible and not
 * :focus. A ring on :focus appears when a mouse user clicks a button, which is
 * what makes teams delete the ring altogether — and deleting it is what leaves
 * keyboard users with no way to tell where they are. :focus-visible shows it to
 * the people who navigate by keyboard and hides it from the people who do not.
 *
 * `outline`, not `box-shadow`: an outline follows the element's shape, survives
 * forced-colours mode, and is never clipped by an ancestor's overflow.
 */
.s-app :focus-visible {
  outline: 0.1875rem solid var(--blue, Highlight);
  outline-offset: 0.125rem;
  border-radius: var(--r-label, 0.25rem);
}

/* A heading focused by moveFocusTo() is a destination, not a control, so it gets no ring. */
.s-app [tabindex="-1"]:focus,
.s-app [tabindex="-1"]:focus-visible {
  outline: none;
}

/* -------------------------------------------------- announcements and errors */

/*
 * Visually hidden but still read aloud. `display: none` and `visibility:
 * hidden` both remove an element from the accessibility tree, so the live
 * region has to be clipped instead — this is the shape that works.
 */
.s-visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.s-problem {
  display: block;
  font-size: var(--fs-small, 0.8125rem);
  color: var(--wine, currentColor);
  min-height: 1em; /* reserved, so a message appearing does not shift the form under the pointer */
}

.s-problem:empty {
  min-height: 0;
}

.s-notice {
  display: flex;
  gap: 0.75em;
  align-items: flex-start;
  padding: var(--card-pad, 1rem);
  border-radius: var(--r-card, 1.25rem);
  border: 0.0625rem solid var(--hairline, currentColor);
}

/* ------------------------------------------------------------------- fields */

/*
 * Every control is labelled by a real <label for>, never a placeholder: a
 * placeholder disappears the moment somebody types, so a user who is
 * interrupted comes back to a filled box with no idea what it holds.
 */
.s-field {
  display: flex;
  flex-direction: column;
  gap: 0.375em;
  min-width: 0;
}

.s-field > label {
  font-size: var(--fs-small, 0.8125rem);
  font-weight: 600;
}

.s-field > input,
.s-field > select,
.s-field > textarea {
  font: inherit;
  color: inherit;
  padding: 0.55em 0.9em;
  border-radius: var(--r-form, 0.75rem);
  border: 0.0625rem solid var(--hairline, currentColor);
  background: var(--glass-bg, transparent);
  min-width: 0;
}

.s-field > [aria-invalid="true"] {
  border-color: var(--wine, currentColor);
}

/* A hint that says what a field wants, referenced by aria-describedby. */
.s-hint {
  font-size: var(--fs-small, 0.8125rem);
  color: var(--ink-soft, inherit);
}

/* ------------------------------------------------------------------ motion */

/*
 * Respected, not decorative: for a user with vestibular sensitivity an
 * animation is not a flourish, it is a symptom. The shell adds no motion of its
 * own, so this is the standing rule for every screen built on it.
 */
@media (prefers-reduced-motion: reduce) {
  .s-app *,
  .s-app *::before,
  .s-app *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}
