/* ═══════════════════════════════════════════════════════════════════════════
   SHELL — the ground, and the parts an application needs.

   Everything here is ours and none of it is a component library: the handful
   of pieces any application has and a stylesheet never ships — a pane, a
   toolbar, a source list, a sheet, a segmented control — built out of rules
   and squares rather than cards and shadows.

   The organising idea is that there are only two ways to separate two things:
   put a rule between them, or put space between them. Nothing is separated by
   being a floating object, because a screen of floating objects has no grid.

   Class names are flat and unprefixed. They read as what they are in markup.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── ground ─────────────────────────────────────────────────────────────── */

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

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  min-height: 100dvh;
  background: var(--paper);
  color: var(--ink);
  font: 400 var(--t-15)/var(--lh-body) var(--font-ui);
  letter-spacing: var(--track-tight);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-wrap: break-word;
}

/* Headings are the grotesque at its heaviest and tightest. Nothing else on
   the screen is 700, so weight alone is enough to rank them — they need no
   colour, no rule and no size jump to be read as headings. */
h1, h2, h3, h4 {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 700;
  line-height: var(--lh-tight);
  letter-spacing: var(--track-tighter);
  color: var(--ink);
  text-wrap: balance;
}
h1 { font-size: clamp(1.5rem, 4.6vw, var(--t-32)); }
h2 { font-size: var(--t-24); }
h3 { font-size: var(--t-17); letter-spacing: var(--track-tight); }

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

a { color: inherit; text-decoration: none; }

button, input, select, textarea { font: inherit; color: inherit; }
button { cursor: pointer; background: none; border: 0; padding: 0; }

svg { flex: none; }
img, video { max-width: 100%; }

/* One focus ring for the whole application. A square offset ring in the
   institution's blue — visible on paper and on ink, and never removed: a
   back-office tablet gets used with a keyboard more often than anyone
   designing for it expects. */
:focus-visible {
  outline: var(--rule) solid var(--brand);
  outline-offset: 2px;
  border-radius: 0;
}

::selection { background: color-mix(in oklab, var(--brand) 22%, transparent); }

/* Utilities. There is no framework here to fight with, so these stay few. */
.mono     { font-family: var(--font-mono); font-variant-numeric: tabular-nums; letter-spacing: 0; }
.num      { font-variant-numeric: tabular-nums; }
.muted    { color: var(--ink-3); }
.grow     { flex: 1; min-width: 0; }
.spacer   { flex: 1 1 auto; }
.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.hide     { display: none !important; }

/* The caps label. It is the one piece of typography that appears on every
   screen: it names a section, a field, a column and a figure, and it is
   always the smallest thing in its group. */
.eyebrow {
  display: block;
  font: 700 var(--t-11)/1.4 var(--font-ui);
  letter-spacing: var(--track-caps);
  text-transform: uppercase;
  color: var(--ink-3);
}

/* ── the network line ───────────────────────────────────────────────────────
   The only moving thing in the interface. A 2px rule across the top of the
   viewport while the application is talking to the graph, in place of a
   spinner in the middle of the content: it says the system is working
   without taking the screen away from whoever is reading it.

   <div class="netline"></div> goes first inside <body>. */

.netline {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 500;
  height: var(--rule);
  pointer-events: none;
  overflow: hidden;
}
.netline::after {
  content: '';
  display: block;
  height: 100%;
  width: 100%;
  background: var(--brand);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform var(--slow) var(--ease), opacity var(--slow) linear;
  opacity: 0;
}
/* Indeterminate, because the store counts requests in flight and not bytes.
   It runs to 90% and waits there — a bar that reaches the end and keeps
   sitting is a bar that has lied. */
.netline.on::after {
  opacity: 1;
  transform: scaleX(.9);
  transition: transform 9s cubic-bezier(0, .7, .3, 1), opacity var(--fast) linear;
}
@media (prefers-reduced-motion: reduce) {
  .netline::after { transition: opacity var(--fast) linear; }
  .netline.on::after { transform: scaleX(1); }
}

/* ── the frame ──────────────────────────────────────────────────────────── */

.shell { display: block; }

@media (min-width: 60rem) {
  .shell {
    display: grid;
    grid-template-columns: var(--rail) minmax(0, 1fr);
    min-height: 100dvh;
  }
}

main {
  padding: var(--sp-5) clamp(var(--sp-4), 3vw, var(--sp-6)) calc(var(--tab-h) + var(--sp-8));
}
@media (min-width: 60rem) {
  main { padding: var(--sp-8) var(--sp-8) var(--sp-16); max-width: 84rem; }
}

