/* Derived from mockups/inkwell.css at the close of the D17 mockup gate.
 * The mockup copy is now the historical artefact; changes go here. */

/* Inkwell admin console — mockup stylesheet.
 *
 * Shared by every file in mockups/ deliberately: these are the components the
 * design brief (docs/PROJECT.md §3a) says to "build once and reuse", so the
 * mockups should prove that reuse rather than each page re-inventing it. After
 * sign-off this file moves to service/static/inkwell.css essentially unchanged.
 *
 * Palette: Jamf Education's, with the hierarchy reversed (PROJECT.md D18).
 * Freshmaker mint is the primary action; the violets and purple are chrome and
 * identity; Celtic Blue is informational only. Layout and density still follow
 * §3a — the change here is colour and shape, not structure.
 */

:root {
  /* --- Brand -------------------------------------------------------------
   * Mint is deepened a touch from Freshmaker #50DEAF so it holds an edge
   * against a white surface instead of washing out.
   */
  --mint: #3ed0a3;          /* primary action */
  --mint-hover: #34bc92;
  --mint-tint: #e4f8f0;     /* mint pill background */
  --mint-ink: #0a6b54;      /* text on a mint tint */

  /* Mint is unreadable as text on white (1.7:1), so anything that is a *word*
   * rather than a surface uses this deep green instead. 5.1:1 on white. */
  --green-deep: #0b7d62;
  --green-deeper: #086450;

  --violet: #5050bc;        /* Blue Violet — identity, avatars, teacher tag */
  --violet-tint: #eeecfa;
  --violet-ink: #3f3b9e;

  --purple: #af52de;        /* Medium Purple — sparing highlight only */
  --purple-tint: #f6eafc;
  --purple-ink: #7b2ca8;

  --blue: #056ae6;          /* Celtic Blue — informational, never an action */
  --blue-tint: #e7f0fd;
  --blue-ink: #0a4fa6;

  --navy-deep: #100f2f;     /* Haiti — text on mint, deepest shade */

  /* --- Surfaces ----------------------------------------------------------
   * Faintly green-leaning off-whites, not neutral grey and no longer the
   * violet-leaning set these were. With a Pine rail, a lavender hairline meets
   * a green rail along the full height of the content edge, and that seam is
   * where a recolour gives itself away. Shifting the large flat surfaces into
   * the rail's hue family is what makes it read as one decision.
   * Near-black text is left alone — hue is imperceptible at body size.
   */
  --surface: #ffffff;
  --bg: #f5f8f6;            /* tinted panels, table headers */
  --row-hover: #f9fbfa;
  --border: #e3e9e6;
  --border-strong: #d0d9d5;

  /* --- Text. Left in the original cool-dark family: at body size a near-grey
   * carries no perceptible hue, so there is nothing here to re-tie to the rail.
   * --text-faint was #8B87A0 — 3.46:1 on white, which quietly failed AA on real
   * text ("Showing 6 of 6 users", the muted zero). Darkened to pass. */
  --text: #1b1930;          /* 15.9:1 */
  --text-muted: #5a5670;    /* 7.0:1  */
  --text-faint: #737090;    /* 4.7:1  */

  /* --- Status pills ------------------------------------------------------ */
  --amber-bg: #fdf3e2;  --amber-fg: #8a5a0b;
  --grey-bg:  #ebf0ee;  --grey-fg:  #4e5c57;   /* 6.1:1 */
  --red-bg:   #fdecec;  --red-border: #f3c4c4;  --red-fg: #a8201a;

  /* --- Sidebar rail — Pine (chosen, D19) -------------------------------
   * The dark end of Freshmaker: the mint hue taken to a deep value. Chosen
   * over the violets after seeing all five full-bleed on the sign-in page.
   *
   * Pine is green-on-green with the primary action, which costs the hue
   * separation the violets gave for free, so the active nav state is tuned
   * harder here than it needed to be on a violet rail — see .nav-item.is-active
   * and --rail-active-* below. Violet has not left the product, only the
   * chrome: avatars, teacher tags and inline code are still Blue Violet, so
   * "green acts, violet identifies" still holds inside the content area.
   *
   * Verified on #0C3A32: mint 6.4:1, rail text 8.1:1, white 12.6:1,
   * rail icons 5.4:1 (3:1 is the bar for non-text).
   */
  --rail-bg: #0c3a32;
  --rail-border: #155246;
  --rail-text: #b6d6cc;             /* 8.1:1 on the rail */
  --rail-text-strong: #ffffff;
  --rail-hover-bg: rgba(255, 255, 255, 0.09);
  /* Mint-over-pine is the same hue, so a 16% wash reads as "slightly lighter"
   * rather than "selected". Lifted to 24% and paired with a mint left edge so
   * the active page is unmistakable without shouting. */
  --rail-active-bg: rgba(62, 208, 163, 0.24);
  --rail-active-edge: var(--mint);
  --rail-active-text: #ffffff;
  --rail-active-icon: var(--mint);
  --rail-icon: #86b3a6;

  /* Rounder than §3a's 6–8px. This is most of the "less boring" — friendlier
   * shape, same density. */
  --radius: 8px;
  --radius-lg: 10px;

  /* 15px base, not 14px: the real constraint is legibility on a projector at
   * the back of a room (§3a density note). */
  --font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
          "Helvetica Neue", Arial, sans-serif;
}

/* The five comparison rails (Deep Violet, Haiti, Midnight Velvet, Deep Sea,
 * light tint) are deleted now Pine is chosen. The --rail-* indirection stays:
 * it is what kept the sidebar and the sign-in background a single decision,
 * and .auth-page still reads --rail-bg so the two cannot drift apart. */

* { box-sizing: border-box; }

body {
  margin: 0;
  font-family: var(--font);
  font-size: 15px;
  line-height: 1.45;
  color: var(--text);
  background: var(--surface);
  -webkit-font-smoothing: antialiased;
}

a { color: var(--green-deep); }

/* Inline code is one of the three jobs violet keeps inside the content area
 * (D19). Promoted from .disclosure-body to a base rule now that issuers, client
 * IDs and claim names appear in tables and helper text too. */
code {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.88em;
  color: var(--violet-ink);
}

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

.app {
  display: flex;
  min-height: 100vh;
}

.sidebar {
  width: 236px;
  flex: 0 0 236px;
  background: var(--rail-bg);
  border-right: 1px solid var(--rail-border);
  display: flex;
  flex-direction: column;
}

.brand {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 18px 16px 16px;
}

/* An ink drop, in mint. Cheap identity, on-theme for a product called Inkwell,
 * and not an emoji (which D16 rules out). */
.brand-mark { color: var(--mint); flex: 0 0 18px; }

.brand-name {
  font-size: 17px;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--rail-text-strong);
}

.brand-sub {
  font-size: 12.5px;
  color: var(--rail-text);
}

.brand-stack { min-width: 0; }
.brand-stack .brand-sub { display: block; margin-top: -1px; }

.nav {
  padding: 0 10px;
}

.nav-label {
  padding: 14px 8px 6px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--rail-text);
  opacity: 0.75;
}

.nav-item {
  position: relative;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 10px;
  margin-bottom: 2px;
  border-radius: var(--radius);
  font-size: 14px;
  color: var(--rail-text);
  text-decoration: none;
}

.nav-item:hover {
  background: var(--rail-hover-bg);
  color: var(--rail-text-strong);
}


