/* The component layer.
 *
 * Load after tokens.css and before the page's own sheet. Everything here is
 * --tau-* only, by rule: this file is the third stylesheet but must never
 * become a third vocabulary.
 *
 * What belongs here: a concept the app renders on more than one surface. The
 * split that produced two divergent vocabularies was style.css and report.css
 * each building the same eight concepts separately — a turn, a card, a meter,
 * a legend, a band, a sparkline, a confirmation, four dimensions. Those eight
 * are defined once, below. A page sheet may lay them out; it may not redefine
 * them.
 *
 * Sections
 *   1. micro-label      6. turn shell
 *   2. theme toggle     7. origin / provenance
 *   3. buttons          8. legend
 *   4. band + meters    9. trajectory sparkline
 *   5. card            10. avatar · field · confirm · offline
 *                      11. chip · dot · stat-tile · tray · list-row
 *                      12. global nav
 *                      13. rail
 *                      14. menu (dropdown + account menu)
 */

/* Bare utility, not a numbered section — needed here because report.html
   never loads style.css (where the workspace's own copy lives), yet the
   shared nav markup this file styles (.tau-nav-local) already depends on
   toggling this class from api.js's renderNavLocal. Without it, report.html
   had no rule for the class at all. */
.hidden { display: none !important; }

/* ---------- 1. micro-label ---------- */

/* Ten near-identical uppercase labels existed across the sheets at four sizes
   and three tracking values. One is enough, and it is the only place uppercase
   is sanctioned — anywhere else it reads as shouting. */