/* A view appears. It does not slide, rise or stagger in: motion that carries
   no information is motion that has to be waited through every single time. */
.view { animation: appear .18s var(--ease) both; }
@keyframes appear { from { opacity: 0; } }
@media (prefers-reduced-motion: reduce) { .view { animation: none; } }

.stack { display: flex; flex-direction: column; gap: clamp(var(--sp-6), 4vw, var(--sp-10)); }

/* The section marker: a heavy rule, then the head sitting directly on it.
   This is what replaces the card. A run of sections down a page reads as a
   single ruled column, which is the grid made visible. */
.section-head {
  display: flex; align-items: baseline; justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--sp-2) var(--sp-4);
  padding-top: var(--sp-3);
  margin-bottom: var(--sp-4);
  border-top: var(--rule-heavy) solid var(--rule-ink);
}
.section-head h2, .section-head h1 { font-size: var(--t-20); }
.section-head > .eyebrow { flex-basis: 100%; }

.grid { display: grid; gap: clamp(var(--sp-4), 2.4vw, var(--sp-5)); }
.grid.two   { grid-template-columns: repeat(auto-fit, minmax(min(100%, 17rem), 1fr)); }
.grid.three { grid-template-columns: repeat(auto-fit, minmax(min(100%, 9.5rem), 1fr)); }
.grid.wide  { grid-template-columns: repeat(auto-fit, minmax(min(100%, 22rem), 1fr)); }
.row        { display: grid; gap: var(--sp-3); grid-template-columns: repeat(auto-fit, minmax(min(100%, 12rem), 1fr)); }

/* `.stagger` is kept as a hook because the views ask for it, and deliberately
   does nothing: things that arrive together were fetched together, and
   pretending otherwise is decoration. */
.stagger > * { animation: none; }

/* ── the rail ───────────────────────────────────────────────────────────────
   A source list, tablet and up. It is chrome, so it is drawn on the chrome
   grey rather than on paper: the rail and the work are two different kinds
   of surface, and a rule alone was asking the eye to remember which side of
   it it was on. The current entry is marked with a blue bar at its leading
   edge rather than a filled pill, because the mark should sit on the grid,
   not float in it. */

.rail { display: none; }

@media (min-width: 60rem) {
  .rail {
    display: flex;
    flex-direction: column;
    position: sticky;
    top: 0;
    height: 100dvh;
    padding: var(--sp-6) 0 var(--sp-4);
    border-right: var(--rule-hair) solid var(--hairline);
    background: var(--chrome);
    overflow-y: auto;
    overscroll-behavior: contain;
  }
}

.rail nav { display: flex; flex-direction: column; }

.rail nav a {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  min-height: 40px;
  padding: 0 var(--sp-5);
  font-size: var(--t-13);
  font-weight: 500;
  color: var(--ink-2);
  transition: color var(--fast) var(--ease), background var(--fast) var(--ease);
}
.rail nav a svg { width: 18px; height: 18px; opacity: .7; }
.rail nav a:hover { background: var(--chrome-2); color: var(--ink); }
.rail nav a[aria-current] { color: var(--ink); font-weight: 700; }
.rail nav a[aria-current]::before {
  content: '';
  position: absolute;
  left: 0; top: 0; bottom: 0;
  width: var(--rule-heavy);
  background: var(--brand);
}
.rail nav a[aria-current] svg { opacity: 1; }

.rail .foot { margin-top: auto; padding-top: var(--sp-3); border-top: var(--rule-hair) solid var(--hairline); }
.rail .who { display: flex; align-items: center; gap: var(--sp-3); padding: var(--sp-3) var(--sp-5); }
.rail .who b { display: block; font-size: var(--t-13); font-weight: 700; }
.rail .who small { display: block; font-size: var(--t-11); color: var(--ink-3); }

/* ── the mark ─────────────────────────────────────────────────────────────
   A blue square with the initial reversed out of it, then the name. The
   square is the seal: it is the only filled shape in the chrome, and it is
   the one place the institution signs its own interface. */

.mark { display: flex; align-items: center; gap: var(--sp-3); padding: 0 var(--sp-5) var(--sp-6); }
.mark .seal {
  width: 30px; height: 30px; flex: none;
  display: grid; place-items: center;
  background: var(--brand);
  color: #fff;
  font: 700 var(--t-17)/1 var(--font-display);
  letter-spacing: var(--track-tighter);
}
/* The words never wrap. "Universidad" broken across two lines as "Univers /
   idad" is not a logotype, and a bar that is one control too full will do
   exactly that rather than overflow. */