.nav-item.is-active {
  background: var(--rail-active-bg);
  color: var(--rail-active-text);
  font-weight: 600;
}

/* Mint left edge on the active item. On a green rail the background wash alone
 * is too close in hue to read as a selection; the edge does the work and the
 * wash becomes support. Inset so it doesn't fight the rounded corners. */
.nav-item.is-active::before {
  content: "";
  position: absolute;
  left: 0;
  top: 6px;
  bottom: 6px;
  width: 3px;
  border-radius: 0 2px 2px 0;
  background: var(--rail-active-edge);
}

.nav-item .icon { color: var(--rail-icon); flex: 0 0 16px; }
.nav-item:hover .icon { color: var(--rail-text); }
.nav-item.is-active .icon {
  color: var(--rail-active-icon);
}

/* Signed-in admin at the bottom — both Goodnotes and Seesaw put it here. */
.sidebar-foot {
  margin-top: auto;
  padding: 12px 14px;
  border-top: 1px solid var(--rail-border);
  display: flex;
  align-items: center;
  gap: 10px;
}

.avatar {
  width: 28px;
  height: 28px;
  flex: 0 0 28px;
  border-radius: 50%;
  background: var(--violet);
  color: #fff;
  font-size: 11.5px;
  font-weight: 600;
  display: flex;
  align-items: center;
  justify-content: center;
}

.whoami { min-width: 0; flex: 1; }
.whoami-name { font-size: 13.5px; font-weight: 600; color: var(--rail-text-strong); }
.whoami-role { font-size: 12px; color: var(--rail-text); }

.signout {
  font-size: 13px;
  color: var(--rail-text);
  text-decoration: none;
}
.signout:hover { color: var(--rail-text-strong); text-decoration: underline; }

/* Signing out changes state, so it is a POST with a CSRF token (D31.10) — which
 * means a <button>. This makes one look like the plain link the mockup drew. */
.signout-form { margin: 0; }
button.signout {
  padding: 0;
  border: 0;
  background: none;
  font-family: inherit;
  cursor: pointer;
}

/* -------------------------------------------------------------- page layout */

.content {
  flex: 1;
  min-width: 0;
  background: var(--surface);
  display: flex;
  flex-direction: column;
}

.page-head {
  padding: 26px 32px 20px;
  border-bottom: 1px solid var(--border);
  display: flex;
  align-items: flex-start;
  gap: 24px;
}

.page-head-text { flex: 1; min-width: 0; }

.page-title {
  margin: 0;
  font-size: 21px;
  font-weight: 600;
  letter-spacing: -0.01em;
}

.page-desc {
  margin: 4px 0 0;
  font-size: 14px;
  color: var(--text-muted);
  max-width: 64ch;
}

.page-body {
  flex: 1;
  padding: 22px 32px 40px;
}

/* ------------------------------------------------------------------ buttons */

.btn {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  height: 36px;
  padding: 0 15px;
  border: 1px solid transparent;
  border-radius: var(--radius);
  font-family: var(--font);
  font-size: 14px;
  font-weight: 500;
  line-height: 1;
  cursor: pointer;
  text-decoration: none;
  white-space: nowrap;
}

/* Dark text, not white: white on mint is 1.95:1 and illegible. Navy on mint is
 * 9.5:1, and reading as a considered pairing rather than a recoloured blue
 * button is most of why this looks designed. */
.btn-primary {
  background: var(--mint);
  color: var(--navy-deep);
  font-weight: 600;
}
.btn-primary:hover { background: var(--mint-hover); }

.btn-secondary {
  background: var(--surface);
  border-color: var(--border-strong);
  color: var(--text);
}
.btn-secondary:hover { background: var(--bg); border-color: var(--text-faint); }

.btn-ghost { background: transparent; color: var(--text-muted); }
.btn-ghost:hover { background: rgba(27, 25, 48, 0.05); color: var(--text); }

/* Destructive confirm only. Solid red rather than a red outline: this button
 * is the last step of a flow the reader has already been warned about, so it
 * should look like the consequence. White on #A8201A is 7.3:1. */
.btn-danger {
  background: var(--red-fg);
  border-color: var(--red-fg);
  color: #ffffff;
}
.btn-danger:hover { background: #8d1a15; border-color: #8d1a15; }

.btn-sm { padding: 0 11px; height: 30px; font-size: 13px; }

/* Dimmed rather than hidden. A save button that vanishes when there is nothing
 * to save makes the bar's height jump as you type, and the greyed button is
 * what tells you where Save *will* be. */
.btn:disabled {
  background: var(--grey-bg);
  border-color: transparent;
  color: var(--text-faint);
  cursor: default;
}

.btn:focus-visible {
  outline: 2px solid var(--green-deep);
  outline-offset: 2px;
}

.btn-block { width: 100%; justify-content: center; }

/* ------------------------------------------------- tinted panel + stat tiles */

.panel {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 14px 16px;
}

.panel-title {
  font-size: 13px;
  font-weight: 600;
  color: var(--text-muted);
  margin-bottom: 8px;
}

.note {
  font-size: 13.5px;
  color: var(--text-muted);
  margin: 0;
}
.note + .note { margin-top: 8px; }
.note strong { color: var(--text); font-weight: 600; }

/* Stat tile row — Goodnotes' "At a glance" (§3a). One bordered card divided by
 * hairlines rather than four floating cards: four separate cards at this size
 * read as a dashboard, and four numbers don't earn one. Numbers stay --text;
 * mint is reserved for actions, not decoration. */
.stat-row {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.stat { padding: 13px 16px; min-width: 0; }
.stat + .stat { border-left: 1px solid var(--border); }

.stat-value {
  font-size: 25px;
  font-weight: 600;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
  line-height: 1.15;
}
.stat-label { margin-top: 1px; font-size: 13px; color: var(--text-muted); }
.stat-foot { margin-top: 7px; font-size: 12.5px; }

/* Inline text link. Mint is unreadable as a word on white (1.95:1), so every
 * link in the product is the deep green instead — 5.1:1. See D18. */
.link { color: var(--green-deep); text-decoration: none; }
.link:hover { color: var(--green-deeper); text-decoration: underline; }

/* White card holding a list rather than a table. */
.list-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 3px 16px;
}

/* Two independent lists side by side — what a restore replaces, and what it
 * refuses to touch (P6). This started as a two-column table and the render
 * showed why that was wrong: a table says its cells are related across a row,
 * and "Notebooks" has nothing whatsoever to do with "Console administrators".
 * Two cards say two lists, which is the truth. */
.split {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 16px;
}
.split .list-card { padding: 13px 16px 4px; }
.split .panel-title { margin-bottom: 2px; }

.fact-list {
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: 14px;
}
.fact-list li { padding: 11px 0; }
.fact-list li + li { border-top: 1px solid var(--border); }
.fact-title { font-weight: 600; }
.fact-desc {
  margin: 2px 0 0;
  font-size: 13px;
  color: var(--text-muted);
}

/* The setup checklist that used to live here is deleted (D23). A four-step
 * onboarding wizard is fiction in a product pretending to be an established
 * service — "most services will already be set up". The landing page carries a
 * recent-sign-ins preview and a help-link grid instead, which is what the
 * reference consoles actually put there.
 */

/* ------------------------------------------------------- help / docs links */

/* Every console in the §3a reference set has one of these. Here it also does a
 * second job: it is the only place on the landing page that names Seamless
 * Learning Access, so the thing being demonstrated is discoverable without the
 * chrome turning into a tutorial. */
.doc-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 10px;
}