.eyebrow {
  font-size: var(--tau-text-xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: var(--tau-track-label);
  color: var(--tau-ink-label);
}

/* ---------- 2. theme toggle ---------- */

/* Sized to --tau-target rather than to its glyph. An icon-only control is the
   exact case where a comfortable-looking button ships at 20px. */
.theme-toggle {
  width: var(--tau-target);
  height: var(--tau-target);
  flex-shrink: 0;
  padding: 0;
  display: grid;
  place-items: center;
  /* Headers here align on the baseline; a grid-centred glyph would hang low. */
  align-self: center;
  border: 1px solid transparent;
  border-radius: var(--tau-r-pill);
  background: none;
  color: var(--tau-ink-soft);
  font-size: var(--tau-text-lg);
  line-height: 1;
  cursor: pointer;
  font-family: inherit;
}
.theme-toggle:hover {
  background: var(--tau-hover-surface);
  border-color: var(--tau-line);
  color: var(--tau-ink);
}

/* Login has no header to sit in. */
.theme-toggle-floating {
  position: fixed;
  top: var(--tau-s3);
  right: var(--tau-s3);
  z-index: 1;
}

/* Where no sibling already claims the free space with margin-left:auto. */
.theme-toggle-end { margin-left: auto; }

/* ---------- 3. buttons ---------- */

/* min-height is --tau-target rather than padding-derived, so a button can never
   ship under the floor by being given tighter padding later. */
.btn {
  font: inherit;
  font-size: var(--tau-text-md);
  font-weight: 600;
  line-height: 1;
  border: 1px solid transparent;
  border-radius: var(--tau-r-md);
  padding: 0 var(--tau-s4);
  min-height: var(--tau-target);
  display: inline-flex;
  align-items: center;
  gap: var(--tau-s2);
  cursor: pointer;
  transition: background var(--tau-dur-short) var(--tau-ease-standard), border-color var(--tau-dur-short) var(--tau-ease-standard), transform var(--tau-dur-short) var(--tau-ease-standard);
}
.btn:disabled { opacity: var(--tau-disabled); cursor: not-allowed; }
.btn:active:not(:disabled) { transform: scale(0.97); }

/* A raised primary action reads as "press me," not just "forest text on a
   rect" — the elevation scale below applies to the one button per screen
   that's actually the primary move, not to every button. */
.btn-primary {
  background: var(--tau-forest);
  color: var(--tau-on-forest);
  border-radius: var(--tau-r-pill);
  box-shadow: 0 3px 8px -3px color-mix(in oklab, var(--tau-forest) 45%, transparent);
}
.btn-primary:hover:not(:disabled) { background: var(--tau-forest-lift); }

.btn-quiet {
  background: var(--tau-surface);
  color: var(--tau-forest);
  border-color: var(--tau-line-strong);
}
.btn-quiet:hover:not(:disabled) { background: var(--tau-surface-2); }

/* The auditor button wears the auditor's hue because pressing it summons that
   voice — the only place a non-forest accent is allowed on a control. */
.btn-auditor {
  background: var(--tau-auditor-bg);
  color: var(--tau-auditor);
  border-color: color-mix(in oklab, var(--tau-auditor) 30%, transparent);
}
.btn-auditor:hover:not(:disabled) { border-color: var(--tau-auditor); }

/* 36px is under --tau-target, so this is for pointer-dense toolbars only —
   never for a primary action or a lone control on a phone view. */
.btn-sm { min-height: 36px; font-size: var(--tau-text-sm); padding: 0 var(--tau-s3); }

/* The confirm button on an irreversible action, and the only filled control in
   the app that isn't forest. Attention rather than caution: deleting an
   assignment is a real one-way action, not a conversation-starter — the
   caution/attention split this app holds everywhere else applies to the
   teacher's own actions too, not just to student signals. */
.btn-danger { background: var(--tau-attention); border-color: var(--tau-attention); color: var(--tau-surface); }
.btn-danger:hover:not(:disabled) { background: color-mix(in oklab, var(--tau-attention) 85%, black); border-color: color-mix(in oklab, var(--tau-attention) 85%, black); }

/* No fill, no border — but still a button (padding, rounded hover fill),
   not a bare underlined link, for an action that sits inline with real
   buttons elsewhere on the same row. */
.btn-tertiary { background: none; border-color: transparent; color: var(--tau-forest); }
.btn-tertiary:hover:not(:disabled) { background: var(--tau-hover-surface); }

/* ---------- 3b. icons ----------
   M3-style, self-hosted (icons.js) — see that file for why not the Material
   Symbols webfont. currentColor by default, so an icon inside a button or a
   coloured chip always matches its own text without a separate colour rule. */
.tau-icon { width: 16px; height: 16px; flex-shrink: 0; }

/* ---------- 3c. disclosure ----------
   Any content that's referenced repeatedly rather than read once (a rubric,
   a teacher's note, a long turn list folded behind "show all N") — a real
   label, not default browser <details> chrome, and forest/auditor-tinted
   even closed so it reads as "clickable" at rest, not just on hover. That
   at-rest tint is the point: a plain grey chip here would be indistinguishable
   from a static, non-interactive one (a stat chip, a flag label). Icon is a
   real chevron element (rotates on open), not a text glyph.
   Shared base + a colour modifier rather than near-identical copies per
   caller — the prompt (sage/forest) and a teacher's note (auditor) are the
   same interaction wearing a different voice. */
.acard-disclosure { margin: 9px 0 0; }
.acard-disclosure-prompt { --tau-disclosure-bg: var(--tau-origin-together-bg); --tau-disclosure-fg: var(--tau-forest); }
.acard-disclosure-note   { --tau-disclosure-bg: var(--tau-auditor-bg);         --tau-disclosure-fg: var(--tau-auditor); }
.acard-disclosure-toggle {
  cursor: pointer; user-select: none; list-style: none;
  display: inline-flex; align-items: center; gap: 7px;
  max-width: 100%;
  padding: 7px 12px;
  border-radius: var(--tau-r-pill);
  background: var(--tau-surface-2);
  font-size: 12.5px; font-weight: 600; color: var(--tau-disclosure-fg);
  transition: background var(--tau-dur-short) var(--tau-ease-standard), border-radius var(--tau-dur-short) var(--tau-ease-standard);
}
.acard-disclosure-toggle::-webkit-details-marker { display: none; }
.acard-disclosure-toggle:hover { background: var(--tau-surface-3); }
/* The note chip carries the auditor tint even closed, not just its text —
   colour is what signals "this carries weight" at a glance. The rubric chip
   stays neutral: it's not competing for attention, it's just recall. */
.acard-disclosure-note .acard-disclosure-toggle { background: var(--tau-auditor-bg); }
.acard-disclosure-note .acard-disclosure-toggle:hover { background: color-mix(in oklab, var(--tau-auditor-bg) 75%, var(--tau-ink) 8%); }
.acard-disclosure-icon { width: 12px; height: 12px; flex-shrink: 0; transition: transform var(--tau-dur-short) var(--tau-ease-standard); }
details[open] > .acard-disclosure-toggle .acard-disclosure-icon { transform: rotate(180deg); }
details[open] > .acard-disclosure-toggle {
  display: flex; width: 100%;
  background: var(--tau-disclosure-bg); color: var(--tau-ink);
  border-radius: var(--tau-r-md) var(--tau-r-md) 0 0;
}
.acard-disclosure p {
  margin: 0; padding: 12px 14px;
  background: var(--tau-disclosure-bg); border-radius: 0 0 var(--tau-r-md) var(--tau-r-md);
  font-size: var(--tau-text-md);
  line-height: 1.65; color: var(--tau-ink);
  animation: tau-reveal var(--tau-dur-medium) var(--tau-ease-decelerate);
}
.acard-disclosure-prompt p { font-family: var(--tau-font-serif); }

/* ---------- 4. band chip + meters ---------- */

/* Plain language leads; SAMR rides alongside in .band-sub, never inside the
   chip. The pip is a second channel so the band survives greyscale. */
.band {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: var(--tau-s1) 11px;
  border-radius: var(--tau-r-pill);
  font-size: 12.5px;
  font-weight: 600;
  box-shadow: inset 0 0 0 1px color-mix(in oklab, currentColor 16%, transparent);
}
.band .band-pip { width: 6px; height: 6px; border-radius: 50%; background: currentColor; opacity: 0.75; }
.band-1 { background: var(--tau-band-1-bg); color: var(--tau-band-1-fg); }
.band-2 { background: var(--tau-band-2-bg); color: var(--tau-band-2-fg); }
.band-3 { background: var(--tau-band-3-bg); color: var(--tau-band-3-fg); }
.band-4 { background: var(--tau-band-4-bg); color: var(--tau-band-4-fg); }
.band-sub { font-size: var(--tau-text-sm); color: var(--tau-ink-faint); }

/* Quiet variant — added 2026-07-29. Same colour + pip as .band-1..4, no chip
   chrome (fill/inset border). For a dense row (dashboard roster) that
   already carries a genuine actionable chip beside it — a SAMR band is
   classification, not something to act on, and giving it the same filled-
   pill weight as a real alert is what buried the alert in the first place.
   The pip still carries the colour for at-a-glance scanning; only the
   background/box-shadow that made it read as equally salient is dropped.
   With no fill, no border and (since 2026-07-29) no pip, this variant is
   plain text — so it is sized as text: it inherits its container's font-size
   instead of keeping .band's 12.5px, and drops to weight 500. In the roster
   it was rendering smaller than the uppercase column header above it, which
   made the row's most important sentence its least legible element.
   Consumers that want it smaller still set their own font-size. */
.band-plain { background: none; box-shadow: none; padding: 0; font-size: inherit; font-weight: 500; }

/* Same SAMR ramp as .band-1..4 above, applied as a border-left accent instead
   of a filled pill — for surfaces that want the level to read as a stripe on
   an existing element (a card, a chip, a collapsible row) rather than its own
   badge. One mapping, two treatments, both documented here — not a second
   copy of the ramp. Consumers set their own border-left-width. */
.samr-substitution { border-left-color: var(--tau-band-1-fg); }
.samr-augmentation { border-left-color: var(--tau-band-2-fg); }
.samr-modification { border-left-color: var(--tau-band-3-fg); }
.samr-redefinition { border-left-color: var(--tau-band-4-fg); }

/* Five discrete steps, because the engine emits 1–5. A continuous bar would
   claim a precision the model does not have — see the locked scoring decision. */
.steps { display: flex; gap: 3px; }
.steps i { flex: 1; height: 8px; border-radius: 2px; background: var(--tau-surface-3); }
.steps i.on { background: var(--tau-sage); }
.steps-sm i { height: 6px; }

/* The continuous meter is for quantities that genuinely are continuous — a
   budget consumed, a proportion of concepts. Never for a dimension score:
   .steps is the component for those. Unfilled track is a lighter step of the
   same ramp, so state reads across the whole bar rather than the fill floating
   on empty space. */
.meter-track {
  display: block;
  height: 7px;
  background: var(--tau-meter-track);
  border-radius: 4px;
  overflow: hidden;
}
.meter-fill { display: block; height: 100%; background: var(--tau-forest); border-radius: 4px; }

/* ---------- 4b. four dimensions ---------- */

/* The engine emits four dimensions at 1–5. This used to render as a
   four-column strip (name + score) sitting above a separate 2×2 grid (name
   again + explanation), so the name and the PQ/SU/CS/OC code each appeared
   twice and the score was split from the reasoning behind it. One quadrant
   grid now carries all three: name once, score as a single instrument,
   one callout line. No value-keyed colour anywhere in the block: a 2 must
   not render in the same red as an error, because a level is a position on
   a path, not a verdict. */
/* No overflow: hidden — a quad has no background of its own to clip against
   the rounded border, and hiding overflow here was clipping the dimension
   tooltip against the grid's own bounding box instead of letting it float
   above the card. */
.dim-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  border: 1px solid var(--tau-line);
  border-radius: var(--tau-r-md);
}
.dim-quad {
  padding: var(--tau-s4) var(--tau-s5);
  border-right: 1px solid var(--tau-line);
  border-bottom: 1px solid var(--tau-line);
  display: flex;
  flex-direction: column;
  gap: var(--tau-s3);
}
.dim-grid .dim-quad:nth-child(2n) { border-right: none; }
.dim-grid .dim-quad:nth-last-child(-n+2) { border-bottom: none; }
/* Zone 1 — the full name, said once. No PQ/SU/CS/OC badge: that was a code a
   reader had to learn, and the full name already fits on one line. The info
   button sits inline rather than on its own head row — same tooltip, no
   extra row height. */