.mark .words { display: flex; flex-direction: column; line-height: 1.1; min-width: 0; }
.mark .words b, .mark .words span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.mark .words b {
  font: 700 var(--t-17)/1.05 var(--font-display);
  letter-spacing: var(--track-tighter);
}
.mark .words span {
  font: 700 var(--t-11)/1.4 var(--font-ui);
  letter-spacing: var(--track-caps);
  text-transform: uppercase;
  color: var(--ink-3);
}

/* ── the top bar ────────────────────────────────────────────────────────────
   One rule under it and nothing else. On a phone it is the only chrome above
   the content; on a tablet the rail carries the identity, so the mark drops
   out and the bar keeps the title and the controls.

   It is drawn on the chrome grey. White-on-white made the brightest band on
   the screen out of the one strip nobody is reading, and left the rule under
   it doing all the work of saying where the page began. A bar that is a
   different surface from the page does not need a rule to be a bar — the
   hairline stays anyway, but now it is a joint rather than a load-bearing
   wall. */

.topbar {
  position: sticky;
  top: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  min-height: 56px;
  padding: var(--sp-2) clamp(var(--sp-4), 3vw, var(--sp-6));
  border-bottom: var(--rule-hair) solid var(--hairline);
  background: var(--chrome);
}
@media (min-width: 60rem) {
  .topbar { min-height: 64px; }
  .topbar .mark { display: none; }
}
.topbar .mark { padding: 0; }
/* On a phone the mark and the title are both fighting for a 390px bar, and
   the tab bar at the bottom already says which screen this is. The identity
   wins: the rail is not there to carry it. */
.topbar h1 { display: none; font-size: clamp(var(--t-17), 4.4vw, var(--t-24)); }
@media (min-width: 60rem) { .topbar h1 { display: block; } }
/* With the title hidden there is nothing left to push the controls to the
   right, so on a phone the mark takes the slack. */
.topbar .mark { flex: 1; min-width: 0; }
@media (min-width: 60rem) { .topbar .mark { flex: none; } }
.topbar .sub { font-size: var(--t-12); color: var(--ink-3); }

/* ── the face selector ──────────────────────────────────────────────────────
   Which of the system's three faces is being looked through. A combo, not a
   list of links: what is selected is what is on screen, and choosing another
   entry changes the whole application rather than navigating within it.

   It is a real <select>, so it gets the platform's own menu — on a phone
   that is a full-height wheel a thumb can actually hit, which no menu built
   out of divs manages. */

.viewpick { position: relative; display: flex; align-items: center; flex: 0 1 13rem; min-width: 0; }
.viewpick select {
  min-height: 38px;
  width: 100%;
  /* Without this the select refuses to shrink past the width of its longest
     option, which on a phone pushes the mark out of the bar entirely. */
  min-width: 0;
  padding: 0 var(--sp-6) 0 var(--sp-3);
  border: var(--rule-hair) solid var(--hairline);
  /* `background-color`, not `background`: the shorthand would clear the
     chevron the select rule below paints, and this control would be the one
     menu in the application with no mark on it. */
  background-color: var(--paper);
  font: 700 var(--t-12)/1 var(--font-ui);
  letter-spacing: .01em;
  text-overflow: ellipsis;
}
.viewpick select:hover { border-color: var(--ink); }
/* On a phone the bar holds a mark, a face selector and two controls, and
   390px will not carry all four with the wordmark spelled out. The seal is
   the identity; the words are a courtesy the desk has room for and the
   phone does not. Only when the selector is actually there — a learner's
   bar has the space and keeps its name. */
@media (max-width: 48rem) {
  .topbar:has(.viewpick) .mark .words { display: none; }
}
@media (max-width: 30rem) { .viewpick { flex-basis: 9.5rem; } }

/* ── the tab bar ────────────────────────────────────────────────────────────
   The phone's navigation. Chrome grey like the rail it stands in for, a
   rule above it, and the current tab carries the same blue bar the rail
   uses — one marker, two places. */

.tabbar {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  z-index: 30;
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  padding-bottom: env(safe-area-inset-bottom);
  border-top: var(--rule-hair) solid var(--hairline);
  background: var(--chrome);
}
@media (min-width: 60rem) { .tabbar { display: none; } }