.doc-card {
  display: block;
  padding: 13px 15px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  text-decoration: none;
  color: inherit;
}
.doc-card:hover { background: var(--bg); border-color: var(--border-strong); }

.doc-title {
  display: flex;
  align-items: center;
  gap: 7px;
  font-size: 14px;
  font-weight: 600;
  color: var(--green-deep);
}
.doc-title .icon { flex: 0 0 14px; color: var(--green-deep); }

.doc-desc {
  margin: 3px 0 0;
  font-size: 13px;
  color: var(--text-muted);
}

/* Canvas-style helper disclosure. Keeps the D5 caveat on the page without
 * letting a paragraph of identity-matching theory dominate it. */
.disclosure { margin-top: 10px; }

.disclosure > summary {
  list-style: none;
  cursor: pointer;
  font-size: 13.5px;
  font-weight: 500;
  color: var(--green-deep);
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.disclosure > summary::-webkit-details-marker { display: none; }
.disclosure > summary:hover { color: var(--green-deeper); }
.disclosure > summary .icon { transition: transform 0.12s ease; }
.disclosure[open] > summary .icon { transform: rotate(90deg); }

.disclosure-body {
  margin-top: 8px;
  padding-left: 22px;
  font-size: 13.5px;
  color: var(--text-muted);
  max-width: 78ch;
}
.disclosure-body p { margin: 0 0 8px; }
.disclosure-body p:last-child { margin-bottom: 0; }
.disclosure-body code {
  font-size: 12.5px;
  padding: 1px 4px;
  border-radius: 4px;
  background: var(--violet-tint);
  color: var(--violet-ink);
}

/* ------------------------------------------------------------------ toolbar */

.toolbar {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 20px 0 14px;
}

.toolbar-spacer { flex: 1; }

.search { position: relative; }

.search .icon {
  position: absolute;
  left: 11px;
  top: 50%;
  transform: translateY(-50%);
  color: var(--text-faint);
  pointer-events: none;
}

/* Typed selector, not `.search input` — the base input rule below sets
 * `padding` and would otherwise win on equal specificity. */
.search input[type="text"] { padding-left: 33px; width: 260px; }

input[type="text"],
input[type="password"],
input[type="email"],
input[type="url"],
input[type="number"],
select {
  height: 36px;
  padding: 0 11px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  background: var(--surface);
  font-family: var(--font);
  font-size: 14px;
  color: var(--text);
}

select { padding-right: 26px; }

/* Focus uses the deep green for the border (5:1 on white) with a mint halo.
 * Mint alone would be too faint to serve as a focus indicator. */
input:focus, select:focus {
  outline: none;
  border-color: var(--green-deep);
  box-shadow: 0 0 0 3px rgba(62, 208, 163, 0.35);
}

input::placeholder { color: var(--text-faint); }

/* Readonly gets the same treatment as disabled. They mean different things to a
 * form POST — readonly submits, disabled does not — but they look identical to
 * a reader, and the redirect URI has to submit nothing while still being
 * selectable so the Copy button has something to copy. */
/* Attribute selector, not :read-only — that pseudo-class also matches checkboxes
 * and radios, which are never user-editable in its sense, and they would pick up
 * a grey box they have no use for. */
input:disabled,
input[readonly] {
  background: var(--bg);
  color: var(--text-muted);
}

/* ------------------------------------------------------------ page sections */

/* More than one thing on a page — Notebooks carries classes too, Overview
 * carries sign-ins and help. Stacked sections rather than tabs: tabs would be
 * a new component and would hide half the page behind a click. */
/* Every sibling a section can follow needs listing explicitly, because the gap
 * belongs to the section rather than to whatever came before it. Missing
 * .table-card left the Classes heading sitting on the "Showing 20 of 20" line
 * of the table above it. */
/* Every sibling a section can follow has to be listed, because the gap belongs
 * to the thing that starts, not the thing that ended (D23.3). The .page-section
 * + .panel pair is the same rule running the other way: on Authentication the
 * visibility switch is a bare panel after the methods table, and without this it
 * sat 5px under the table and the two read as one block. */
.page-section + .page-section,
.panel + .page-section,
.table-card + .page-section { margin-top: 30px; }

.page-section + .panel,
.table-card + .panel { margin-top: 26px; }

/* And the same pair the other way round. Settings has a note straight above its
 * administrators table with no toolbar between them, which every other list page
 * happens to have — so the two sat 9px apart and read as one block. */
.panel + .table-card,
.panel + .split { margin-top: 20px; }

/* When a list is empty its toolbar is hidden, and the 34px of gap the toolbar
 * was providing goes with it — leaving the page's note panel 7px off the top of
 * the table, so the two read as one block. display:none does not take an element
 * out of adjacency, so .panel + .table-card above can't see past it and the fix
 * has to key off the state that hid it. Pre-existing on Users, Classes and
 * Notebooks; found by comparing the new Recent sign-ins empty state with theirs. */
/* When a list is empty its toolbar is hidden, and the 34px of gap the toolbar
 * was providing goes with it — leaving the page's note panel 7px off the top of
 * the table, so the two read as one block. display:none does not take an element
 * out of adjacency, so .panel + .table-card above can't see past it and the fix
 * has to key off the state that hid it. Pre-existing on Users, Classes and
 * Notebooks; found by comparing the new Recent sign-ins empty state with theirs.
 * In the templates the toolbar is rendered with the `hidden` attribute instead of
 * a page-state class, which keeps this adjacency chain exactly as it was. */
.toolbar[hidden] + .table-card { margin-top: 34px; }


/* .fed-section is gone: the two nine-field sections it was separating became
 * expandable cards (D26), so the page no longer has two long forms running into
 * each other. The gap problem it solved was a symptom of the shape, not the
 * spacing. */

/* Top-aligned, not bottom-aligned: the descriptions here run to two lines at
 * narrower widths, and a bottom-aligned button drifts away from the heading it
 * belongs to as the text rewraps. */
.section-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: 11px;
}

.section-head-text { min-width: 0; }
.section-head .section-desc { margin-bottom: 0; }

/* --------------------------------------------------------------- data table */

.table-card {
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface);
}

table.data {
  width: 100%;
  border-collapse: collapse;
}

table.data thead th {
  background: var(--bg);
  padding: 11px 16px;
  border-bottom: 1px solid var(--border);
  font-size: 12.5px;
  font-weight: 600;
  color: var(--text-muted);
  text-align: left;
  white-space: nowrap;
}

table.data thead th:first-child { border-top-left-radius: var(--radius-lg); }
table.data thead th:last-child { border-top-right-radius: var(--radius-lg); }

table.data tbody td {
  padding: 12px 16px;
  border-bottom: 1px solid var(--border);
  font-size: 14.5px;
  vertical-align: middle;
}

table.data tbody tr:last-child td { border-bottom: none; }
table.data tbody tr:hover td { background: var(--row-hover); }

/* Inline so a badge can sit beside the name without changing the row height. */
.cell-primary {
  display: flex;
  align-items: center;
  gap: 7px;
  font-weight: 600;
}

/* Smaller than a status pill, because it rides alongside a name rather than
 * owning a column. Violet, per "green acts, violet identifies" — console
 * privilege is identity, and it is the thing you scan a user list for. */
.badge {
  padding: 1px 7px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.02em;
  background: var(--violet-tint);
  color: var(--violet-ink);
  white-space: nowrap;
}