.dim-quad-name {
  display: flex; align-items: center; gap: var(--tau-s2);
  font-size: var(--tau-text-lg); font-weight: 600; color: var(--tau-ink);
}
/* Zone 2 — the score as one instrument: the number and its five-pip .steps
   bar read as a single fact on one row, not stacked as two. Forest,
   uniformly, regardless of value — brand identity on the number's face, not
   a verdict painted onto a low score (the report hero's band pill is the one
   place score value gets its own colour). */
.dim-quad-score { display: flex; align-items: center; gap: var(--tau-s4); }
.dim-quad-score-n { display: flex; align-items: baseline; gap: 3px; flex-shrink: 0; font-variant-numeric: tabular-nums; }
.dim-quad-score-n .n { font-size: var(--tau-text-2xl); font-weight: 700; color: var(--tau-forest); line-height: 1; }
/* The denominator is never optional — a bare "2" is a mark, "/5" is a
   position. Same reason the total always carries its band. */
.dim-quad-score-n .of { font-size: var(--tau-text-sm); color: var(--tau-ink-faint); }
.dim-quad-steps { flex: 1; }
.dim-quad-steps i { height: 9px; }
/* Zone 3 — the one thing this dimension is calling out: a quiet evidence
   clause plus the coaching sentence, joined in one paragraph instead of two
   stacked ones. Same sage rule the old .dim-nudge used for "this is the
   takeaway" — now load-bearing instead of competing with a second paragraph
   above it. Neutral, and deliberately not keyed to the score: reinforcing
   and coaching nudges must not read as good/bad via colour. */
.dim-quad-callout {
  font-size: var(--tau-text-sm);
  line-height: var(--tau-leading-snug);
  color: var(--tau-ink);
  border-left: 2px solid var(--tau-sage);
  padding-left: 10px;
}
.dim-evidence { color: var(--tau-ink-soft); }

@media (max-width: 640px) {
  .dim-grid { grid-template-columns: 1fr; }
  .dim-grid .dim-quad:nth-child(2n) { border-right: none; }
  .dim-grid .dim-quad:nth-last-child(-n+2) { border-bottom: 1px solid var(--tau-line); }
  .dim-grid .dim-quad:last-child { border-bottom: none; }
}

/* ---------- 5. card ---------- */

/* Six card-shaped things existed across the sheets. The differences that
   actually carried meaning were the edge rules; everything else was drift. */
.card {
  background: var(--tau-surface);
  border: 1px solid var(--tau-line);
  border-radius: var(--tau-r-md);
  padding: var(--tau-s4) 18px;
  display: flex;
  flex-direction: column;
  gap: 9px;
  /* Depth, not just a boundary — a hairline says "here is an edge," a soft
     shadow says "this is raised off the page." Additive: the border stays,
     so nothing that already looked fine breaks. */
  box-shadow: var(--tau-shadow);
}
.card-lg { border-radius: var(--tau-r-lg); padding: var(--tau-s5) 22px; gap: var(--tau-s3); }

/* The one panel per screen that's the actual takeaway (the report's score,
   not its tabs) gets one more step of both radius and elevation, so shape
   itself signals "this is the important one" before anything is read. */
.card-hero { border-radius: var(--tau-r-xl); border-color: transparent; box-shadow: var(--tau-shadow-lift); }
/* Recessed rather than raised — for a block sitting inside another card. */
.card-quiet { background: var(--tau-surface-2); border-color: transparent; }
.card-grid { display: grid; gap: var(--tau-s4); grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); }

/* Urgency earns a rule on the card edge, not a recoloured card — a recoloured
   card makes an ordinary deadline look like an error state. An inset rule,
   not a border-left: .card is borderless-and-shadowed now, and a hard border
   sitting on top of a soft shadow was exactly the flat-vs-raised seam the
   depth pass was trying to erase everywhere else. Composes with the plain
   .card elevation (var(--tau-shadow)) rather than card-hero's, since edge
   classes are only ever applied to plain-tier cards today (the assignment
   card) — revisit if a hero-tier card ever needs an edge too. */
.card-edge-caution   { box-shadow: var(--tau-shadow), inset 3px 0 0 var(--tau-caution); }
.card-edge-attention { box-shadow: var(--tau-shadow), inset 3px 0 0 var(--tau-attention); }
/* The auditor's own form, squared on the ruled side. Matches .turn-auditor so
   the second voice looks the same wherever it speaks. */
.card-edge-auditor {
  border-left: 3px solid var(--tau-auditor);
  border-radius: 0 var(--tau-r-md) var(--tau-r-md) 0;
  background: var(--tau-auditor-bg);
}

.card-head { display: flex; align-items: baseline; gap: var(--tau-s3); flex-wrap: wrap; }
.card-head h3, .card-head h4 { font-size: var(--tau-text-md); font-weight: 600; }

/* ---------- 6. turn shell ---------- */

/* One conversation turn, wherever it is read: live in the chat, or replayed on
   the report and the teacher's transcript. Speaker label above the bubble
   rather than inside it, so a screen reader announces who is talking before
   what they said. */
.turn { display: flex; flex-direction: column; gap: 5px; max-width: 72%; }
.turn-speaker {
  font-size: 10.5px;
  letter-spacing: var(--tau-track-caps);
  text-transform: uppercase;
  color: var(--tau-ink-faint);
  font-weight: 600;
}
.msg {
  padding: 12px var(--tau-s4);
  border-radius: var(--tau-r-lg);
  line-height: var(--tau-leading-body);
  font-size: var(--tau-text-lg);
  white-space: pre-wrap;
  position: relative;
}

.turn-student { align-self: flex-end; align-items: flex-end; }
.turn-student .msg { background: var(--tau-forest); color: var(--tau-on-forest); border-bottom-right-radius: 4px; }

.turn-coach { align-self: flex-start; }
.turn-coach .msg { background: var(--tau-surface); border: 1px solid var(--tau-line); border-bottom-left-radius: 4px; }

/* The auditor is separated from the coach by form as well as hue: squared, a
   ruled edge, a monospace label, full width. Hue alone would not survive a
   colour-blind reader, and it must never look like a chat partner — it isn't one. */
.turn-auditor { align-self: stretch; max-width: 100%; }
.turn-auditor .turn-speaker { color: var(--tau-auditor); font-family: var(--tau-font-mono); letter-spacing: var(--tau-track-label); }
.turn-auditor .msg {
  background: var(--tau-auditor-bg);
  border-left: 3px solid var(--tau-auditor);
  border-radius: 0 var(--tau-r-sm) var(--tau-r-sm) 0;
  color: var(--tau-ink);
}
/* Permanent, undismissable framing: this is what makes "How am I doing?" safe
   to press. Remove it and the button becomes a score request. */
.auditor-disclaimer {
  display: block;
  margin-top: 7px;
  font-size: var(--tau-text-sm);
  color: var(--tau-ink-soft);
}

/* A turn the server has not confirmed yet. Dimmed, never removed — every turn
   is persisted as sent, so the state may say "not yet", never "lost". */
.turn-pending .msg { opacity: var(--tau-disabled); }

/* A turn a later edit replaced. Distinct from .turn-pending: this one did
   happen, it just isn't the version that counts anymore — struck through
   rather than dimmed, so the two states can't be mistaken for each other. */