.tabbar a {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  min-height: var(--tab-h);
  font: 700 var(--t-11)/1 var(--font-ui);
  letter-spacing: .02em;
  color: var(--ink-3);
}
.tabbar a svg { width: 21px; height: 21px; }
.tabbar a[aria-current] { color: var(--ink); }
.tabbar a[aria-current]::before {
  content: '';
  position: absolute;
  top: 0; left: 0; right: 0;
  height: var(--rule-heavy);
  background: var(--brand);
}

/* ── pane ───────────────────────────────────────────────────────────────────
   A block of work, ruled off. Not a card: no shadow, no radius, no lift. It
   knows how to hold a header, a body and a footer without the body pushing
   the footer off the bottom. */

.pane {
  display: flex;
  flex-direction: column;
  min-height: 0;
  min-width: 0;
  border: var(--rule-hair) solid var(--hairline);
  background: var(--paper);
}

.pane-head, .pane-foot {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--sp-1) var(--sp-3);
  flex: none;
  padding: var(--sp-3) var(--sp-4);
}
.pane-head { border-bottom: var(--rule) solid var(--rule-ink); }
.pane-foot { border-top: var(--rule-hair) solid var(--hairline); }
.pane-title {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--t-15);
  font-weight: 700;
  letter-spacing: var(--track-tight);
}
.pane-sub { margin: 0; font-size: var(--t-12); color: var(--ink-3); }
.pane-body { flex: 1; min-height: 0; padding: var(--sp-4); }
.pane-body.is-flush { padding: 0; }

/* `.pad` is a pane used as a plain block — the common case. */
.pane.pad { padding: clamp(var(--sp-4), 3.4vw, var(--sp-5)); }

/* ── toolbar ────────────────────────────────────────────────────────────────
   Filters above a list. Ruled top and bottom rather than boxed, so it reads
   as a band across the column instead of a second object floating over it.
   Wraps on a phone rather than scrolling sideways: a filter you cannot see is
   a filter you do not know is applied. */

.toolbar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--sp-2);
  padding: var(--sp-3) 0;
  border-top: var(--rule-hair) solid var(--hairline);
  border-bottom: var(--rule-hair) solid var(--hairline);
}
/* A control in a toolbar is sized to what it holds, not to the column. A
   select of twelve hotel names stretched across 700px of screen is a target
   the width of the page for a menu the width of a word. */
.toolbar input, .toolbar select {
  min-height: 40px;
  width: auto;
  min-width: 9rem;
  flex: 0 1 16rem;
}
.toolbar input[type="search"] { flex: 1 1 14rem; }

/* ── controls ─────────────────────────────────────────────────────────────
   A button is a rectangle with a rule around it. The primary one is solid
   ink — black, not red, because the red is the institution's and an
   interface that shouts its own Save button has nothing left to say when
   something actually goes wrong. */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  min-height: 48px;
  padding: 0 var(--sp-4);
  border: var(--rule-hair) solid var(--rule-ink);
  background: var(--paper);
  font: 700 var(--t-13)/1 var(--font-ui);
  letter-spacing: .01em;
  color: var(--ink);
  white-space: nowrap;
  transition: background var(--fast) var(--ease), color var(--fast) var(--ease);
}
.btn svg { width: 16px; height: 16px; }
.btn:hover { background: var(--ink); color: var(--paper); }
.btn[disabled], .btn[aria-disabled="true"] { opacity: .38; pointer-events: none; }

.btn.primary { background: var(--ink); color: var(--paper); }
.btn.primary:hover { background: var(--ink-2); border-color: var(--ink-2); }

/* Ghost: no rule until it is wanted. Used for the icon-only controls in the
   bar, where five outlined squares in a row would be a fence. */
.btn.ghost { border-color: transparent; color: var(--ink-2); }
/* A ghost sits on the chrome as often as on the page, and the two grounds
   are only a step apart — so its hover is the chrome's own pressed step,
   which reads against both. `--paper-2` read as nothing at all in a bar. */
.btn.ghost:hover { background: var(--chrome-2); color: var(--ink); border-color: transparent; }