/* Secondary sub-line under the primary cell: email, then the identity source
 * the user last arrived through. Goodnotes' inline-suffix convention (§3a). */
.cell-sub {
  margin-top: 2px;
  font-size: 13px;
  color: var(--text-faint);
}
.cell-sub .src { color: var(--text-muted); font-weight: 500; }

.num { font-variant-numeric: tabular-nums; }
.muted { color: var(--text-faint); }

.col-actions { width: 48px; }
.col-narrow { width: 150px; }
.col-sm { width: 84px; }
.col-md { width: 178px; }
/* Width on the header as well as the cell. With max-width on the cell alone,
 * auto-layout still handed the column 640px and then truncated the text at 340,
 * leaving 300px of dead space mid-table — visible only in the render. */
.col-url { width: 460px; }

/* A long URL in a table cell. Issuers are the one value on the Authentication
 * page with no sane short form, and letting one wrap turns a three-row table
 * into a ragged five-line block — the same defect D23.3 found with person names,
 * arriving from the other direction. Ellipsis rather than nowrap, because unlike
 * a name a truncated URL is still useful: the host is the part you read. */
.trunc {
  max-width: 460px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Monospace but *not* violet. A whole cell of violet monospace reads as a link
 * you can click, which an issuer is not — the violet `code` colour is for
 * marking a literal inside a sentence, where there is surrounding prose to tell
 * it apart. Here the cell is the value, so it takes the data colour. */
.mono {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 12.5px;
  color: var(--text-muted);
}

/* A person's name in a narrow column. "Declan Thompson" wraps to two lines at
 * 150px, which grows the row by a line and leaves a twenty-row table with a
 * ragged rhythm — and it's the kind of raggedness that reads as a bug rather
 * than as long data. The table is auto-layout, so refusing to wrap lets the
 * column size itself to the longest name instead of guessing a width. */
.person { white-space: nowrap; }

.table-foot {
  padding: 11px 16px;
  border-top: 1px solid var(--border);
  font-size: 13px;
  color: var(--text-faint);
  border-bottom-left-radius: var(--radius-lg);
  border-bottom-right-radius: var(--radius-lg);
}

/* ------------------------------------------------------------------- pills */

.pill {
  display: inline-flex;
  align-items: center;
  padding: 3px 10px;
  border-radius: 999px;
  font-size: 12.5px;
  font-weight: 600;
  white-space: nowrap;
}

.pill-mint   { background: var(--mint-tint);   color: var(--mint-ink); }
.pill-violet { background: var(--violet-tint); color: var(--violet-ink); }
.pill-purple { background: var(--purple-tint); color: var(--purple-ink); }
.pill-blue   { background: var(--blue-tint);   color: var(--blue-ink); }
.pill-amber  { background: var(--amber-bg);    color: var(--amber-fg); }
.pill-grey   { background: var(--grey-bg);     color: var(--grey-fg); }

/* ------------------------------------------------------------ row kebab menu */

.row-menu { position: relative; }

.kebab {
  width: 30px;
  height: 30px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid transparent;
  border-radius: var(--radius);
  background: transparent;
  color: var(--text-faint);
  cursor: pointer;
}
.kebab:hover { background: var(--violet-tint); color: var(--violet-ink); }

/* Hide the default details marker on summary.kebab so the <details>-based row
 * menu looks identical to the mockup's kebab button. */
summary.kebab { list-style: none; }
summary.kebab::-webkit-details-marker { display: none; }
summary.kebab::marker { content: ""; }


/* The only elevation in the system. Cards stay hairline-only per §3a; an
 * overlay that floats over a table does need to read as floating. */
.menu {
  position: absolute;
  right: 0;
  top: 34px;
  z-index: 10;
  min-width: 188px;
  padding: 6px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: 0 10px 24px rgba(27, 25, 48, 0.14);
}

.menu-item {
  display: block;
  padding: 7px 9px;
  border-radius: 6px;
  font-size: 14px;
  color: var(--text);
  text-decoration: none;
  white-space: nowrap;
}
.menu-item:hover { background: var(--bg); }
.menu-item.is-danger { color: var(--red-fg); }
.menu-item.is-danger:hover { background: var(--red-bg); }

/* An action that exists but is refused, with the reason underneath. Shown rather
 * than hidden: a menu that silently loses an item teaches nothing, and "why
 * can't I delete this one?" is exactly the question worth answering in place. */
.menu-item.is-disabled {
  color: var(--text-faint);
  cursor: not-allowed;
}
.menu-item.is-disabled:hover { background: transparent; }

.menu-reason {
  margin: 0;
  padding: 0 9px 5px;
  font-size: 12px;
  line-height: 1.35;
  color: var(--text-faint);
  /* Narrower than the menu's own max-width so the reason wraps to two short
   * lines instead of stretching the overlay to the length of the sentence. */
  max-width: 190px;
  white-space: normal;
}

.menu-sep {
  height: 1px;
  margin: 6px 0;
  background: var(--border);
}

/* -------------------------------------------------------------- empty state */

.empty {
  padding: 46px 32px;
  text-align: center;
}

.empty-mark {
  color: var(--mint);
  margin-bottom: 10px;
}

.empty-title { font-size: 15px; font-weight: 600; }

.empty-desc {
  margin: 4px auto 16px;
  max-width: 46ch;
  font-size: 14px;
  color: var(--text-muted);
}

/* ------------------------------------------------------------ stacked form */

.form { max-width: 620px; }

/* A field whose content has a known short width — a page count, a port number.
 * A full-measure input for a two-digit number tells you to type a sentence. */
.input-sm { width: 110px; }

/* The same measure, for stacked fields that are not the whole page — the two
 * provider editors on Authentication sit under full-width tables and panels, so
 * the cap has to live on the fields rather than on a form wrapping everything.
 * A 1400px-wide text input is unreadable and looks unfinished. */
.field-stack { max-width: 620px; }

.field-row { margin-bottom: 18px; }

.field-row > label,
.field-legend {
  display: block;
  margin-bottom: 6px;
  font-size: 13px;
  font-weight: 600;
}

.field-row input[type="text"],
.field-row input[type="email"],
.field-row input[type="password"],
.field-row input[type="url"],
.field-row select {
  width: 100%;
}

.help {
  margin: 6px 0 0;
  font-size: 12.5px;
  color: var(--text-muted);
  max-width: 70ch;
}

.back-link {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  margin-bottom: 8px;
  font-size: 13px;
  color: var(--text-muted);
  text-decoration: none;
}
.back-link:hover { color: var(--green-deep); }

.radio-row { display: flex; gap: 20px; padding-top: 2px; }

.radio {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-size: 14px;
}
.radio input { accent-color: var(--green-deep); }

.check-list { display: grid; gap: 9px; padding-top: 2px; }

.check {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  font-size: 14px;
}
.check input { accent-color: var(--green-deep); margin-top: 3px; }
.check-sub { display: block; font-size: 13px; color: var(--text-faint); }

/* An input with exactly one action beside it. Three jobs on the Authentication
 * page: the read-only redirect URI with Copy, the discovery URL with Fetch, and
 * the masked client secret with Replace. The name is from the first of those and
 * has outlived it; the shape is the component. */
.copy-field { display: flex; gap: 8px; }
.copy-field input { flex: 1; min-width: 0; }
.copy-field .btn { flex: 0 0 auto; }

/* A disclosure that belongs to the field above it rather than to the page, so it
 * sits inside the field's bottom margin instead of adding to it. */
.disclosure-tight { margin: -10px 0 18px; }

/* Read-only name/value pairs — the endpoints a discovery document filled in.
 * Not a table: there are no column headers worth writing, and at four rows a
 * table's chrome costs more than it explains. */
.kv {
  display: grid;
  grid-template-columns: minmax(130px, auto) 1fr;
  gap: 4px 14px;
  margin: 0 0 10px;
  font-size: 13px;
}
.kv dt { color: var(--text-muted); }
.kv dd { margin: 0; min-width: 0; overflow-wrap: anywhere; }

/* ------------------------------------------------ federation card (D26) */

/* A federation is a summary row you click to reveal its configuration — the
 * Canvas pattern, and what Ant asked for after seeing two nine-field forms
 * stacked on one page. Built on <details>, so it opens and closes with no
 * JavaScript at all and converts to a Jinja template unchanged.
 *
 * It replaces a <table> of providers. A table cannot be a disclosure, and the
 * table was only ever earning its place as a summary — so the summary became
 * the thing you click, which is one component instead of two.
 */
.fed-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
}
.fed-card + .fed-card { margin-top: 12px; }