.msg-superseded { opacity: 0.6; text-decoration: line-through; }
.queued { display: inline-flex; align-items: center; gap: 6px; font-size: var(--tau-text-xs); color: var(--tau-ink-faint); }
.queued i { width: 6px; height: 6px; border-radius: 50%; background: var(--tau-caution); flex: none; }

/* A pulled quote from a transcript, on the report or in a teacher's note. */
.quote {
  border-left: 2px solid var(--tau-sage);
  padding: var(--tau-s1) 0 var(--tau-s1) 14px;
  color: var(--tau-ink-soft);
  font-size: var(--tau-text-md);
  display: flex;
  flex-direction: column;
  gap: 5px;
}
.quote-src { font-size: var(--tau-text-xs); color: var(--tau-ink-faint); font-family: var(--tau-font-mono); }

/* ---------- 7. origin / provenance ---------- */

/* Authorship, not quality — so nothing in this ramp is ordered good-to-bad and
   no arrangement of it can accuse a student. Same you → together → coach
   vocabulary as the conversation map, so the two visuals teach one encoding.
   The hue sits in the fill and the text stays ink: sage and faint grey both
   fail contrast as text, and the chip's own words are the channel a
   colour-blind reader gets. */
.prov-bar { display: flex; height: 22px; border-radius: var(--tau-r-sm); overflow: hidden; }
.prov-you      { background: var(--tau-origin-you); }
.prov-together { background: var(--tau-origin-together); }
.prov-coach    { background: var(--tau-origin-coach); }

.origin-chip {
  font-size: var(--tau-text-xs);
  padding: 1px var(--tau-s2);
  border-radius: var(--tau-s2);
  white-space: nowrap;
  color: var(--tau-ink);
}
.origin-student-born, .origin-prior { background: var(--tau-origin-you-bg); }
.origin-synthesized { background: var(--tau-origin-together-bg); }
.origin-ai-born     { background: var(--tau-origin-coach-bg); }

/* ---------- 8. legend ---------- */

/* Three legends existed, one per visual. A legend is the same object every
   time: a row of samples, each sample paired with words. Swatch and rule are
   separate because a line-based chart must show dash and weight, not a block
   of colour — colour is never the only channel. */
.legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--tau-s2) var(--tau-s5);
  font-size: 12.5px;
  color: var(--tau-ink-soft);
}
.legend-item { display: inline-flex; align-items: center; gap: var(--tau-s2); }
.legend-swatch { width: 10px; height: 10px; border-radius: 2px; flex: none; }
.legend-item svg { flex: none; }
.legend-divider { width: 1px; height: 14px; background: var(--tau-line); flex: none; }

/* ---------- 9. trajectory sparkline ---------- */

/* Direction of travel, next to the total — the shape alone, no caption. A
   text delta ("+2 since draft 2") used to travel with this by locked rule;
   dropped because the hero eyebrow now names the draft and the ring shows
   the current score, so the line's own rise or fall was the one thing left
   to say and saying it twice added nothing. */