.btn.danger { color: var(--red); border-color: var(--red); }
.btn.danger:hover { background: var(--red); color: #fff; }

.btn.sm { min-height: 36px; padding: 0 var(--sp-3); font-size: var(--t-12); }
.btn.block { width: 100%; }

.btn-row { display: flex; flex-wrap: wrap; gap: var(--sp-2); }

/* A segmented control. One ruled track, the pressed segment reversed out of
   it in ink — a tab set, not a set of pills. */
.segments {
  display: flex;
  overflow-x: auto;
  scrollbar-width: none;
  border: var(--rule-hair) solid var(--hairline);
}
.segments::-webkit-scrollbar { display: none; }
.segments button {
  flex: none;
  min-height: 38px;
  padding: 0 var(--sp-4);
  border-right: var(--rule-hair) solid var(--hairline);
  font: 700 var(--t-12)/1 var(--font-ui);
  letter-spacing: .02em;
  color: var(--ink-3);
  white-space: nowrap;
  transition: background var(--fast) var(--ease), color var(--fast) var(--ease);
}
.segments button:last-child { border-right: 0; }
.segments button:hover { background: var(--paper-2); color: var(--ink); }
.segments button[aria-pressed="true"] { background: var(--ink); color: var(--paper); }

/* A chip is a label, not a lozenge: caps, ruled, square. The tone chips take
   their colour on the rule and the text rather than as a fill, so a row of
   them stays a row of labels instead of a row of sweets. */
.chip {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
  padding: 2px var(--sp-2);
  border: var(--rule-hair) solid var(--hairline);
  font: 700 var(--t-11)/1.6 var(--font-ui);
  letter-spacing: var(--track-caps);
  text-transform: uppercase;
  color: var(--ink-2);
  white-space: nowrap;
}
.chip svg { width: 13px; height: 13px; }
.chip.ok     { color: var(--good); border-color: var(--good); }
.chip.warn   { color: var(--warn); border-color: var(--warn); }
.chip.bad    { color: var(--bad);  border-color: var(--bad); }
.chip.accent { color: var(--accent, var(--brand)); border-color: var(--accent, var(--brand)); }

/* A person, as a square of initials. Round avatars are the one convention
   this design gives up without regret: nothing else here is a circle. */
.avatar {
  width: 38px; height: 38px; flex: none;
  display: grid; place-items: center;
  background: var(--paper-2);
  color: var(--ink-2);
  font: 700 var(--t-12)/1 var(--font-ui);
  letter-spacing: .02em;
}
.avatar.sm { width: 32px; height: 32px; font-size: var(--t-11); }

.divider { height: var(--rule-hair); background: var(--hairline); margin: var(--sp-6) 0; border: 0; }

/* ── fields ───────────────────────────────────────────────────────────────
   A caps label above a ruled box. The field is sunken rather than outlined
   on all four sides — one heavy rule under it would be more Swiss still, but
   a bottom-rule field on a touch screen has no obvious tap area. */

.field { display: flex; flex-direction: column; gap: var(--sp-2); margin-bottom: var(--sp-5); }
.field > label {
  font: 700 var(--t-11)/1.4 var(--font-ui);
  letter-spacing: var(--track-caps);
  text-transform: uppercase;
  color: var(--ink-2);
}
.field .help { font-size: var(--t-12); color: var(--ink-3); }

input[type="text"], input[type="email"], input[type="number"], input[type="date"],
input[type="search"], input[type="tel"], select, textarea {
  width: 100%;
  min-height: 48px;
  padding: var(--sp-2) var(--sp-3);
  border: var(--rule-hair) solid var(--hairline);
  background-color: var(--paper);
  color: var(--ink);
  font-size: var(--t-15);
  transition: border-color var(--fast) var(--ease), box-shadow var(--fast) var(--ease);
}
input:hover, select:hover, textarea:hover { border-color: var(--ink-2); }
input:focus, select:focus, textarea:focus {
  outline: none;
  border-color: var(--ink);
  box-shadow: inset 0 0 0 1px var(--ink);
}
textarea { min-height: 6rem; resize: vertical; line-height: var(--lh-body); }

/* A select is a menu, and it has to look like one. With `appearance: none`
   and no mark it is a text field that cannot be typed into — which is worse
   than a platform chevron, not better. The mark is drawn rather than
   fetched: one data URI, no request, no asset to lose. It cannot take
   `currentColor`, so the two schemes carry the two inks. */
select {
  appearance: none;
  padding-right: var(--sp-8);
  background-repeat: no-repeat;
  background-position: right var(--sp-3) center;
  background-size: 10px 7px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='7' viewBox='0 0 10 7'%3E%3Cpath d='M1 1.5l4 4 4-4' fill='none' stroke='%2355534e' stroke-width='1.6'/%3E%3C/svg%3E");
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) select {
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='7' viewBox='0 0 10 7'%3E%3Cpath d='M1 1.5l4 4 4-4' fill='none' stroke='%23b4b1a9' stroke-width='1.6'/%3E%3C/svg%3E");
  }
}
:root[data-theme="dark"] select {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='7' viewBox='0 0 10 7'%3E%3Cpath d='M1 1.5l4 4 4-4' fill='none' stroke='%23b4b1a9' stroke-width='1.6'/%3E%3C/svg%3E");
}
select[multiple] { padding-right: var(--sp-3); background-image: none; }