/* Grid rather than flex, so the name, issuer and status line up between the two
 * cards even though each one is a separate element. Columns match the table this
 * replaced: a flexible primary, a fixed issuer, a fixed status. */
/* The issuer column is 400px rather than the 460px the table used. At 460 the
 * truncated URL ran flush into the status pill with only the gap between them,
 * and a value touching a pill reads as one field. */
.fed-head {
  list-style: none;
  cursor: pointer;
  display: grid;
  grid-template-columns: 16px minmax(180px, 1fr) 400px 130px;
  align-items: center;
  gap: 16px;
  padding: 14px 16px;
  border-radius: var(--radius-lg);
}
.fed-head::-webkit-details-marker { display: none; }
.fed-head:hover { background: var(--row-hover); }

/* The whole row is the control, so the focus ring belongs to the row. */
.fed-card:focus-within .fed-head {
  box-shadow: inset 0 0 0 2px var(--green-deep);
}

.fed-chevron {
  color: var(--text-faint);
  transition: transform 0.12s ease;
}
.fed-card[open] .fed-chevron { transform: rotate(90deg); }

.fed-name { font-size: 15px; font-weight: 600; }
.fed-role {
  font-size: 13px;
  color: var(--text-muted);
  margin-top: 1px;
}

.fed-body {
  padding: 16px 16px 20px;
  border-top: 1px solid var(--border);
  margin-top: -1px;
}

/* ------------------------------------------- Seamless Learning Access block */

/* What a real service shows for an integration you have not switched on yet:
 * the logo, what it does for your school, a link to the documentation, and the
 * switch. It is the honest replacement for the switch that used to sit loose on
 * this page claiming to hide a thing that was visibly there (D26.2).
 */
.promo {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 16px 18px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
}
.page-section + .promo,
.fed-card + .promo { margin-top: 20px; }
.promo + .fed-card { margin-top: 12px; }

/* The real Seamless Learning Access app icon, at the size a feature card gives a
 * product mark. Not an invented glyph: this is the thing an administrator will
 * have seen in Jamf, and recognising it is the point. */
/* Size only. The asset already *is* a rounded white app-icon tile with its own
 * hairline, so the first attempt at a CSS tile put a tile inside a tile and the
 * mark came out looking small and boxed in — visible in the render, invisible in
 * the markup. The radius is kept to match the artwork in case the asset is ever
 * swapped for a square one.
 *
 * Worth knowing: the real mark is Haiti navy and Freshmaker mint, which is the
 * palette D18 adopted. Not a coincidence — both come from Jamf Education — but
 * it does mean the one piece of borrowed artwork on the page sits in the
 * console's own colours without being asked to. */
.promo-mark {
  flex: 0 0 auto;
  width: 46px;
  height: 46px;
  border-radius: 11px;
}

.promo-text { flex: 1; min-width: 0; }
.promo-title {
  font-size: 15px;
  font-weight: 600;
  margin-bottom: 2px;
}
.promo-desc {
  margin: 0;
  font-size: 13.5px;
  color: var(--text-muted);
  max-width: 78ch;
}

/* ------------------------------------------------------------------- switch */

/* The one genuinely new component this batch needed, and the only affordance in
 * the console that gets flipped while an audience watches. A 15px checkbox is
 * invisible on a projector at the back of a room, which is 3a's stated density
 * constraint, so this earns the extra CSS.
 *
 * Mint when on: the track is a *surface*, which is mint's correct job under D18,
 * and the knob is white on mint — fine, because a knob is not a word. */
.switch-row {
  display: flex;
  align-items: flex-start;
  gap: 14px;
}

.switch {
  flex: 0 0 auto;
  position: relative;
  display: inline-flex;
  align-items: center;
  width: 44px;
  height: 26px;
  margin-top: 1px;
}

.switch input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}

.switch-track {
  width: 44px;
  height: 26px;
  border-radius: 13px;
  background: var(--border-strong);
  transition: background 0.14s ease;
}

.switch-track::after {
  content: "";
  position: absolute;
  top: 3px;
  left: 3px;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: var(--surface);
  box-shadow: 0 1px 2px rgba(16, 15, 47, 0.25);
  transition: transform 0.14s ease;
}

.switch input:checked + .switch-track { background: var(--mint); }
.switch input:checked + .switch-track::after { transform: translateX(18px); }

.switch input:focus-visible + .switch-track {
  box-shadow: 0 0 0 3px rgba(62, 208, 163, 0.45);
}

.switch-text { min-width: 0; }

.switch-label {
  font-size: 14.5px;
  font-weight: 600;
  display: block;
  margin-bottom: 2px;
}

/* ------------------------------------------------------ form page wrapper */

/* Lets the sticky save bar be a sibling of .page-body — it carries the same
 * 32px horizontal padding, so it is built to span the content area — while
 * still sitting inside the <form> it submits. Without this the submit button
 * ends up outside its own form. */
.form-page {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
}

.form-sep {
  height: 1px;
  margin: 26px 0 22px;
  background: var(--border);
}

.section-title {
  margin: 0 0 4px;
  font-size: 15px;
  font-weight: 600;
}

.section-desc {
  margin: 0 0 16px;
  font-size: 13.5px;
  color: var(--text-muted);
  max-width: 70ch;
}

/* An action that ends a *section* rather than a form, so it gets the gap without
 * .form-actions' hairline — on Settings that rule drew a full-width line under
 * the restore cards, which read as a third section starting rather than as the
 * button belonging to the one above. */
.section-actions {
  display: flex;
  gap: 10px;
  margin-top: 20px;
}

/* Plain action row — the alternative to the sticky save bar on short forms. */
.form-actions {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 24px;
  padding-top: 20px;
  border-top: 1px solid var(--border);
}

/* Sticky save bar with a pending-change count — Seesaw's pattern (§3a).
   Deliberately unused in Phase 1: the user form is short enough to see whole,
   so a change count there is ceremony. Kept for Phase 2's Configuration →
   Authentication page, where several OIDC fields change together. */
.save-bar {
  position: sticky;
  bottom: 0;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 32px;
  background: var(--surface);
  border-top: 1px solid var(--border);
}