.traj { display: inline-flex; align-items: center; gap: var(--tau-s3); }
.traj-svg { width: 100%; height: 46px; overflow: visible; display: block; }
.traj-line { fill: none; stroke: var(--tau-sage); stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
/* Between two cycles the line is not a measurement, it is a gap. */
.traj-split { stroke: var(--tau-line); stroke-width: 1; stroke-dasharray: 2 3; }
.traj-dot { fill: var(--tau-sage); stroke: var(--tau-surface); stroke-width: 2; }
.traj-dot-now { fill: var(--tau-forest); }

/* ---------- 10. avatar · field · confirm · offline ---------- */

.avatar {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: var(--tau-surface-2);
  color: var(--tau-forest);
  display: grid;
  place-items: center;
  font-size: var(--tau-text-sm);
  font-weight: 700;
  flex-shrink: 0;
}
.avatar-sm { width: 26px; height: 26px; font-size: var(--tau-text-xs); }
.avatar-lg { width: 40px; height: 40px; font-size: var(--tau-text-md); }

.field { display: flex; flex-direction: column; gap: var(--tau-s1); }
.field > label { font-size: var(--tau-text-sm); color: var(--tau-ink-soft); }
.field input, .field textarea, .field select {
  font: inherit;
  font-size: var(--tau-text-md);
  color: var(--tau-ink);
  background: var(--tau-surface);
  border: 1px solid var(--tau-line-strong);
  border-radius: var(--tau-r-md);
  padding: 10px var(--tau-s3);
  min-height: var(--tau-target);
}
.field textarea { resize: vertical; }
.field input:focus, .field textarea:focus, .field select:focus { outline: none; border-color: var(--tau-forest); }
.field-help { font-size: var(--tau-text-xs); color: var(--tau-ink-faint); }

/* A persistent label for a field whose neighbour is close enough in meaning
   that a placeholder alone (which vanishes on focus) isn't enough to keep
   them apart — see product-design-review/references/content-design.md. */
.micro-label { font-size: var(--tau-text-xs); font-weight: 600; color: var(--tau-ink-soft); display: block; margin-bottom: 5px; }
.req-mark { color: var(--tau-caution); margin-left: 3px; }

/* The field that names the thing everywhere else in the app refers back to
   (assignment lists, notifications, the coach) outranks its plain-text
   siblings — borrows .tray-title's own size/weight convention rather than
   inventing a new one. See references/content-hierarchy.md layer 3. */
.title-field { font-size: var(--tau-text-lg); font-weight: 600; }

.eyebrow-row { display: flex; align-items: baseline; justify-content: space-between; gap: var(--tau-s3); }
.link-btn { font-family: inherit; font-size: var(--tau-text-xs); font-weight: 600; color: var(--tau-forest); background: none; border: none; padding: 0; cursor: pointer; }
.link-btn:hover { text-decoration: underline; }

/* Lowest-priority, optional content — pulled down in weight to match, so it
   doesn't compete visually with required fields above it. */
.eyebrow.deemphasized { color: var(--tau-ink-faint); }
textarea.deemphasized { border-color: var(--tau-line); color: var(--tau-ink-soft); }

/* New-assignment form — dashboard.html's modal is the only consumer now;
   teacher.js's own inline duplicate (the collapsible variant this rule used
   to also serve) was retired 2026-08-03 in favor of linking to this one. */
.new-assignment-form { display: flex; flex-direction: column; gap: 10px; }
.form-row { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
.form-row label { font-size: 13px; color: var(--tau-ink-soft); }
.level-selects { display: flex; flex-direction: column; gap: 6px; }
.draft-slot-row { display: flex; align-items: center; gap: 8px; }
.draft-slot-label { font-size: 13px; color: var(--tau-ink-soft); width: 52px; flex-shrink: 0; }

/* Containing the due-date/draft-budget/per-draft-rows group makes the
   causal relationship visible (changing the budget regenerates the rows
   below it) instead of leaving three loose controls with no visual link —
   see references/interaction-ux.md, "status visibility for causal
   relationships". */
.assign-schedule-panel { background: var(--tau-surface-2); border: 1px solid var(--tau-line); border-radius: var(--tau-r-lg); padding: var(--tau-s4); display: flex; flex-direction: column; gap: var(--tau-s3); }

.scrim {
  position: fixed;
  inset: 0;
  background: var(--tau-scrim);
  display: grid;
  place-items: center;
  padding: var(--tau-s4);
  z-index: 10;
}
/* Used for irreversible steps, which for a student means submitting a draft.
   The list is the component's whole point: it names every consequence,
   including the one nobody predicts — that the coach gets less helpful next
   round. Keep it a list; a paragraph hides the fourth item. */
.confirm {
  background: var(--tau-surface);
  border: 1px solid var(--tau-line);
  border-radius: var(--tau-r-lg);
  box-shadow: var(--tau-shadow-lift);
  width: min(460px, 100%);
  padding: 22px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.confirm h4 { font-size: 17px; font-weight: 600; }
.confirm p { font-size: var(--tau-text-md); color: var(--tau-ink-soft); }
.confirm ul {
  margin: 0;
  padding-left: 18px;
  font-size: var(--tau-text-md);
  color: var(--tau-ink-soft);
  display: flex;
  flex-direction: column;
  gap: 5px;
}
.confirm ul b { color: var(--tau-ink); font-weight: 600; }
.confirm-acts { display: flex; gap: 9px; justify-content: flex-end; flex-wrap: wrap; }
/* Widened variant for a confirm dialog whose content is more than a warning
   and two buttons — e.g. the submit-draft flow, which also holds an essay
   textarea. */
.confirm-lg { width: min(620px, 92vw); }

/* Caution, not attention: nothing is lost. Every turn is persisted server-side
   as sent, so this bar is allowed to promise the work is safe — and must, or
   a student retypes an answer the server already has. */
.offline-bar {
  display: flex;
  align-items: center;
  gap: var(--tau-s3);
  flex-wrap: wrap;
  padding: 10px 18px;
  background: var(--tau-caution-bg);
  color: var(--tau-caution);
  border-bottom: 1px solid var(--tau-line);
  font-size: var(--tau-text-sm);
}
.offline-bar b { font-weight: 600; }
.offline-bar .offline-act { margin-left: auto; }

/* ---------- 11. chip · dot · stat-tile · tray · list-row ---------- */

/* The generic status pill. Distinct from .band: .band always carries a pip
   and is reserved for the SAMR level; .chip is for everything else that used
   to improvise its own pill — submission status, a flag's category, an arc
   or reflection badge, a role label. Six independent copies of this same
   shape existed across dashboard.html and teacher.html before this section. */
.chip {
  display: inline-block;
  font-size: var(--tau-text-xs);
  font-weight: 600;
  padding: 2px var(--tau-s2);
  border-radius: var(--tau-r-pill);
  letter-spacing: 0.3px;
}
.chip-neutral   { background: var(--tau-band-3-bg); color: var(--tau-band-3-fg); }
.chip-grey      { background: var(--tau-surface-2); color: var(--tau-ink-soft); border: 1px solid var(--tau-line); }
.chip-positive  { background: var(--tau-positive-bg); color: var(--tau-positive); }
.chip-caution   { background: var(--tau-caution-bg); color: var(--tau-caution); }
.chip-attention { background: var(--tau-attention-bg); color: var(--tau-attention); }
/* An aggregate-score observation (e.g. "this class trails the others"), not
   a behavioural flag or a deadline problem — tool-info, not caution/
   attention, so it can't outrank either of those on the same row. */
.chip-tool-info { background: var(--tau-tool-info-bg); color: var(--tau-tool-info); }

/* A filled circle indicator. Colour rides on currentColor so a parent sets
   color: var(--tau-caution) etc. rather than this component making that
   call — the same caution dot means "worth a chat" in a table cell, a
   sidebar row, and a tray header, and must always be the one the voice
   rules already settled on (never --tau-attention for a behavioural signal). */
.dot { display: inline-block; width: 7px; height: 7px; border-radius: 50%; background: currentColor; flex: none; }
.dot-sm { width: 6px; height: 6px; }
/* The one colour a behavioural "worth a chat" signal is ever allowed —
   never --tau-attention, which is reserved for a real deadline problem. */
.dot-caution { color: var(--tau-caution); }
/* The real deadline problem itself — a closed assignment with a student who
   never submitted. Never used for a behavioural signal; see .dot-caution. */
.dot-attention { color: var(--tau-attention); }

/* Label/value pair in a tinted, bordered box — a count with what it counts.
   Colour is set by the consumer via style or a chip-* class reused here for
   the tint, so one tile serves a caution stat and a neutral one alike. */
.stat-tile {
  border-radius: var(--tau-r-md);
  padding: var(--tau-s4) var(--tau-s4);
  flex: 1;
  min-width: 110px;
}
.stat-tile-clickable { cursor: pointer; }
.stat-tile .stat-tile-val { font-size: var(--tau-text-2xl); font-weight: 800; line-height: 1; }
.stat-tile .stat-tile-label { font-size: var(--tau-text-xs); font-weight: 600; margin-top: 5px; opacity: 0.85; }

/* The right-sliding drawer. Mechanically distinct from .scrim/.confirm
   (transform-based slide-in vs. a centred grid) rather than a duplicate of
   it — for a detail view that wants to stay beside the list it drilled
   from, not interrupt it. */
.tray-overlay {
  display: none;
  position: fixed;
  inset: 0;
  background: var(--tau-scrim);
  z-index: 100;
}
.tray-overlay.open { display: block; }
.tray {
  position: fixed;
  top: 0;
  right: 0;
  width: 400px;
  height: 100%;
  background: var(--tau-surface);
  border-left: 1px solid var(--tau-line);
  box-shadow: var(--tau-shadow-lift);
  z-index: 101;
  display: flex;
  flex-direction: column;
  transform: translateX(100%);
  transition: transform var(--tau-dur-medium) var(--tau-ease-decelerate);
  overflow: hidden;
}
.tray.open { transform: translateX(0); }
.tray-header {
  display: flex;
  align-items: flex-start;
  gap: var(--tau-s3);
  padding: var(--tau-s5) var(--tau-s5) var(--tau-s4);
  border-bottom: 1px solid var(--tau-line);
  flex-shrink: 0;
}
.tray-title { font-size: var(--tau-text-lg); font-weight: 700; color: var(--tau-ink); letter-spacing: -0.2px; flex: 1; }
.tray-subtitle { font-size: var(--tau-text-xs); color: var(--tau-ink-soft); margin-top: 3px; }
.tray-close {
  background: none;
  border: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--tau-ink-soft);
  cursor: pointer;
  padding: 6px;
  border-radius: 4px;
  flex-shrink: 0;
}
.tray-close:hover { background: var(--tau-hover-surface); color: var(--tau-ink); }
.tray-body { flex: 1; overflow-y: auto; padding: var(--tau-s5); display: flex; flex-direction: column; gap: var(--tau-s5); }
.tray-section-label {
  font-size: 10px;
  font-weight: 700;
  color: var(--tau-ink-soft);
  text-transform: uppercase;
  letter-spacing: 0.8px;
  margin-bottom: 6px;
}
.tray-section-body { font-size: var(--tau-text-md); color: var(--tau-ink); line-height: 1.6; }
.tray-steps { display: flex; flex-direction: column; gap: var(--tau-s2); margin-top: 0; padding: 0; list-style: none; }
.tray-steps li { display: flex; gap: var(--tau-s3); font-size: var(--tau-text-md); color: var(--tau-ink); line-height: 1.5; }
.tray-step-num {
  font-size: var(--tau-text-xs);
  font-weight: 700;
  color: var(--tau-forest);
  background: var(--tau-surface-2);
  width: 20px;
  height: 20px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  margin-top: 1px;
}

/* Avatar + name/meta stack + trailing action — a non-tabular row of
   students, for the surfaces that want a list rather than the sortable
   table.roster component. Lighter weight, same underlying job. */
.list-row {
  display: flex;
  align-items: center;
  gap: var(--tau-s3);
  padding: 10px var(--tau-s4);
  cursor: pointer;
  transition: background var(--tau-dur-short) var(--tau-ease-standard);
}
.list-row:hover { background: var(--tau-hover-surface); }
.list-row-name { font-size: var(--tau-text-sm); font-weight: 600; color: var(--tau-ink); }
.list-row-meta {
  font-size: var(--tau-text-xs);
  color: var(--tau-ink-soft);
  margin-top: 1px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.list-row-action { font-size: var(--tau-text-xs); color: var(--tau-forest); font-weight: 600; flex-shrink: 0; }

/* Bordered/surfaced variant — three near-identical bordered rows on
   dashboard.html (class drill, assignment summary, submission rows) had
   reinvented this exact chrome independently before this modifier existed. */
.list-row-boxed {
  background: var(--tau-surface);
  border: 1px solid var(--tau-line);
  border-radius: var(--tau-r-sm);
  margin-bottom: 6px;
}
.list-row-boxed:last-child { margin-bottom: 0; }

/* ---------- 12. global nav ---------- */

/* The one element that's the same on every student screen — index.html's
   home and workspace views, and report.html. Only the breadcrumb and the
   local toggle change; the mark, spacing and identity slot never do. The
   IA is strictly hierarchical (Assignment -> Draft -> Conversation and/or
   Report), so this is a breadcrumb plus a *contextual* toggle, never a
   tab bar implying the three screens are peers a student can jump between. */
.tau-nav {
  display: flex;
  align-items: center;
  gap: var(--tau-s4);
  height: 52px;
  padding: 0 var(--tau-s5);
  background: var(--tau-surface);
  border-bottom: 1px solid var(--tau-line);
  flex-shrink: 0;
}
.tau-nav-mark {
  display: flex;
  align-items: center;
  gap: 8px;
  font-weight: 700;
  font-size: var(--tau-text-md);
  letter-spacing: -0.01em;
  color: var(--tau-ink);
  flex-shrink: 0;
}
.tau-nav-logo {
  height: 22px;
  width: auto;
  flex-shrink: 0;
  display: block;
}

/* Root and mid segments are plain or clickable; the current segment is the
   one place a student's eye should land — everything else stays quiet. */
.tau-nav-crumbs {
  display: flex;
  align-items: center;
  gap: 7px;
  min-width: 0;
  font-size: var(--tau-text-sm);
  color: var(--tau-ink-faint);
  overflow: hidden;
  white-space: nowrap;
}
.tau-nav-crumbs .crumb-link {
  border: none;
  background: none;
  padding: 0;
  font: inherit;
  color: var(--tau-ink-soft);
  cursor: pointer;
}
.tau-nav-crumbs .crumb-link:hover { color: var(--tau-forest); text-decoration: underline; }
.tau-nav-crumbs .crumb-label { color: var(--tau-ink-soft); }
.tau-nav-crumbs .crumb-sep { opacity: 0.5; }
.tau-nav-crumbs .crumb-current {
  color: var(--tau-ink);
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
}

.tau-nav-fill { flex: 1; min-width: var(--tau-s3); }

/* Scoped to one draft, never global — rendered only when a conversation and
   a report genuinely both exist for the draft currently on screen. Visually
   related to a segmented control, but it is state for "this draft," not a
   destination list for the whole app. */
.tau-nav-local {
  display: flex;
  gap: 3px;
  background: var(--tau-surface-2);
  padding: 3px;
  border-radius: var(--tau-r-pill);
  flex-shrink: 0;
}
.tau-nav-local-opt {
  border: none;
  background: none;
  padding: 5px 12px;
  border-radius: var(--tau-r-pill);
  font-size: 12px;
  font-weight: 600;
  color: var(--tau-ink-soft);
  cursor: pointer;
  transition: background var(--tau-dur-short) var(--tau-ease-standard),
              color var(--tau-dur-short) var(--tau-ease-standard),
              box-shadow var(--tau-dur-short) var(--tau-ease-standard);
}
/* Sage, not forest: this is wayfinding state ("which of the two views am I
   on"), not the primary action — forest stays reserved for the one thing
   that actually moves the student forward on a given screen. */
.tau-nav-local-opt.active {
  background: var(--tau-surface);
  color: var(--tau-sage);
  box-shadow: var(--tau-shadow);
  cursor: default;
}

/* Identity slot at the end of the nav. Self-contained rather than leaning
   on a page's own generic `button` reset — report.html has no such reset,
   which is exactly why this rendered as a bare native button there before
   it lived here. */
/* The slot the account menu mounts into. .account-name and .account-signout
   went with the flat name-plus-button strip this replaced — the only thing
   still borrowing .account-signout was report.html's Pattern Guide button,
   which is a button and now says so (.btn.btn-quiet.btn-sm). */
.account-chip { margin-left: auto; display: flex; align-items: center; gap: 10px; flex-shrink: 0; }

/* ---------- 13. rail ---------- */

/* The vertical rail, wherever one appears: the teacher's persistent nav, the
   admin's section list, the workspace's session list, the student's home
   rail. Four surfaces had built this independently and agreed on nothing —
   four widths (292/290/240/224), four grounds, four active states, and two
   of them shared the class name `.sidebar` for different objects, inert only
   because dashboard.html is the one page that doesn't load style.css.
   Three of those four active states argued their case in a code comment and
   the cases contradicted each other; none of the three knew the others
   existed.

   Three regions, because a rail's contents fall into three groups and only
   one of them scrolls: .rail-head (identity, search, the action that makes a
   new thing), .rail-body (the list, the only region that can overflow), and
   .rail-foot (pinned via margin-top:auto). A rail with no head or no foot
   just omits them. */
.rail {
  width: var(--tau-rail-w);
  min-width: var(--tau-rail-w);
  /* A step quieter than the content it frames — this is structure, not
     content. The same move style.css made for .sidebar/.dash-rail in the
     2026-07-26 pass, now the rule rather than two copies of it. */
  background: var(--tau-surface-2);
  padding: var(--tau-s4) var(--tau-s3);
  display: flex;
  flex-direction: column;
  gap: var(--tau-s5);
  min-height: 0;
}
.rail-head { flex-shrink: 0; display: flex; flex-direction: column; gap: var(--tau-s3); }
.rail-body { flex: 1; overflow-y: auto; min-height: 0; display: flex; flex-direction: column; gap: var(--tau-s5); }
/* Flex column, not a bare block — a foot holds stacked controls (the
   workspace's Submit draft, and anything that joins it) and they should fill
   the rail's width rather than shrink to their own text. */
.rail-foot { flex-shrink: 0; margin-top: auto; display: flex; flex-direction: column; gap: var(--tau-s2); }

/* A rail that doesn't run the full height of the viewport — admin's, which
   sits in a centred page grid and is shorter than its content. Left square
   and full-bleed it reads as a stray grey rectangle on the page ground, so
   this variant closes itself into a panel. Chrome, so it lives here and not
   in admin.html. */
.rail-inset { border-radius: var(--tau-r-md); }

.rail-group { display: flex; flex-direction: column; gap: 1px; }
.rail-group > .eyebrow { padding: 0 var(--tau-s3) var(--tau-s2); }

.rail-item {
  display: flex;
  align-items: center;
  gap: var(--tau-s3);
  width: 100%;
  /* Not advisory — two of the four rails this replaces shipped 34px and 36px
     rows against a token documented as a real minimum. */
  min-height: var(--tau-target);
  padding: var(--tau-s2) var(--tau-s3);
  border: none;
  border-radius: var(--tau-r-sm);
  background: none;
  font: inherit;
  color: var(--tau-ink);
  text-align: left;
  cursor: pointer;
  transition: background var(--tau-dur-short) var(--tau-ease-standard);
}
/* Hover darkens, selection lightens — two directions off the same ground, so
   the two states can never be mistaken for each other. --tau-hover-surface
   is unusable here: it resolves to --tau-surface-2, which is the rail's own
   background. */
.rail-item:hover { background: var(--tau-surface-3); }
.rail-item:focus-visible { outline: var(--tau-focus-ring); outline-offset: -2px; }

/* The selected row is the only white surface in the rail, so it reads as
   lifted out of the quiet ground and onto the same plane as the content it
   opens. Three channels — surface reversal, shadow, then colour — which is
   what lets the fill stay neutral. The rail this replaces on dashboard.html
   tinted its active row with `color-mix(--tau-forest 10%, --tau-surface)`,
   a seventh forest job the Hard Constraints don't allow; the tint existed
   because a neutral wash on a *white* rail is a near-invisible 3% lightness
   shift. On a toned rail the problem dissolves — white is a step up in
   lightness against surface-2, which is the contrast that rail wanted.
   Same construction as .tau-nav-local-opt.active above, which is the one
   place in the system that had already solved this. */
.rail-item.active {
  background: var(--tau-surface);
  box-shadow: var(--tau-shadow);
  cursor: default;
}

.rail-item-text { display: flex; flex-direction: column; gap: 1px; min-width: 0; flex: 1; }
.rail-item-name {
  font-size: var(--tau-text-sm);
  font-weight: 500;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.rail-item-sub {
  font-size: var(--tau-text-xs);
  color: var(--tau-ink-soft);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Right-aligned trailing value — a count, a due date, a status word. */
.rail-item-meta {
  margin-left: auto;
  flex-shrink: 0;
  font-size: var(--tau-text-xs);
  font-weight: 600;
  color: var(--tau-ink-faint);
  font-variant-numeric: tabular-nums;
}
/* Forest under its "interactive text" job. Not a forest fill: a fill marks
   the one action that moves the page forward, never wayfinding state. */
.rail-item.active .rail-item-name { color: var(--tau-forest); font-weight: 600; }
.rail-item.active .rail-item-meta { color: var(--tau-forest); }

/* A row that filters the row above it rather than being its own destination
   (Open under All assignments, Worth a chat under All students). Indented
   and a step down in size so it doesn't visually tie with what it filters. */
.rail-item-nested { padding-left: var(--tau-s6); min-height: 38px; }
.rail-item-nested .rail-item-name { font-size: 12px; }

/* Identity block — who you are, at the top of a rail that has one. */
.rail-identity { display: flex; align-items: center; gap: var(--tau-s3); min-width: 0; }
/* .avatar's own ground is --tau-surface-2, which is the rail's ground — on a
   rail it would be a circle you can't see. One step further down the ramp. */
.rail .avatar { background: var(--tau-surface-3); }
.rail-who { display: flex; flex-direction: column; min-width: 0; }
.rail-who-name { font-size: var(--tau-text-sm); font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.rail-who-sub { font-size: var(--tau-text-xs); color: var(--tau-ink-soft); }

/* Empty state for a .rail-body that has nothing in it yet. */
.rail-empty { font-size: var(--tau-text-sm); color: var(--tau-ink-soft); padding: var(--tau-s2) var(--tau-s3); }

/* ---------- 14. menu ---------- */

/* One dropdown, wherever one appears: the dashboard's "+ Add" picker and the
   account menu on all five surfaces. Promoted here from dashboard.html, which
   is where this vocabulary was written and where it was correct to live while
   exactly one page had a header menu.
 *
   The account menu was the piecemeal one: dashboard.html hand-built a real
   dropdown, while admin/index/report/teacher rendered a flat strip of a name
   and a Sign out button from api.js — no panel, no keyboard model, and no
   room for the destinations a person with two roles needs. One component, one
   behaviour (tauMenu in api.js), one set of items decided from /api/me.

   Behaviour lives in api.js, not here, because the WAI-ARIA menu-button
   pattern is keyboard work: this file only has to make sure the states that
   pattern sets — [aria-expanded], [hidden], :focus-visible — are visible. */

.menu-wrap { position: relative; }

.menu-trigger {
  display: inline-flex; align-items: center; gap: 6px; min-height: 36px; padding: 0 var(--tau-s3);
  border-radius: var(--tau-r-md); border: 1px solid var(--tau-line); background: var(--tau-surface);
  font: inherit; font-size: var(--tau-text-sm); font-weight: 600; color: var(--tau-ink); cursor: pointer;
  transition: background var(--tau-dur-short) var(--tau-ease-standard), border-color var(--tau-dur-short) var(--tau-ease-standard);
}
.menu-trigger:hover, .menu-trigger[aria-expanded="true"] { background: var(--tau-hover-surface); }
.menu-trigger[aria-expanded="true"] { border-color: var(--tau-line-strong); }
/* The "+ Add" trigger — the one primary action in the header — needs
   .btn-primary's forest fill, but .btn-primary and .menu-trigger are equal
   specificity, so whichever rule comes later in the cascade would silently
   win the whole property set. A dedicated modifier avoids that fight. */
.menu-trigger.menu-trigger-primary { background: var(--tau-forest); border-color: var(--tau-forest); color: var(--tau-on-forest); }
.menu-trigger.menu-trigger-primary:hover, .menu-trigger.menu-trigger-primary[aria-expanded="true"] { background: var(--tau-forest-lift); border-color: var(--tau-forest-lift); }
.menu-trigger.menu-trigger-primary .menu-chevron { color: var(--tau-on-forest); opacity: 0.85; }
.menu-chevron { color: var(--tau-ink-faint); transition: transform var(--tau-dur-short) var(--tau-ease-standard); font-size: 10px; }
.menu-trigger[aria-expanded="true"] .menu-chevron { transform: rotate(180deg); }

/* The account trigger. A pill rather than the squared .menu-trigger so the
   identity control never reads as one more action button in the row.
 *
   No initials monogram: the name is the identity, and a generated two-letter
   circle was decoration standing in for one. Padding is symmetric now that
   nothing round sits inside the left edge. */
.avatar-trigger {
  display: inline-flex; align-items: center; gap: 7px; min-height: 32px; padding: 4px 11px; border-radius: var(--tau-r-pill);
  border: 1px solid var(--tau-line); background: var(--tau-surface); cursor: pointer;
  font: inherit;
  transition: background var(--tau-dur-short) var(--tau-ease-standard);
}
.avatar-trigger:hover, .avatar-trigger[aria-expanded="true"] { background: var(--tau-hover-surface); }
.avatar-trigger:focus-visible { outline: var(--tau-focus-ring); outline-offset: 2px; }
.avatar-trigger .whoami-name {
  font-size: var(--tau-text-sm); font-weight: 600; color: var(--tau-ink);
  /* A long name shortens rather than pushing the header into a second row. */
  max-width: 22ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.menu-panel {
  position: absolute; top: calc(100% + 8px); right: 0; min-width: 240px;
  background: var(--tau-surface); border: 1px solid var(--tau-line); border-radius: var(--tau-r-md);
  box-shadow: var(--tau-shadow-lift); padding: 6px; z-index: 40;
  animation: tau-reveal var(--tau-dur-medium) var(--tau-ease-decelerate);
}
.menu-panel[hidden] { display: none; }
/* An opening panel is a position change, not information. Readers who ask for
   less motion get the panel, not the slide. */
@media (prefers-reduced-motion: reduce) {
  .menu-panel { animation: none; }
}

.menu-item {
  display: flex; align-items: flex-start; gap: 10px; width: 100%; text-align: left; padding: 9px 10px;
  border-radius: var(--tau-r-sm); border: none; background: none; font: inherit; font-size: var(--tau-text-sm);
  font-weight: 600; color: var(--tau-ink); cursor: pointer; text-decoration: none;
}
/* Author `display: flex` above beats the UA stylesheet's `[hidden] { display:
   none }`, so a plain `.hidden = true` on a .menu-item silently does nothing
   without this — same fix .roster-preview[hidden] already needed. Caught by
   actually testing the New Class success view's two shortcut buttons live,
   not by reading the code: both stayed visible after being marked done. */
.menu-item[hidden] { display: none; }
.menu-item:hover { background: var(--tau-hover-surface); }
/* Roving tabindex means the focused item is the only tabbable one, so focus
   is the sole indication of position in the menu — it has to be unmissable,
   and it must not rely on the hover wash alone to show it. */
.menu-item:focus-visible {
  outline: var(--tau-focus-ring);
  outline-offset: -2px;
  background: var(--tau-hover-surface);
}
.menu-item .mi-icon { flex-shrink: 0; width: 18px; text-align: center; margin-top: 1px; color: var(--tau-ink-soft); }
.menu-item .mi-body { display: flex; flex-direction: column; gap: 1px; }
.menu-item .mi-desc { font-weight: 400; font-size: var(--tau-text-xs); color: var(--tau-ink-faint); }

.menu-divider { height: 1px; background: var(--tau-line); margin: 5px 8px; }
.menu-account-head { padding: 8px 10px 10px; }
.menu-account-head .mn { font-size: var(--tau-text-sm); font-weight: 700; }
.menu-account-head .mr { font-size: var(--tau-text-xs); color: var(--tau-ink-faint); }
.menu-footnote { padding: 4px 10px 6px; font-size: var(--tau-text-xs); color: var(--tau-ink-faint); line-height: 1.45; }

/* ── Object action menus ────────────────────────────────────────────────────
   The second half of one system, not a second system. "+ Add" in the global
   header handles the case where the thing doesn't exist yet and has nothing to
   attach to; this handles the case where the object is the page you're on. Same
   tauMenu behaviour, same .menu-panel/.menu-item chrome, same icon set — the
   only difference is what the menu is anchored to, which is the distinction
   the two triggers below are shaped to carry. */

/* Icon-only trigger. Unlabelled on purpose: the object's own title sits
   immediately to its left and is already the label, which is what makes a
   global door labelled-and-primary and a local one small-and-adjacent. The
   accessible name comes from an aria-label naming that object.
   36px matches the .btn-sm row it sits in; ::after takes the hit region out to
   the real --tau-target minimum without inflating the visible box. */
.menu-trigger.menu-trigger-icon {
  width: 36px; min-height: 36px; padding: 0; justify-content: center; position: relative;
}
.menu-trigger.menu-trigger-icon::after {
  content: ''; position: absolute; top: 50%; left: 50%;
  width: var(--tau-target); height: var(--tau-target); transform: translate(-50%, -50%);
}
.menu-trigger.menu-trigger-icon:focus-visible { outline: var(--tau-focus-ring); outline-offset: 2px; }

/* Groups a menu's items by what kind of action they are — scoped creation
   above, management below. Only earns its place when a panel holds both
   kinds; a single-group panel takes no label, same rule .eyebrow follows. */
.menu-group-label {
  padding: 7px 10px 3px; font-size: 10px; font-weight: 650;
  text-transform: uppercase; letter-spacing: 0.07em; color: var(--tau-ink-faint);
}

/* A destructive item states it in colour as well as wording ("Delete
   assignment" — the object is named, per the labeling rule). Attention, not
   caution: this is a real irreversible action, not a conversation-starter. */
.menu-item.menu-item-danger { color: var(--tau-attention); }
.menu-item.menu-item-danger .mi-icon { color: var(--tau-attention); }
.menu-item.menu-item-danger:hover { background: var(--tau-attention-bg); }

/* The row an object page's actions live in — same corner of every
   .content-header, so "where do I do things to this?" is learned once. */
.obj-actions { display: flex; align-items: center; gap: var(--tau-s2); flex-shrink: 0; }

/* Appearance as a menu row. This is the real shared toggle markup — theme.js
   wires every `.theme-toggle` on the page generically — laid out as a row via
   a modifier, not a second toggle implementation.
 *
   §2 sizes .theme-toggle as a square icon button (fixed width/height, grid
   centring). Those have to be undone explicitly here, not just overridden by
   .menu-item's display: a stale `width: var(--tau-target)` would squeeze the
   label into a 44px column. Two classes beat one, so this wins on specificity
   wherever it lands in the cascade. */
.menu-item.theme-toggle {
  width: 100%;
  height: auto;
  min-height: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  border: none;
  border-radius: var(--tau-r-sm);
  font-size: var(--tau-text-sm);
  color: var(--tau-ink);
}
.menu-item.theme-toggle:hover { border-color: transparent; }
.menu-item .theme-toggle-icon { margin-left: auto; font-size: 14px; line-height: 1; }

/* A named destination that doesn't exist yet. Rendered rather than omitted so
   the gap is a stated limitation instead of a thing the reader assumes is
   hiding somewhere they haven't looked. */
.menu-future {
  display: flex; align-items: flex-start; gap: 10px; padding: 9px 10px; border-radius: var(--tau-r-sm);
  color: var(--tau-ink-faint); font-size: var(--tau-text-sm); font-weight: 600; cursor: not-allowed;
}
.menu-future .mi-desc { color: var(--tau-ink-faint); }