::placeholder { color: var(--ink-3); opacity: 1; }

.check { display: flex; align-items: center; gap: var(--sp-3); min-height: 48px; cursor: pointer; }
.check input { width: 20px; height: 20px; accent-color: var(--ink); flex: none; }
.check span { font-size: var(--t-15); }

/* ── sheet ──────────────────────────────────────────────────────────────────
   Bottom sheet on a phone, centred panel on a desk. It is the one element
   allowed a shadow, and only because it genuinely is above the page: without
   it the panel and the document behind it are the same white rectangle. */

.scrim {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: grid;
  align-items: end;
  background: color-mix(in oklab, var(--ink) 42%, transparent);
  animation: appear .14s both;
}
@media (min-width: 46rem) { .scrim { place-items: center; padding: var(--sp-8); } }

.sheet {
  width: 100%;
  max-height: 92dvh;
  overflow-y: auto;
  overscroll-behavior: contain;
  border-top: var(--rule-heavy) solid var(--rule-ink);
  background: var(--paper);
  box-shadow: 0 -2px 40px rgba(0, 0, 0, .18);
  padding: var(--sp-5) clamp(var(--sp-4), 4vw, var(--sp-6)) calc(var(--sp-6) + env(safe-area-inset-bottom));
  animation: sheet-up .18s var(--ease) both;
}
@media (min-width: 46rem) {
  .sheet {
    max-width: 42rem;
    border: var(--rule-hair) solid var(--rule-ink);
    border-top-width: var(--rule-heavy);
    box-shadow: 0 24px 60px rgba(0, 0, 0, .22);
    padding-bottom: var(--sp-6);
  }
}
@keyframes sheet-up { from { transform: translateY(16px); opacity: 0 } }
@media (prefers-reduced-motion: reduce) {
  .sheet, .scrim { animation: none; }
}

.sheet .head {
  display: flex; align-items: flex-start; gap: var(--sp-4);
  padding-bottom: var(--sp-4); margin-bottom: var(--sp-5);
  border-bottom: var(--rule-hair) solid var(--hairline);
}
.sheet .foot {
  display: flex; gap: var(--sp-2);
  margin-top: var(--sp-6); padding-top: var(--sp-4);
  border-top: var(--rule-hair) solid var(--hairline);
}
.sheet .foot .btn { flex: 1; }
.sheet .foot .btn.danger { flex: 0 0 auto; }

/* ── the banner ─────────────────────────────────────────────────────────────
   An administrator looking through somebody else's screen. Deliberately
   loud — a view that silently shows another person's data is how somebody
   mistakes it for their own — and loud here means reversed out of ink,
   because that is the one treatment nothing else on the page uses. */

.viewing {
  position: sticky;
  top: 56px;
  z-index: 19;
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-2) clamp(var(--sp-4), 3vw, var(--sp-6));
  background: var(--ink);
  color: var(--paper);
  font-size: var(--t-13);
}
@media (min-width: 60rem) { .viewing { top: 64px; } }
.viewing svg { color: var(--red); }
.viewing span { flex: 1; min-width: 0; }
.viewing b { font-weight: 700; }
.viewing .btn { border-color: color-mix(in oklab, var(--paper) 45%, transparent); color: var(--paper); background: transparent; }
.viewing .btn:hover { background: var(--paper); color: var(--ink); border-color: var(--paper); }

/* ── scroll ─────────────────────────────────────────────────────────────────
   No trough and no radius. The thumb is a bar of ink at low alpha. */

* { scrollbar-width: thin; scrollbar-color: color-mix(in oklab, var(--ink) 30%, transparent) transparent; }
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  border: 3px solid transparent;
  background-clip: content-box;
  background-color: color-mix(in oklab, var(--ink) 28%, transparent);
}
::-webkit-scrollbar-thumb:hover { background-color: color-mix(in oklab, var(--ink) 46%, transparent); }