/* The count sits *after* the buttons, not pushed to the far right. Two reasons,
 * and the first only showed up in a render: a right-aligned action landed
 * underneath the mockup notes chip, which is fixed to the same corner. The
 * better reason is consistency — .form-actions is left-aligned on every other
 * form in the console, so a save bar that right-aligns is a second convention
 * for the same job. It also reads as one phrase: "Save changes · 3 pending". */
.save-count {
  flex: 0 1 auto;
  font-size: 13px;
  color: var(--text-faint);
}
.save-bar.is-dirty .save-count { color: var(--text-muted); }

/* ------------------------------------------------------------------- alerts */

.alert {
  padding: 10px 12px;
  border-radius: var(--radius);
  font-size: 13.5px;
  margin-bottom: 16px;
}

.alert-error {
  background: var(--red-bg);
  border: 1px solid var(--red-border);
  color: var(--red-fg);
}

/* Links and inline code inside an alert take the alert's colour. The deep green
 * links use on white, and the violet code uses, are both clashes on a red tint
 * and both lose most of their contrast — it read as another message leaking into
 * this one. Underline carries the link affordance instead. */
.alert a {
  color: inherit;
  text-decoration: underline;
}
.alert a:hover { text-decoration: none; }
.alert code { color: inherit; }

/* ------------------------------------------------- destructive confirm page */

/* A page, not a modal — same reasoning as the user form: a server-rendered,
 * CSRF-protected POST to its own URL is the honest shape and needs no
 * JavaScript. It also gives room to spell out what cascades, which a modal
 * never has. */
.confirm { max-width: 620px; }

.danger-panel {
  background: var(--red-bg);
  border: 1px solid var(--red-border);
  border-radius: var(--radius-lg);
  padding: 14px 16px;
}

.danger-title {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 14.5px;
  font-weight: 600;
  color: var(--red-fg);
}

/* What the cascade actually destroys. The point of the whole page. */
.impact {
  margin: 10px 0 0;
  padding: 0;
  list-style: none;
  font-size: 14px;
}
.impact li {
  display: flex;
  gap: 9px;
  align-items: baseline;
  padding: 5px 0;
}
.impact li + li { border-top: 1px solid var(--red-border); }
/* min-width so the description column lines up whether the count is 1 or 24.
 * The point of the count-first layout is that a glance reads the numbers as a
 * column; a ragged left edge on the text undoes that. */
.impact .impact-count {
  flex: 0 0 auto;
  min-width: 1.7em;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

/* For a cascade that is empty. Removing a notebook destroys nothing else, and
 * saying so in words rather than with a "0" is what makes the other variants'
 * long lists believable rather than boilerplate. It leads its sentence inline
 * rather than sitting in the count slot: in the render, words in that column
 * pushed the description out of line with the row above, which is exactly the
 * alignment the count-first layout exists to hold. */
.impact .impact-none {
  color: var(--text-muted);
  font-weight: 600;
}

/* The reassurance note sits under the warning, in the quieter tinted panel, so
 * the red block is read first and the "nothing changes in your IdP" second. */
.danger-panel + .panel { margin-top: 16px; }

/* ------------------------------------------------------------ sign-in page */

/* Shares the rail tokens so the sign-in field and the sidebar can never drift
 * apart — picking a rail colour picks this one too. */
.auth-page {
  min-height: 100vh;
  background: var(--rail-bg);
  display: flex;
  flex-direction: column;
}

/* The one place the brand gets to be loud: a dark violet field with the mint
 * mark on it. It is also the only page a partner sees before the console, so
 * it does the most work per pixel. */
.auth-bar {
  padding: 16px 24px;
  display: flex;
  align-items: center;
  gap: 9px;
}
.auth-bar .brand-mark { color: var(--mint); }
.auth-bar .brand-name { color: var(--rail-text-strong); font-size: 16px; }
.auth-bar .brand-sub { color: var(--rail-text); font-size: 13px; }

/* Deliberately not vertically centred in a void — D16 rules out the "single
 * centred card on an empty page" look. */
.auth-main { padding: 56px 24px; }

.auth-card {
  width: 100%;
  max-width: 400px;
  margin: 0 auto;
  padding: 26px;
  background: var(--surface);
  border-radius: 14px;
}

.auth-title { margin: 0 0 4px; font-size: 18px; font-weight: 600; }

.auth-desc {
  margin: 0 0 20px;
  font-size: 13.5px;
  color: var(--text-muted);
}

/* Inside the card, under the button: the D3 point about the console having its
 * own local account. Round 4 moved the Inkwell description into .auth-desc, so
 * this needed somewhere to live, and below the button is where real sign-in
 * forms put their "about this account" line anyway. */
.auth-note {
  margin: 14px 0 0;
  font-size: 12.5px;
  line-height: 1.45;
  color: var(--text-muted);
  text-align: center;
}

/* Attribution line under the card. The one place "demo harness" is allowed to
 * appear in the UI (D23): everywhere else Inkwell talks like a real service, but
 * the sign-in page should be honest about what this actually is. */
.auth-foot {
  max-width: 440px;
  margin: 16px auto 0;
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--rail-icon);
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: 0 6px;
}

/* The real Seamless Learning Access app icon, sized from the surrounding text
 * rather than in pixels, so it tracks the font and can never grow into a logo
 * wall. Deliberately small: this is a credit, not branding — D16 still says
 * Inkwell must not look like a Jamf product. */
.logo-inline {
  width: 1.3em;
  height: 1.3em;
  border-radius: 4px;
  vertical-align: -0.3em;
}


/* ------------------------------------------- Authentication: no-JS controls
 * Added in Phase 1 (docs D31.9). Two problems the mockup solved with scripting.
 *
 * A switch that has to change the page on one click. A checkbox only toggles; a
 * <button type=submit> toggles *and* submits, so the switch is a button laid over
 * the same track. `form=` on the button lets it submit its own tiny form while
 * sitting inside the page's big one (forms cannot nest). */
.switch button {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
  border: 0;
  background: transparent;
  opacity: 0;
  cursor: pointer;
}
.switch-track.is-on { background: var(--mint); }
.switch-track.is-on::after { transform: translateX(18px); }
.switch button:focus-visible + .switch-track {
  box-shadow: 0 0 0 3px rgba(62, 208, 163, 0.45);
}

/* Pressing Enter in a text field fires the form's *first* submit button, which on
 * this page would be Fetch. A real Save placed first, and kept off-screen, makes
 * Enter mean Save. Off-screen rather than display:none so browsers still treat
 * it as the default button. */
.default-submit {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/* Recent sign-ins (and any list page): `.toolbar { display: flex }` outranks the
 * browser's [hidden] rule, so a toolbar rendered with the hidden attribute still
 * showed. Restore it. */
.toolbar[hidden] { display: none; }


/* Ported from mockups/inkwell.css at the close of Phase 2's D17 gate (D33.7).
 * Everything below is the portal's; the mockup copy is the historical artefact and
 * changes go here. Deliberately NOT ported, because they only exist to flip states
 * in a static mockup and the real pages render one state at a time: the "Recently
 * updated" switch (body[data-recent]), the signed-out card's two-state toggle
 * (.when-auth-*) and the refusal page's five-state reveal (.fail-state — which is
 * display:none, so copying it would have hidden every refusal page). */
/* ======================================================================
 * THE PORTAL (Phase 2)
 * ======================================================================
 *
 * The end-user side: what a student or teacher at the school sees. It reuses
 * the console's shell, palette, type stack and density deliberately — same
 * product, two audiences — and then diverges in exactly three ways, because
 * Ant asked for an interface that reads less buttoned-up than the console:
 *
 *   1. Colour identifies a class. The console spends colour on *state*
 *      (amber Not configured, mint Connected) and keeps everything else
 *      hairline-and-white. Here a class carries a hue, which is what every
 *      school portal in the reference set does, and is a *new job* for
 *      colour rather than a breach of D18's "green acts, violet identifies"
 *      — class identity is not an action and not a status.
 *   2. Cards rather than tables, for classes and for your own notebooks. A
 *      table is the right shape for an administrator scanning twenty rows;
 *      a student has three classes and two notebooks, and a card can carry
 *      an icon, a colour and a count without becoming a dense grid.
 *   3. A warmer, larger greeting than any console page has.
 *
 * What is deliberately NOT different, because §3a and D16 still bind: the
 * system-UI type stack (no Inter), no gradients, no emoji icons, no shadows,
 * no oversized display type, and nothing violet or purple on a primary
 * button. The icons are the same 16px stroked set as the nav.
 */

/* --- Subject colour ---------------------------------------------------
 * Five subjects, five hues, assigned so that the classes any one person is
 * actually in are maximally distinguishable: Anika sees violet / amber /
 * green and Jak sees purple / amber. Set on the card as a class so the bar,
 * the icon mark and the chip all follow one declaration.
 *
 * Amber is the one reuse worth flagging: in the console it means "needs
 * attention" (Not configured, One of two connected). Here it is Spanish.
 * That is only safe because the portal has no status pills at all — nothing
 * on these pages is amber-because-something-is-wrong — so there is no second
 * meaning for it to collide with. If the portal ever grows a warning state,
 * this is the thing to move first.
 */
.sc-violet { --sc: var(--violet); --sc-tint: var(--violet-tint); --sc-ink: var(--violet-ink); }
.sc-purple { --sc: var(--purple); --sc-tint: var(--purple-tint); --sc-ink: var(--purple-ink); }
.sc-blue   { --sc: var(--blue);   --sc-tint: var(--blue-tint);   --sc-ink: var(--blue-ink); }
.sc-amber  { --sc: #b4690e;       --sc-tint: var(--amber-bg);    --sc-ink: var(--amber-fg); }
.sc-green  { --sc: var(--green-deep); --sc-tint: var(--mint-tint); --sc-ink: var(--mint-ink); }

/* --- Greeting --------------------------------------------------------
 * Bigger than .page-title's 21px and with the name carrying the weight. The
 * console's page heads label a *function* ("Users", "Authentication"); this
 * one addresses a person, and 21px semibold reads as a label rather than a
 * greeting. Still well short of §3a's "oversized display type" anti-pattern.
 */
.hello {
  margin: 0;
  font-size: 26px;
  font-weight: 600;
  letter-spacing: -0.02em;
}

/* Where the demo's whole claim becomes visible: the method this person
 * arrived through, next to the account it matched. §3a item 3 is the
 * precedent — Goodnotes shows the auth source inline beside each user. On the
 * iPad, via Seamless Learning Access, this same line reads SLA, and that is
 * the pairing the harness exists to demonstrate. */
.signed-as {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  margin: 9px 0 0;
  font-size: 13.5px;
  color: var(--text-muted);
}
.signed-as .mono { font-size: 13px; }

/* --- Dashboard tiles -------------------------------------------------
 * The console's .stat-row is one bordered card divided by hairlines, with a
 * deliberate note that "four numbers don't earn one [dashboard]". Three
 * numbers addressed to the person they are about earn a little more: separate
 * cards, an icon in the subject-neutral mint tint, and the value given room.
 * Same information architecture, warmer delivery.
 */
.port-tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 12px;
}

/* A link, not a panel. Without that the dashboard only tells you things the
 * sidebar already could; with it, it is the hub the three pages hang off. */
.port-tile {
  display: flex;
  align-items: center;
  gap: 13px;
  padding: 15px 17px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  text-decoration: none;
  color: inherit;
}
.port-tile:hover { background: var(--row-hover); border-color: var(--border-strong); }
.port-tile:focus-visible {
  outline: none;
  border-color: var(--green-deep);
  box-shadow: 0 0 0 3px var(--mint-tint);
}

.tile-mark {
  flex: 0 0 38px;
  height: 38px;
  display: grid;
  place-items: center;
  border-radius: var(--radius);
  background: var(--sc-tint, var(--mint-tint));
  color: var(--sc, var(--green-deep));
}

/* These are spans inside an anchor, so each one has to be told it is a block —
 * an inline element ignores the margins below it. */
.tile-text { display: block; min-width: 0; }

.tile-value {
  display: block;
  font-size: 27px;
  font-weight: 600;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
  line-height: 1.1;
}
.tile-label { display: block; margin-top: 1px; font-size: 13.5px; color: var(--text-muted); }

/* --- Card grid, shared by classes and notebooks ---------------------
 * Tracks are capped rather than 1fr, and the grid packs from the left. With
 * `minmax(…, 1fr)` three cards stretched across four tracks' worth of space
 * and left one empty column visibly hanging off the right, which reads as a
 * missing card; and two cards on My notebooks each became 590px wide, which
 * is not a card any more. Capped tracks plus start-packing is what a card grid
 * in any of §3a's references actually does — the whitespace on the right is
 * normal, a stretched card is not.
 */
.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(272px, 300px));
  justify-content: start;
  gap: 12px;
}

/* --- Class card ------------------------------------------------------
 * The whole card is one link, because a class has exactly one destination.
 * The colour arrives as a 4px top bar rather than a tinted card body: a
 * fully tinted card at this size fights the white content area and makes the
 * grid read as five alerts, which is the lesson D18's contrast work already
 * paid for. A bar plus a tinted icon mark is enough to make the grid scannable
 * by colour while the text stays on white at 15.9:1.
 */
.class-card,
.nb-card {
  position: relative;
  display: block;
  padding: 16px 17px 14px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  overflow: hidden;
}

/* Column flex so the foot can be pinned to the bottom. Grid stretches every
 * card in a row to the tallest one, but it does not move their *contents*, so
 * a card whose head wraps to two lines pushed its hairline down while its
 * neighbours' stayed put. Pinning the foot makes the hairlines line up across
 * a row by construction rather than by every card happening to be the same
 * shape — and the gap above it stays a minimum rather than an exact value. */
.class-card,
.nb-card { display: flex; flex-direction: column; }
.card-head { margin-bottom: 13px; }

.class-card { text-decoration: none; color: inherit; }
.class-card:hover { background: var(--row-hover); border-color: var(--border-strong); }
.class-card:focus-visible,
.nb-card:focus-within {
  outline: none;
  border-color: var(--green-deep);
  box-shadow: 0 0 0 3px var(--mint-tint);
}

.card-bar {
  position: absolute;
  inset: 0 0 auto;
  height: 4px;
  background: var(--sc);
}

.card-head {
  display: flex;
  align-items: flex-start;
  gap: 12px;
}

.card-mark {
  flex: 0 0 34px;
  height: 34px;
  display: grid;
  place-items: center;
  border-radius: var(--radius);
  background: var(--sc-tint);
  color: var(--sc);
}

.card-title {
  display: block;
  font-size: 15.5px;
  font-weight: 600;
  letter-spacing: -0.01em;
}

/* No subject sub-line. Every class name already contains its subject
 * ("English — Class of 2025"), and a sub-line that restates the line above it
 * is the tell of a template filling slots — the defect D23.3 found in the
 * admin Classes table and removed for the same reason. */
.card-sub {
  display: block;
  margin-top: 2px;
  font-size: 13px;
  color: var(--text-muted);
}

/* Counts sit on their own row under a hairline so the card has a readable
 * rhythm at a glance: who teaches it, then how much is in it.
 *
 * The min-height is not cosmetic padding. A pill is taller than a line of
 * 13px text, so a card carrying "1 of yours" had a taller foot than one
 * without, and in a row of three cards the baseline of "4 notebooks" sat
 * three pixels lower than its neighbours — visible in the render, invisible
 * in the markup, and the same class of defect as D23.3's ragged table rows.
 */
.card-foot {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: auto;
  padding-top: 11px;
  border-top: 1px solid var(--border);
  font-size: 13px;
  color: var(--text-muted);
}
.card-foot .spacer { flex: 1; }

/* "1 of yours" is plain text, not a pill, and that is D19.2's rule rather
 * than a spacing workaround — a pill is a *status* affordance, and a count of
 * how many of these are yours is an attribute. It also removes the defect that
 * sent me round this loop twice: a pill is 24px tall against the foot's 19.5px
 * line, so the one card without one had its text sitting 3px high. Two
 * attempts at a min-height guessed the pill's height and both missed. Plain
 * text cannot be the wrong height.
 *
 * In a *table* the same fact stays a pill, because there it is a scanning aid
 * across many rows, which is the job D23 already blessed pills for with SSO
 * and SLA. */
.card-foot .mine { color: var(--mint-ink); font-weight: 600; }

/* --- Notebook card ---------------------------------------------------
 * NOT one big link, unlike the class card, and the reason is structural
 * rather than aesthetic: this card has two destinations — the notebook and
 * its class — and an anchor cannot be nested inside another anchor. So the
 * card is a plain element with two real links in it, which also keeps both
 * reachable by keyboard in the order they are read.
 */
.nb-card:hover { background: var(--row-hover); border-color: var(--border-strong); }

.card-title a {
  color: inherit;
  text-decoration: none;
}
.card-title a:hover { text-decoration: underline; }

/* The title's anchor is stretched over the whole card, so the card is clickable
 * and *looks* it, while the markup stays one anchor per destination. In the
 * first render the title was the only hit area and nothing on a static screen
 * said so — "appear clickable" was the one thing in the brief the card failed.
 * The chip is lifted above the stretched layer so the second destination still
 * works; it has to come after in paint order, which is what z-index buys. */
.nb-card .card-title a::after {
  content: "";
  position: absolute;
  inset: 0;
}
.nb-card .card-foot .class-chip { position: relative; z-index: 1; }

/* The class a notebook belongs to, carrying that class's colour so the
 * notebook grid and the class grid agree without anything having to say so.
 *
 * Named `class-chip` and not `chip` because `index.html` already defines a
 * page-local `.chip` for the nine palette swatches, and a global `.chip` would
 * have given all nine padding and an inline-flex box. Nothing would have
 * failed; the signed-off palette page would just have quietly gone wrong. */
.class-chip {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  max-width: 100%;
  /* Asymmetric on purpose. With equal padding the box is balanced but the
   * *label* is not: the dot and its gap add 13px of visual air on the left,
   * so the word ends up crammed against the right edge — which is what it
   * looked like on screen. More padding after the text than before the dot
   * puts the label back in the middle of the pill. */
  padding: 2px 13px 2px 9px;
  border-radius: 999px;
  background: var(--sc-tint);
  color: var(--sc-ink);
  font-size: 12.5px;
  font-weight: 500;
  text-decoration: none;
}
.class-chip:hover { text-decoration: underline; }
.class-chip .dot {
  flex: 0 0 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--sc);
}

/* "Yours" — the one marker on these pages. Mint, because it is the only
 * positive-and-true thing a student's own notebook needs to say, and the
 * console has already spent mint tint on exactly that register. */
.pill-yours { background: var(--mint-tint); color: var(--mint-ink); }

/* --- Notebook details ------------------------------------------------
 * The page a notebook click lands on. It exists because of D28.1: a card that
 * visibly does not respond looks broken on a projector, and that rule has
 * already decided this question twice (D23.2's help cards, D26.3's guide
 * link).
 *
 * What it does NOT do is apologise for having no page content. The obvious
 * draft said "page content isn't part of this reference service", which is a
 * straight D23 violation — copy says what a feature does for a school, never
 * what the harness is for. So this is simply a notebook *details* page, which
 * is a page a real product has, and it shows everything Inkwell actually
 * knows. Nothing is promised, so nothing is missing. The explanation belongs
 * in a presenter's mouth, not in the interface.
 */
.nb-detail { max-width: 620px; }

/* The notebook's own title is the page title. The first render had a
 * page-head reading "Notebook" above a card reading "Vocabulario — Unidad 3",
 * which is two headings where one is doing the work — and the same page then
 * stated the class twice, once as a chip and once as a row in the list below.
 * Both are the "template filling slots" tell D23.3 and D25.8 keep finding. */
.head-row {
  display: flex;
  align-items: flex-start;
  gap: 14px;
}

.nb-detail-sub {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 7px;
}

/* A notebook title inside a table cell. The browser's default underline on
 * thirteen rows of links turns the first column into a block of blue-book
 * ruling and pulls the eye away from the data; the console's own tables have
 * no links in them at all, so there was no existing rule to inherit. Underline
 * on hover only, which is the convention every table in §3a's reference set
 * uses. */
td .cell-primary a {
  color: var(--green-deep);
  text-decoration: none;
}
td .cell-primary a:hover { color: var(--green-deeper); text-decoration: underline; }

/* The page head of a class or a notebook carries that class's colour, so
 * arriving from a violet card does not land on a colourless page. Without it
 * the subject identity stopped dead at the click, which made the colour look
 * decorative rather than meaningful. */
.head-mark {
  flex: 0 0 40px;
  height: 40px;
  display: grid;
  place-items: center;
  border-radius: var(--radius);
  background: var(--sc-tint);
  color: var(--sc);
}

/* The portal's signed-out cards are centred in the viewport; the console's
 * sign-in stays top-weighted at its signed-off 56px. Not a whim — the console
 * card is tall (two fields, a button and a note) so it fills the space, while
 * a card holding one button and two lines leaves a screen of empty rail under
 * it and reads as a page that has not finished loading. Scoped with a class
 * rather than changed globally, because the console's sign-in is signed off
 * and this is not the moment to move it. */
.auth-page.is-portal .auth-main {
  flex: 1;
  display: grid;
  align-content: center;
  justify-items: center;
}

/* `.alert` carries only a bottom margin, because on every page that has one so
 * far something sits above it — a description, a field. On a refusal card the
 * alert follows the title directly, and the title's own 4px left them looking
 * like one block. The gap belongs to the thing that starts (D23.3). */
.auth-title + .alert { margin-top: 14px; }

/* The gap belongs to the section that starts, not to whatever ended — D23.3's
 * second defect, which recurs every time a *new* kind of element can precede a
 * section. The dashboard's tile row was exactly that: "Recently updated" sat
 * 16px under the tiles and read as their caption. Listing every possible
 * preceding sibling is the whole fix, and the whole lesson. */
.port-tiles + .page-section { margin-top: 30px; }
