/* AIA platform stylesheet — layered: element defaults, app shell, components,
   form primitives, page-layout helpers, legacy (pre-sweep) classes, media queries.
   Every rule traces to a token (tokens.css) or the Case Detail hi-fi mock. */

/* ============================== 1. Element defaults ============================== */

* {
  box-sizing: border-box;
}

html, body {
  margin: 0;
  padding: 0;
}

/* Reserves the scrollbar's track width whether or not it's needed, so
   toggling between viewport-short and viewport-tall content (e.g. the
   notifications "Unread only" filter hiding rows) never shifts the page
   horizontally. */
html {
  scrollbar-gutter: stable;
}

body {
  font-family: var(--font-sans);
  font-size: var(--text-base);
  line-height: var(--text-line-height);
  color: var(--color-text);
  background: var(--color-bg);
}

h1, h2, h3, h4, h5, h6 {
  font-family: inherit;
}

a {
  color: var(--color-link);
}

a:hover {
  color: var(--color-link-hover);
}

input, textarea, select, button {
  font-family: inherit;
}

[x-cloak] {
  display: none !important;
}

:focus-visible {
  outline: var(--focus-ring);
  outline-offset: 2px;
}

.skip-link {
  position: absolute;
  left: -999px;
  top: auto;
  width: 1px;
  height: 1px;
  overflow: hidden;
  z-index: 100;
}

/* Visually hidden: keeps a label in the DOM (and the accessibility tree) while
   removing it from sighted layout -- used where the mock shows no visible
   label (e.g. the activity composer's avatar + placeholder treatment). */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.skip-link:focus-visible {
  position: fixed;
  left: var(--space-4);
  top: var(--space-4);
  width: auto;
  height: auto;
  padding: var(--space-2) var(--space-4);
  background: var(--nav-bg);
  color: var(--mint);
  border-radius: var(--radius-md);
  outline: var(--focus-ring);
}

/* ============================== 2. App shell (Task 2) ============================== */

.app-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: 68px;
  padding: 0 var(--space-12);
  background: var(--nav-bg);
  position: relative;
}

.app-bar__left {
  display: flex;
  align-items: center;
  gap: var(--space-10);
  min-width: 0;
}

.app-bar__brand {
  display: flex;
  align-items: center;
  line-height: 0;
  flex: none;
}

.app-bar__brand img {
  display: block;
  /* Half the 68px bar height: the wordmark's stacked text needs the size to stay
     legible, and the brand reads as the anchor rather than another nav item. */
  height: 34px;
  width: auto;
}

.app-bar__nav ul {
  display: flex;
  align-items: center;
  gap: var(--space-6);
  list-style: none;
  margin: 0;
  padding: 0;
}

.app-bar__item {
  position: relative;
}

.app-bar__item-row {
  display: flex;
  align-items: center;
  gap: var(--space-1);
}

.app-bar__disclosure {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  padding: 0;
  border: none;
  background: transparent;
  color: var(--nav-text);
  cursor: pointer;
  border-radius: var(--radius-xs);
}

.app-bar__disclosure:hover {
  color: var(--nav-text-active);
}

.app-bar__disclosure svg {
  transition: transform 120ms ease;
}

/* Floating on desktop; the mobile block below makes it part of the stacked
   panel instead, where a popover has nothing to float over. */
.app-bar__submenu {
  position: absolute;
  top: calc(100% + 10px);
  left: 0;
  min-width: 180px;
  margin: 0;
  padding: var(--space-2);
  list-style: none;
  background: var(--nav-bg);
  border: 1px solid rgba(240, 255, 250, 0.14);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-overlay);
  z-index: 40;
}

/* Overrides the flex row `.app-bar__nav ul` sets, which would otherwise lay the
   submenu out horizontally. */
.app-bar__submenu,
.app-bar__nav .app-bar__submenu {
  display: block;
}

.app-bar__sublink {
  display: block;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
  color: var(--nav-text);
  text-decoration: none;
  white-space: nowrap;
}

.app-bar__sublink:hover {
  background: rgba(240, 255, 250, 0.08);
  color: var(--nav-text-active);
}

.app-bar__sublink--active {
  color: var(--nav-text-active);
  font-weight: var(--weight-bold);
}

.app-bar__link {
  display: inline-block;
  font-size: var(--text-sm);
  color: var(--nav-text);
  text-decoration: none;
  padding-bottom: 2px;
  border-bottom: 2px solid transparent;
}

.app-bar__link:hover {
  color: var(--nav-text-active);
}

.app-bar__link--active {
  color: var(--nav-text-active);
  font-weight: var(--weight-bold);
  border-bottom-color: var(--nav-accent);
}

.app-bar__link--disabled {
  cursor: default;
}

.app-bar__link--disabled:hover {
  color: var(--nav-text);
}

.app-bar__link--disabled small {
  font-size: var(--text-2xs);
}

.app-bar__right {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  flex: none;
}

.app-bar__identity {
  position: relative; /* anchors the user-menu panel */
}

/* The trigger keeps the identity block's exact look: reset button chrome only. */
.app-bar__identity-button {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: 0;
  border: none;
  background: transparent;
  color: var(--nav-text);
  cursor: pointer;
  text-align: left;
}

.app-bar__identity-button svg {
  transition: transform 120ms ease;
}

.app-bar__identity-button:hover {
  color: var(--nav-text-active);
}

/* Same surface as .app-bar__submenu, but right-aligned to the bar edge. */
.app-bar__user-menu {
  position: absolute;
  top: calc(100% + 10px);
  right: 0;
  min-width: 220px;
  max-width: 320px;
  padding: var(--space-2);
  background: var(--nav-bg);
  border: 1px solid rgba(240, 255, 250, 0.14);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-overlay);
  z-index: 40;
}

.app-bar__user-email {
  display: block;
  padding: var(--space-2) var(--space-3);
  font-size: var(--text-sm);
  color: var(--nav-text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis; /* the column allows 320 chars; don't let it blow the panel open */
}

.app-bar__user-menu-action {
  display: block;
  width: 100%;
  padding: var(--space-2) var(--space-3);
  border: none;
  border-radius: var(--radius-sm);
  background: transparent;
  font-size: var(--text-sm);
  color: var(--nav-text);
  text-align: left;
  cursor: pointer;
}

.app-bar__user-menu-action:hover {
  background: rgba(240, 255, 250, 0.08);
  color: var(--nav-text-active);
}

.app-bar__who {
  display: flex;
  flex-direction: column;
  line-height: 1.25;
}

.app-bar__name {
  font-size: var(--text-sm);
  font-weight: var(--weight-bold);
  color: var(--mint);
}

.app-bar__role {
  font-size: var(--text-2xs);
  color: rgba(240, 255, 250, 0.55);
}

.app-bar__bell {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  color: var(--nav-text);
  border-radius: var(--radius-md);
}

.app-bar__bell:hover {
  color: var(--nav-text-active);
}

.notification-badge {
  position: absolute;
  top: -4px;
  right: -4px;
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  border-radius: var(--radius-full);
  background: var(--green-300);
  color: var(--green-900);
  font-size: var(--text-2xs);
  font-weight: var(--weight-bold);
  line-height: 1;
}

.notification-badge--zero {
  display: none;
}

.app-bar__toggle {
  display: none;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border: none;
  background: transparent;
  color: var(--mint);
  cursor: pointer;
  border-radius: var(--radius-md);
  flex: none;
}

.app-bar__toggle:focus-visible {
  outline: var(--focus-ring);
}

/* Container */
.page {
  max-width: 1500px;
  margin: 0 auto;
  padding: var(--space-8) var(--space-12);
}

/* Identity pages (sign-in / auth error) */
.identity-page {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--color-bg);
  padding: var(--space-6);
}

.identity-card {
  width: 100%;
  max-width: 400px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: var(--space-8);
  background: var(--color-surface);
}

.identity-card__logo {
  display: block;
  margin: 0 0 var(--space-6);
  height: 24px;
  width: auto;
}

/* SSO sign-in: full-bleed dark hero, no card */
.identity-page--hero {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  background: var(--green-900);
  color: var(--mint);
  padding: var(--space-6);
  text-align: center;
}

.identity-hero {
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 100%;
  max-width: 400px;
}

.identity-hero__logo {
  display: block;
  height: 40px;
  width: auto;
  margin-bottom: var(--space-10);
}

.identity-hero__title {
  margin: 0 0 var(--space-10);
  font-size: var(--text-3xl);
  font-weight: var(--weight-bold);
  color: #ffffff;
}

.identity-hero__sso {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 44px;
  padding: 0 var(--space-6);
  border-radius: var(--radius-md);
  background: var(--green-300);
  color: var(--green-900);
  font-weight: var(--weight-bold);
  font-size: var(--text-base);
  text-decoration: none;
}

.identity-hero__sso:hover {
  background: var(--green-500);
}

.identity-hero__sso:focus-visible {
  outline: var(--focus-ring);
}

.identity-hero__sso + .identity-hero__sso {
  margin-top: var(--space-4);
}

.identity-hero__sso--ghost {
  background: transparent;
  border: 1px solid var(--green-300);
  color: var(--green-300);
}

.identity-hero__sso--ghost:hover {
  background: rgba(240, 255, 250, 0.08);
}

.identity-hero__alt {
  margin: var(--space-8) 0 0;
  font-size: var(--text-sm);
  color: var(--neutral-500);
}

.identity-hero__alt a {
  color: var(--green-300);
}

.identity-hero__form {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  width: 100%;
  text-align: left;
}

.identity-hero__label {
  font-size: var(--text-sm);
  color: var(--mint);
}

.identity-hero__input {
  height: 44px;
  padding: 0 var(--space-4);
  border: 1px solid var(--green-300);
  border-radius: var(--radius-md);
  background: transparent;
  color: var(--mint);
  font: inherit;
}

.identity-hero__input:focus-visible {
  outline: var(--focus-ring);
}

.identity-hero__input--code {
  font-size: var(--text-xl);
  letter-spacing: 0.3em;
  text-align: center;
}

button.identity-hero__sso {
  border: 0;
  cursor: pointer;
  font-family: inherit;
}

.identity-hero__message {
  margin: 0 0 var(--space-6);
  color: var(--mint);
}

.identity-hero__error {
  margin: 0 0 var(--space-4);
  color: var(--green-300);
  font-weight: var(--weight-bold);
}

.identity-hero__resend {
  margin-top: var(--space-6);
}

.identity-hero__link {
  border: 0;
  background: none;
  padding: 0;
  color: var(--green-300);
  font: inherit;
  font-size: var(--text-sm);
  text-decoration: underline;
  cursor: pointer;
}

/* ============================== 3. Components ============================== */

/* Button. Sizing lives on the base `.btn` (the "md" shape) rather than only on
   `.btn--md`, because legacy hand-rolled markup (home_*.html.j2, and the
   single-dash `.btn-primary`/`.btn-secondary` still emitted by
   cases/detail.html.j2 until Task 5/6 replace them with the macro) applies bare `.btn` with no size
   modifier and must still render as a properly shaped button. `.btn--md` is
   kept as an explicit no-op alias for callers that pass size="md" on purpose;
   `.btn--sm` overrides the base sizing down. */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  height: 40px;
  padding: 0 var(--space-5);
  font-size: var(--text-base);
  border-radius: var(--radius-md);
  border: 1px solid transparent;
  font-weight: var(--weight-bold);
  cursor: pointer;
  text-decoration: none;
  white-space: nowrap;
}

.btn--md {
  height: 40px;
  padding: 0 var(--space-5);
  font-size: var(--text-base);
}

.btn--sm {
  height: 32px;
  padding: 0 var(--space-4);
  font-size: var(--text-sm);
}

.btn--primary {
  background: var(--green-300);
  color: var(--green-900);
}

.btn--primary:hover {
  color: var(--green-900);
  filter: brightness(0.96);
}

.btn--secondary {
  background: var(--green-900);
  color: var(--mint);
}

.btn--secondary:hover {
  color: var(--mint);
  filter: brightness(1.2);
}

.btn--ghost {
  background: transparent;
  border-color: var(--color-border-input);
  color: var(--color-text);
}

.btn--ghost:hover {
  color: var(--color-text);
  background: var(--color-surface-subtle);
}

/* Tinted variant (thesis hi-fi composer's "Post comment"): mint fill, dark
   green text, no visible border -- distinct from `--secondary`'s solid dark
   fill, which would be too heavy for a row that already has a checkbox and a
   select competing for attention. */
.btn--tint {
  background: var(--mint);
  color: var(--green-600);
}

.btn--tint:hover {
  color: var(--green-600);
  filter: brightness(0.97);
}

.btn--danger {
  background: transparent;
  border-color: var(--color-danger);
  color: var(--color-danger);
}

.btn--danger:hover {
  color: var(--color-danger);
  background: rgba(255, 45, 45, 0.06);
}

.btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* The global `a:hover` outranks a variant's resting `color` (element+pseudo beats
   a lone class), so an anchor rendered as a button turned link-green on hover
   while the same variant as a <button> did not. Each variant restates its colour
   in its own :hover rule above to win that tie. */

/* Tag (badge) */
.tag {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px;
  border-radius: var(--radius-full);
  border: 1px solid var(--color-border);
  background: var(--color-surface);
  font-size: var(--text-sm);
  font-weight: var(--weight-bold);
  color: var(--color-text);
  line-height: 1.3;
}

.tag__dot,
.status-dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: var(--radius-full);
  flex: none;
  background: var(--neutral-400);
}

.tag[data-status="new"] .tag__dot,
.status-dot[data-status="new"] {
  background: var(--green-300);
}

.tag[data-status="in_progress"] .tag__dot,
.status-dot[data-status="in_progress"] {
  background: var(--green-600);
}

.tag[data-status="escalated"] .tag__dot,
.status-dot[data-status="escalated"] {
  background: var(--yellow-300);
}

.tag[data-status="closed"] .tag__dot,
.status-dot[data-status="closed"] {
  background: var(--neutral-400);
}

.tag[data-status="danger"] .tag__dot,
.status-dot[data-status="danger"] {
  background: var(--color-danger);
}

.tag[data-status="info"] .tag__dot,
.status-dot[data-status="info"] {
  background: var(--color-info);
}

.tag[data-status="success"] .tag__dot,
.status-dot[data-status="success"] {
  background: var(--color-success);
}

.tag[data-status="warning"] .tag__dot,
.status-dot[data-status="warning"] {
  background: var(--color-warning);
}

.tag[data-status="default"] .tag__dot,
.status-dot[data-status="default"] {
  background: var(--neutral-400);
}

.tag[data-status="internal"] .tag__dot,
.status-dot[data-status="internal"] {
  background: var(--neutral-400);
}

/* Thesis "Internal note" chip: neutral like the internal bubble it labels. */
.tag[data-status="internal"] {
  background: var(--neutral-100);
  border-color: var(--color-border);
  color: var(--color-text);
}

.tag[data-status="unpublished"] .tag__dot,
.status-dot[data-status="unpublished"] {
  background: var(--green-600);
}

/* "Unpublished" inherits the mint chip the internal note used to wear; the
   internal chip goes neutral to match its solid grey bubble (spec 2026-10-13). */
.tag[data-status="unpublished"] {
  background: var(--mint);
  border-color: rgba(40, 120, 65, 0.35);
  color: var(--color-text);
}

/* Color variant used directly (no dot) by the activity item's clarification label */
.tag--info {
  background: rgba(25, 80, 255, 0.08);
  border-color: rgba(25, 80, 255, 0.3);
  color: var(--color-info);
}

/* Color variant used directly (no dot) by the activity item's internal-note label */
.tag--success {
  background: rgba(40, 120, 65, 0.08);
  border-color: rgba(40, 120, 65, 0.3);
  color: var(--color-success);
}

/* Avatar (also used unstyled-by-size in the app bar identity chip) */
.avatar {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: var(--radius-md);
  font-size: var(--text-xs);
  font-weight: var(--weight-bold);
  flex: none;
}

.avatar--sm {
  width: 28px;
  height: 28px;
}

.avatar--md {
  width: 36px;
  height: 36px;
}

.avatar--lg {
  width: 64px;
  height: 64px;
  font-size: var(--text-lg);
  border-radius: var(--radius-lg);
}

.avatar--neutral {
  background: var(--neutral-50);
  color: var(--color-text-secondary);
}

.avatar--tint {
  background: var(--mint);
  color: var(--green-600);
}

.avatar--accent {
  background: var(--green-300);
  color: var(--green-900);
}

.avatar--dark {
  background: var(--green-900);
  color: var(--green-300);
}

/* Card */
.card {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: 32px 36px;
}

.card--tint {
  background: var(--color-surface-tint);
  border-color: var(--color-border-tint);
}

.card__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-4);
  margin-bottom: var(--space-4);
}

.card__title {
  font-size: var(--text-xl);
  font-weight: var(--weight-bold);
  margin: 0;
}

.card__action {
  display: flex;
  align-items: center;
  gap: var(--space-4);
}

/* Breadcrumb (shared with the hand-rolled `<nav class="breadcrumb">` pages still emit) */
.breadcrumb {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  margin-bottom: var(--space-4);
}

.breadcrumb a,
.breadcrumb__link {
  color: var(--color-link);
  text-decoration: none;
}

.breadcrumb a:hover,
.breadcrumb__link:hover {
  color: var(--color-link-hover);
}

.breadcrumb span[aria-hidden="true"],
.breadcrumb__sep {
  color: var(--neutral-400);
}

.breadcrumb__current {
  color: var(--color-text);
}

/* Distinct from the breadcrumb above it: that says where this page sits, this
   says how the reader arrived (from a specific case) -- own row, muted like
   a breadcrumb rather than styled as a primary action. */
.deal-back-link {
  margin: 0 0 var(--space-4);
}

.deal-back-link a {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  text-decoration: none;
}

.deal-back-link a:hover {
  color: var(--color-text);
}

/* Status path (lifecycle strip) */
.status-path {
  display: flex;
  align-items: center;
  list-style: none;
  margin: 0;
  padding: 0;
  overflow-x: auto;
}

/* Steps size to their content; only the connector grows. Giving every step an equal
   width instead makes each connector absorb whatever its label leaves over, so the
   lines come out different lengths -- longest after the shortest label -- and the
   first step, having no connector, ends in a long dead gap. */
.status-path__step {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex: none;
}

/* Every step but the first carries a leading connector, and each grows by the same
   amount, so all the connectors end up the same length. */
.status-path__step:not(:first-child) {
  flex: 1 1 auto;
}

.status-path__step:not(:first-child)::before {
  content: "";
  flex: 1 1 auto;
  min-width: 24px;
  height: 2px;
  background: rgba(40, 120, 65, 0.18);
  margin: 0 var(--space-4);
}

/* `:not(:first-child)` must be repeated here: without it this rule loses on
   specificity to the base connector above and no completed leg ever turns green,
   leaving the tracker showing the same pale line at every step. */
.status-path__step--done:not(:first-child)::before,
.status-path__step--current:not(:first-child)::before {
  background: var(--green-600);
}

.status-path__marker {
  width: 22px;
  height: 22px;
  border-radius: var(--radius-full);
  display: flex;
  align-items: center;
  justify-content: center;
  flex: none;
  border: 2px solid rgba(40, 120, 65, 0.3);
  box-sizing: border-box;
  color: var(--mint);
}

.status-path__step--done .status-path__marker {
  background: var(--green-600);
  border-color: var(--green-600);
}

.status-path__step--current .status-path__marker {
  background: var(--green-300);
  border: 3px solid rgba(70, 240, 130, 0.32);
  box-sizing: content-box;
  width: 16px;
  height: 16px;
}

.status-path__label {
  font-size: var(--text-base);
  color: var(--color-text-muted);
  /* The strip scrolls sideways when it runs out of room (see the narrow-screen
     rules); a wrapped "In Progress" would misalign every marker on its row. */
  white-space: nowrap;
}

.status-path__step--current .status-path__label {
  color: var(--color-text);
  font-weight: var(--weight-bold);
}

.status-path__step--done .status-path__label {
  color: var(--green-600);
  font-weight: var(--weight-bold);
}

/* Avatar-monogram tile / fact list / rail live under Page-layout below; the following
   are small standalone components. */

.fact-list {
  margin: 0;
  display: flex;
  flex-direction: column;
}

.fact-list__row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-2) 0;
  font-size: var(--text-base);
}

.fact-list__row:not(:last-child) {
  border-bottom: 1px solid var(--color-divider);
}

.card--tint .fact-list__row:not(:last-child) {
  border-bottom-color: var(--color-border-tint);
}

.fact-list__label {
  margin: 0;
  color: var(--color-text-muted);
}

.fact-list__value {
  margin: 0;
  font-weight: var(--weight-bold);
  text-align: right;
}

/* A link in a value cell is still a value: the hi-fi mock underlines no link
   anywhere, and the browser default was the only underline on the thesis rail. */
.fact-list__value a {
  text-decoration: none;
}

/* Chevron link */
.chevron-link {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  font-size: var(--text-sm);
  font-weight: var(--weight-bold);
  color: var(--color-link);
  text-decoration: none;
}

.chevron-link:hover {
  color: var(--color-link-hover);
}

/* Tooltip */
.tooltip {
  position: relative;
  display: inline-flex;
}

.tooltip__bubble {
  position: absolute;
  bottom: calc(100% + 8px);
  left: 50%;
  transform: translateX(-50%);
  background: var(--green-900);
  color: var(--mint);
  font-size: var(--text-xs);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-overlay);
  /* Anchored at left:50% with no right edge, the bubble would shrink-to-fit into
     half the trigger's width and wrap short text onto several lines; max-content
     sizes it to the text, with max-width still capping a long hint. */
  width: max-content;
  max-width: 260px;
  white-space: normal;
  text-align: center;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.12s ease;
  z-index: 20;
}

.tooltip:hover .tooltip__bubble,
.tooltip:focus-within .tooltip__bubble {
  opacity: 1;
}

.tooltip--bottom .tooltip__bubble {
  bottom: auto;
  top: calc(100% + 8px);
}

.tooltip--left .tooltip__bubble {
  left: auto;
  right: 0;
  transform: none;
}

.tooltip--right .tooltip__bubble {
  left: 0;
  transform: none;
}

/* Menu */
.menu {
  position: relative;
  display: inline-flex;
}

.menu__popover {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  min-width: 180px;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-overlay);
  padding: var(--space-2);
  z-index: 30;
  display: flex;
  flex-direction: column;
}

.menu__item {
  display: block;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
  color: var(--color-text);
  text-decoration: none;
}

.menu__item:hover {
  background: var(--color-surface-subtle);
}

/* Tabs and the segmented control share one look -- a pill track with the chosen
   option raised out of it. They differ in semantics, not appearance: `tabs`
   switches what is on screen, `segmented` is a form input that gets submitted. */
.tabs,
.segmented {
  display: inline-flex;
  flex-wrap: wrap;
  gap: var(--space-1);
  background: var(--color-surface-subtle);
  border-radius: var(--radius-md);
  padding: 4px;
}

.tabs__option,
.segmented__option {
  position: relative;
  border: none;
  background: transparent;
  font-size: var(--text-sm);
  padding: 6px 12px;
  border-radius: var(--radius-sm);
  color: var(--color-text-muted);
  cursor: pointer;
}

.tabs__option--selected,
.segmented__option:has(.segmented__input:checked) {
  background: var(--color-surface);
  color: var(--color-text);
  font-weight: var(--weight-bold);
}

/* The native radio stays the control -- it owns focus, arrow-key navigation and
   the accessibility tree. Hidden by being taken out of the flow rather than with
   `display: none`, which would also take it out of the tab order. */
.segmented__input {
  position: absolute;
  width: 0;
  height: 0;
  opacity: 0;
}

/* Focus lands on that hidden radio, so the ring has to be drawn by the label. */
.segmented__option:has(.segmented__input:focus-visible) {
  outline: var(--focus-ring);
  outline-offset: 2px;
}

/* Subtype pills inside a case-type card (mock 2026-09-02): individually bordered
   buttons rather than one shared pill track, so they read as a smaller, nested
   choice rather than a second instance of the type picker above them. */
.segmented--pills {
  background: none;
  padding: 0;
  gap: var(--space-2);
}

.segmented--pills .segmented__option {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  padding: 6px 14px;
  background: var(--color-surface);
}

.segmented--pills .segmented__option:has(.segmented__input:checked) {
  background: var(--color-text);
  border-color: var(--color-text);
  color: var(--color-surface);
}

/* Raise-a-case type picker (mock 2026-09-02): one radio per card rather than a
   segmented control, because each option carries a description and (for most
   types) an expanding subtype row -- content a pill track has no room for. */
.case-type-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.case-type-card {
  display: block;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--space-4);
  cursor: pointer;
}

.case-type-card--selected {
  border-color: var(--color-border-tint);
  background: var(--color-surface-tint);
}

.case-type-card__head {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
}

/* A real radio input, styled as a circle rather than hidden -- unlike the
   segmented control, each card is large enough that the native dot has its own
   room and doesn't need a label-drawn substitute. */
.case-type-card__radio {
  appearance: none;
  width: 18px;
  height: 18px;
  margin-top: 2px;
  /* Whole pixels only: 18px box, 2px ring, 8px dot leaves exactly 3px on every
     side. A 1.5px ring rounded differently per side and the dot sat off-centre. */
  border: 2px solid var(--color-text);
  border-radius: var(--radius-full);
  flex: none;
  display: grid;
  place-content: center;
}

.case-type-card__radio::before {
  content: "";
  width: 8px;
  height: 8px;
  border-radius: var(--radius-full);
  transform: scale(0);
  background: var(--color-text);
}

.case-type-card__radio:checked::before {
  transform: scale(1);
}

.case-type-card__body {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.case-type-card__title {
  font-weight: var(--weight-bold);
  color: var(--color-text);
}

.case-type-card__desc {
  font-size: var(--text-sm);
  color: var(--color-text-secondary);
}

.case-type-card__detail {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  margin-top: var(--space-4);
  /* Lines up with the title, not the radio: 18px dot + the head's gap. */
  margin-left: calc(18px + var(--space-3));
  padding-top: var(--space-4);
  border-top: 1px solid var(--color-border-tint);
}

.case-type-card__subtype {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.case-type-hint {
  margin-top: var(--space-3);
}

/* Step 2 (spec 2026-09-03): the picker card keeps its frame and title so the
   page doesn't jump; only the unchosen rows go, and the change link sits where
   the "Not sure?" hint was. */
.case-type-change {
  margin: var(--space-3) 0 0;
  text-align: right;
}

.case-type-change .chevron-link {
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  font-weight: var(--weight-bold);
  cursor: pointer;
}

.request-card--locked {
  opacity: 0.45;
}

.request-card__lock {
  font-size: var(--text-sm);
}

.rail-facts__hint {
  margin: var(--space-2) 0 0;
  font-size: var(--text-sm);
  text-align: center;
}

/* The rail's summary card is the only button on the raise-a-case page, and the
   mock spans it edge to edge rather than left-aligning it like an inline form
   action. */
.rail-facts .btn {
  display: block;
  width: 100%;
  text-align: center;
  margin-top: var(--space-2);
}

/* Underline tabs -- same All/Unread-style filter contract as `.tabs`, but a
   lighter-weight text-plus-underline look for pages that don't want the pill
   track (e.g. the notifications feed). */
.tabs-underline {
  display: flex;
  gap: var(--space-2);
  border-bottom: 1px solid var(--color-border);
}

.tabs-underline__option {
  position: relative;
  border: none;
  background: transparent;
  font-size: var(--text-sm);
  font-weight: var(--weight-normal);
  color: var(--color-text-muted);
  cursor: pointer;
  padding: var(--space-2) var(--space-3) var(--space-3);
  text-decoration: none;
}

.tabs-underline__option--selected {
  color: var(--color-text);
  font-weight: var(--weight-bold);
}

.tabs-underline__option--selected::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -1px;
  height: 2px;
  background: var(--color-accent);
}

/* Stat tile */
.stat-tile {
  padding: var(--space-5) var(--space-6);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  min-width: 160px;
  flex: 1;
}

.stat-tile__value {
  font-size: var(--text-3xl);
  font-weight: var(--weight-bold);
  margin: 0;
  letter-spacing: var(--tracking-tight);
}

.stat-tile__label {
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  margin: var(--space-1) 0 0;
}

.stat-tile__hint {
  font-size: var(--text-xs);
  color: var(--color-text-faint);
  margin: var(--space-1) 0 0;
}

/* Empty state */
.empty-state {
  text-align: center;
  padding: var(--space-12) var(--space-6);
  background: var(--color-surface-subtle);
  border: 1px dashed var(--color-border);
  border-radius: var(--radius-lg);
}

.empty-state__icon {
  display: flex;
  justify-content: center;
  margin: 0 0 var(--space-4);
}

.empty-state__title {
  font-size: var(--text-lg);
  font-weight: var(--weight-bold);
  margin: 0 0 var(--space-2);
  color: var(--color-text);
}

.empty-state__description {
  color: var(--color-text-muted);
  margin: 0 0 var(--space-4);
}

/* Home panels: an empty state directly under the action buttons would touch them */
.home-panel__actions + .empty-state {
  margin-top: var(--space-4);
}

/* Activity stream (the <ul> that wraps activity items) */
.activity-stream {
  list-style: none;
  margin: 0;
  padding: 0;
}

/* The border lives on the <li>, not `.activity-item`: each item div is the sole
   child of its own <li>, so a border keyed off the item's own :not(:first-child)
   never matches. The item's existing top/bottom padding (--space-5) already
   supplies the mock's ~22px rhythm around the divider. */
.activity-stream > li:not(:first-child) {
  border-top: 1px solid var(--color-divider);
}

/* Activity item */
.activity-item {
  display: grid;
  grid-template-columns: 36px 1fr;
  gap: var(--space-4);
  padding: var(--space-5) 0;
}

/* An internal note keeps the base activity-item layout but reads as set apart:
   dashed border + faint background, mirroring the reference mockup. Applied to the
   item itself (not the <li>) so the existing divider rule between list items is
   undisturbed. Scoped to `.activity-item.activity-item--internal` (not the bare
   modifier class) because the thesis comment thread's internal root keeps
   `activity-item--internal` for its tests/e2e selector but never carries the
   base `.activity-item` class -- its own dashed frame lives on
   `.comment__bubble--internal`, and without this scoping the legacy rule
   double-boxed the bubble (hi-fi 2026-09-18). `activity_item()`
   (components/activity_item.html.j2) always emits both classes together, so
   the cases activity feed this rule exists for is unaffected. */
.activity-item.activity-item--internal {
  border: 1px dashed rgba(40, 120, 65, 0.35);
  border-radius: var(--radius-md);
  background: rgba(40, 120, 65, 0.04);
  padding: var(--space-4);
}

.activity-item__meta {
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
  flex-wrap: wrap;
}

.activity-item__actor {
  font-size: var(--text-base);
  font-weight: var(--weight-bold);
}

.activity-item__time {
  font-size: var(--text-xs);
  color: var(--color-text-faint);
}

/* A comment is the content of the stream; an event is an annotation about it. The
   two were a pair of near-identical greys, so the comment takes the primary text
   colour to open the gap. */
.activity-item__body {
  font-size: var(--text-base);
  line-height: 1.6;
  color: var(--color-text);
  margin-top: var(--space-2);
}

/* Size as well as tone: colour alone is a weak signal and disappears in greyscale
   or for a low-vision reader. Muted stops at --color-text-muted (5.2:1 on white)
   rather than going lighter, which would fall below AA for body text. */
.activity-item__body--event {
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}

.activity-item__body--clamped {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.activity-item__more {
  background: none;
  border: none;
  padding: 0;
  margin-top: var(--space-1);
  cursor: pointer;
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  font-weight: 700;
  color: var(--green-600);
}

.activity-item__more:hover {
  color: var(--green-700);
}

/* Toggle row for a collapsed activity bucket ("Show N earlier updates" /
   "Hide earlier updates"). Left-aligned under the item content column (not
   the avatar column), matching where the item's own body text starts. */
.activity-collapse-toggle {
  padding: var(--space-3) 0;
}

/* A native <summary>: the marker is stripped since the chevron SVG is the
   disclosure affordance already. */
.activity-collapse-toggle__button {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-left: 36px;
  padding-left: var(--space-4);
  list-style: none;
  color: var(--color-link);
  font-size: var(--text-sm);
  cursor: pointer;
}

.activity-collapse-toggle__button::-webkit-details-marker {
  display: none;
}

.activity-collapse-toggle__button:hover {
  color: var(--color-link-hover);
}

.activity-collapse-toggle__chevron {
  transition: transform 0.12s ease;
}

/* Text and chevron state both key off `[open]` directly -- no expand flag to
   keep in sync with it. */
.activity-collapse-toggle__hide-text {
  display: none;
}

details[open] .activity-collapse-toggle__show-text {
  display: none;
}

details[open] .activity-collapse-toggle__hide-text {
  display: inline;
}

details[open] .activity-collapse-toggle__chevron {
  transform: rotate(180deg);
}

/* Table (Data List slot 04) -- always inside a `.list-card`. Header labels
   are 12px uppercase grey; a sortable column shows a faint double chevron,
   the active one turns ink + bold with a single arrow; non-sortable columns
   show no glyph. Rows given a link (`data-href`) are click targets with a
   Light Green hover (static/list-rows.js). */
.list-card {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: var(--color-surface);
  margin-top: 18px;
}

.table-scroll {
  overflow-x: auto;
}

.table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--text-base);
}

.table th,
.table td {
  padding-left: var(--space-3);
  padding-right: var(--space-3);
}

.table th:first-child,
.table td:first-child {
  padding-left: var(--space-6);
}

.table th:last-child,
.table td:last-child {
  padding-right: var(--space-6);
}

.table th {
  text-align: left;
  padding-top: 14px;
  padding-bottom: 14px;
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: var(--tracking-caps);
  color: var(--color-text-faint);
  font-weight: var(--weight-normal);
  border-bottom: 1px solid var(--color-border);
  white-space: nowrap;
}

.table td {
  padding-top: 15px;
  padding-bottom: 15px;
  border-bottom: 1px solid var(--color-divider);
  vertical-align: middle;
}

.table tbody tr:last-child td {
  border-bottom: 0;
}

/* Identifier columns stay link-styled as a shortcut; the row and "Open ›"
   remain the primary targets. */
.table td > a:not(.row-open) {
  font-weight: var(--weight-bold);
  color: var(--color-link);
  text-decoration: none;
}

.table td > a:not(.row-open):hover {
  color: var(--color-link-hover);
}

.table tbody tr[data-href] {
  cursor: pointer;
}

.table tbody tr[data-href]:hover {
  background: var(--color-surface-tint);
}

.table__open {
  text-align: right;
  white-space: nowrap;
}

.row-open {
  font-size: var(--text-sm);
  font-weight: var(--weight-bold);
  color: var(--color-link);
  text-decoration: none;
  white-space: nowrap;
}

.row-open:hover {
  color: var(--color-link-hover);
}

/* Empty states sit inside the card, centred, without the dashed standalone box. */
.table__empty-row td {
  padding: 0;
}

.table__empty-row .empty-state {
  border: 0;
  border-radius: 0;
  background: none;
}

.sort-header {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  font: inherit;
  text-transform: inherit;
  letter-spacing: inherit;
  color: inherit;
}

.sort-header svg {
  width: 11px;
  height: 11px;
  color: var(--neutral-400);
}

.sort-header:hover,
.sort-header:hover svg {
  color: var(--color-text-secondary);
}

th[aria-sort] .sort-header,
th[aria-sort] .sort-header svg {
  color: var(--color-text);
  font-weight: var(--weight-bold);
}

.activity-cell {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-1);
}

.activity-cell__time {
  font-size: var(--text-xs);
  color: var(--color-text-faint);
}

.comment-count {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  color: var(--color-text-muted);
  font-size: var(--text-sm);
}

.groups-table {
  table-layout: fixed;
}

.groups-table th,
.groups-table td {
  width: 20%;
}

.groups-domain-form {
  display: flex;
  gap: var(--space-3);
  align-items: flex-end;
  flex-wrap: wrap;
}

/* .field carries margin-bottom for vertically stacked forms; here the fields sit
   side by side in one row, so that margin would otherwise count towards
   align-items: flex-end and leave the Save button (which has no such margin)
   sitting visibly lower than the dropdowns next to it. */
.groups-domain-form .field {
  margin-bottom: 0;
}

/* The Population slot is a select for most groups but static text for the
   settings-assigned Admin group (see admin/routes.py's admin_group_ids) -- a
   fixed width keeps that slot the same size either way, so the Save button
   doesn't jump left on the Admin row for lack of a dropdown to push it right. */
.groups-population-slot {
  display: inline-block;
  width: 140px;
}

/* ============================== 4. Form primitives ============================== */

.field {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-bottom: var(--space-4);
}

/* A group of controls (a radiogroup) rather than one full-width input: it should
   be as wide as its options, not stretched to the column by the field's flex. */
.field--group {
  align-items: flex-start;
}

.field__label {
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: var(--tracking-caps);
  color: var(--color-text-faint);
  font-weight: var(--weight-bold);
}

/* Muted, not danger-red: red is what a validation error uses, and spending it on a
   field that is merely *needed* leaves "needed" and "wrong" looking the same. Once
   the field IS in error, the requirement is the violated rule, so it joins the red. */
.field__required {
  text-transform: none;
  letter-spacing: 0;
  color: var(--color-text-faint);
  font-weight: var(--weight-normal);
}

.field--error .field__required {
  color: var(--color-danger);
}

.field__input,
.field__select,
.field__textarea {
  font-family: inherit;
  font-size: var(--text-base);
  padding: 11px 13px;
  border: 1px solid var(--color-border-input);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  color: var(--color-text);
}

.field__input::placeholder,
.field__textarea::placeholder {
  color: var(--color-text-placeholder);
}

.field__input:focus,
.field__select:focus,
.field__textarea:focus {
  outline: var(--focus-ring);
  outline-offset: 1px;
}

.field--error .field__input,
.field--error .field__select,
.field--error .field__textarea {
  border-color: var(--color-danger);
}

.field__error {
  font-size: var(--text-xs);
  color: var(--color-danger);
  margin: 0;
}

.field__hint {
  font-size: var(--text-xs);
  margin: 0;
}

.select {
  position: relative;
  display: block;
}

.field__select {
  width: 100%;
  appearance: none;
  padding-right: 36px;
}

.select__chevron {
  position: absolute;
  right: 13px;
  top: 50%;
  transform: translateY(-50%);
  color: var(--color-text-muted);
  pointer-events: none;
  display: flex;
}

.field__textarea {
  resize: vertical;
  min-height: 80px;
}

.checkbox {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-sm);
  color: var(--color-text-secondary);
  cursor: pointer;
}

.checkbox__input {
  width: 16px;
  height: 16px;
  margin: 0;
  border-radius: var(--radius-xs);
  border: 1px solid var(--color-border-input);
  accent-color: var(--green-900);
}

/* ============================== 5. Page-layout helpers ============================== */

.page-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-8);
  margin-bottom: var(--space-6);
  flex-wrap: wrap;
}

.page-header h1 {
  font-size: var(--text-2xl);
  font-weight: var(--weight-bold);
  margin: 0;
}

.page-header__row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-8);
  /* The action cluster stays one row pinned right, top-aligned with the first
     title line -- buttons never wrap or shrink; a long title clamps instead. */
  flex-wrap: nowrap;
  width: 100%;
}

.page-header__title-block {
  min-width: 0;
  flex: 1;
}

/* Prototype panel B's long-title rule: the number stays inline at the start
   and never truncates; the subject continues in the same text flow, wraps to
   at most two lines, then ellipsizes (the h1's title attribute carries the
   full subject as a native tooltip). */
.page-header__title {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.page-header__number {
  font-size: var(--text-3xl);
  font-weight: var(--weight-bold);
  letter-spacing: var(--tracking-tight);
  line-height: 1.12;
  margin-right: var(--space-2);
}

.page-header__subject {
  font-size: var(--text-3xl);
  font-weight: var(--weight-normal);
  color: var(--color-text-secondary);
  line-height: 1.12;
}

.page-header__actions {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex: none;
  padding-top: var(--space-1);
}

/* The out-of-band swap target for the header buttons (TM-06) is a real DOM
   node wrapping them, but it must not be a layout box of its own -- without
   `display: contents` it falls back to normal inline flow instead of
   `.page-header__actions`'s flex row, and the icon-only pencil button (no
   text baseline to align on) visibly drifts out of line with its neighbors. */
#case-header-actions {
  display: contents;
}

.detail-grid {
  display: grid;
  grid-template-columns: 1fr 372px;
  gap: 36px;
  align-items: start;
}

.rail {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
}

/* ============================== 6. Legacy (pre-Task-5-sweep) ============================== */

.alert {
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-md);
  font-size: var(--text-sm);
  margin-bottom: var(--space-4);
}

.alert-error {
  background: rgba(255, 45, 45, 0.08);
  border: 1px solid var(--color-danger);
  color: var(--color-danger);
}

.form-error {
  font-size: var(--text-xs);
  color: var(--color-danger);
  margin: var(--space-2) 0 0;
}

.field-grid {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: var(--space-2) var(--space-4);
  margin: 0;
}

.field-grid dt {
  color: var(--color-text-muted);
}

.field-grid dd {
  margin: 0;
  font-weight: var(--weight-bold);
}

.stat-tiles {
  display: flex;
  gap: var(--space-4);
  margin-bottom: var(--space-6);
  flex-wrap: wrap;
}

.dashboard-charts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: var(--space-4);
}

.chart-card {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: var(--space-5);
}

.chart-card h2 {
  font-size: var(--text-md);
  font-weight: var(--weight-bold);
  margin: 0 0 var(--space-3);
}

.scope-hint {
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  margin: 0 0 var(--space-4);
}

.text-muted {
  color: var(--color-text-muted);
}

/* "Unassigned" and similar placeholders in a data cell. */
.text-faint {
  color: var(--color-text-faint);
}

/* ============================== Data list pattern (hi-fi 2026-09-18) ============================== */
/* Slots 01-03 and 05 of components/list.html.j2; slot 04 is `.list-card` + `.table` above. */

.list-header {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--space-8);
  flex-wrap: wrap;
}

/* `flex: 1 1 0` rather than the default `auto` basis: a flex item's *base*
   size decides whether the line wraps, and shrinking happens only afterwards,
   so a long subline's max-content width pushed `.list-header__tools` onto a
   second row while leaving room to spare (Thesis, whose subline is ~3x Cases'
   -- reported 2026-09-18). A zero basis makes the title block absorb whatever
   is left beside the tools instead, so every Data List header looks the same
   whatever the subline's length. `min-width: 0` so the block may actually
   shrink below its longest word. */
.list-header__title-block {
  flex: 1 1 0;
  min-width: 0;
}

.list-header__title {
  font-size: var(--text-2xl);
  font-weight: var(--weight-bold);
  line-height: 1.12;
  letter-spacing: -0.4px;
  margin: 0;
}

.list-header__subline {
  margin: var(--space-2) 0 0;
  font-size: var(--text-base);
  color: var(--color-text-muted);
  /* The mock's subtitle measure. With the title block now free to take the
     whole remaining width, an unconstrained subline would stretch to the
     tools on a wide viewport and read as a single over-long line. */
  max-width: 560px;
  text-wrap: pretty;
}

.list-header__tools {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex: none;
}

.list-search {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 280px;
  padding: 10px 14px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  color: var(--color-text-muted);
}

.list-search:focus-within {
  border-color: var(--green-600);
}

.list-search__input {
  width: 100%;
  border: none;
  outline: none;
  background: transparent;
  font: inherit;
  font-size: var(--text-base);
  color: var(--color-text);
}

.list-search__input::placeholder {
  color: var(--color-text-faint);
}

.list-count {
  font-size: var(--text-sm);
  color: var(--color-text-faint);
  white-space: nowrap;
}

/* 02 Tabs: the row carries the rule so the count can sit on it. */
.list-tabs {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-5);
  margin-top: var(--space-6);
  border-bottom: 1px solid var(--color-border);
}

.list-tabs .tabs-underline {
  border-bottom: 0;
  gap: var(--space-1);
}

.list-tabs .tabs-underline__option {
  padding: 10px 14px 12px;
  margin-bottom: -1px;
}

.list-tabs .list-count {
  padding-bottom: 6px;
}

/* 03 Filters: one row of chips under the tabs (or under the header, with a rule). */
.filter-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-5);
  margin-top: 14px;
}

.filter-row--rule {
  margin-top: var(--space-6);
  padding-top: 18px;
  border-top: 1px solid var(--color-border);
}

.filter-row__chips {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
}

.filter-row__clear {
  font-size: var(--text-sm);
  color: var(--color-link);
  padding-left: 6px;
  text-decoration: none;
}

.filter-row__clear:hover {
  color: var(--color-link-hover);
}

.filter-chip {
  position: relative;
}

.filter-chip__button {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: 7px 12px;
  border: 1px solid var(--color-border-input);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  font: inherit;
  font-size: var(--text-sm);
  color: var(--color-text);
  cursor: pointer;
}

.filter-chip__label {
  color: var(--color-text-muted);
}

.filter-chip__value {
  font-weight: var(--weight-bold);
}

.filter-chip__chevron {
  display: inline-flex;
  color: var(--color-text-muted);
  transition: transform 120ms;
}

.filter-chip__chevron--open {
  transform: rotate(180deg);
}

/* Active filter = mid-green border on Light Green. */
.filter-chip--active .filter-chip__button {
  border-color: var(--green-600);
  background: var(--color-surface-tint);
}

.filter-chip--active .filter-chip__chevron {
  color: var(--green-600);
}

.filter-chip__menu {
  position: absolute;
  top: calc(100% + 8px);
  left: 0;
  z-index: 20;
  display: flex;
  flex-direction: column;
  min-width: 240px;
  padding: 8px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  background: var(--color-surface);
  box-shadow: 0 8px 24px rgba(5, 35, 25, 0.1);
}

.filter-chip__option {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 10px;
  border-radius: var(--radius-md);
  font-size: var(--text-base);
  cursor: pointer;
  white-space: nowrap;
}

.filter-chip__option:hover {
  background: var(--color-surface-tint);
}

.filter-chip__option input {
  width: 16px;
  height: 16px;
  margin: 0;
  accent-color: var(--green-600);
}

.filter-chip__actions {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-top: 4px;
  padding: 10px 10px 4px;
  border-top: 1px solid var(--color-divider);
  font-size: var(--text-sm);
}

.filter-chip__select-all,
.filter-chip__apply {
  padding: 0;
  border: none;
  background: none;
  font: inherit;
  font-size: var(--text-sm);
  color: var(--color-link);
  cursor: pointer;
}

.filter-chip__apply {
  font-weight: var(--weight-bold);
}

/* Person cell: avatar + name over email (thesis candidates). */
.candidate-cell {
  display: flex;
  align-items: center;
  gap: 14px;
}

.candidate-cell__name {
  display: block;
  font-weight: var(--weight-bold);
}

.candidate-cell__email {
  display: block;
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}

/* Standalone centred "Load older ›" under all groups of a grouped list
   (notifications: the mock gives grouped lists no footer). */
.list-load-more {
  display: flex;
  justify-content: center;
  margin-top: 28px;
  font-size: var(--text-sm);
}

.list-load-more__link {
  color: var(--color-link);
  font-weight: var(--weight-bold);
  text-decoration: none;
}

.list-load-more__link:hover {
  color: var(--color-link-hover);
}

/* 05 Footer: inside the card, under the rows. */
.list-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding: 14px 24px;
  border-top: 1px solid var(--color-border);
  font-size: var(--text-sm);
  color: var(--color-text-faint);
}

.list-footer__more {
  color: var(--color-link);
  font-weight: var(--weight-bold);
  text-decoration: none;
  white-space: nowrap;
}

.list-footer__more:hover {
  color: var(--color-link-hover);
}

/* ============================== 7. Media queries ============================== */

@media (max-width: 960px) {
  .page {
    padding: var(--space-8) var(--space-5);
  }

  .app-bar {
    padding: 0 var(--space-5);
  }

  .detail-grid {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 720px) {
  /* The one place the header *should* wrap: below this the tools (a 280px
     search plus the primary action) leave the title block too little to be
     worth keeping on the line, so it takes the full width and the tools drop
     under it deliberately rather than by accident. */
  .list-header__title-block {
    flex-basis: 100%;
  }

  .app-bar__who {
    display: none;
  }

  .app-bar__toggle {
    display: flex;
  }

  .app-bar__nav {
    display: none;
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    background: var(--nav-bg);
    z-index: 50;
    box-shadow: var(--shadow-overlay);
  }

  .app-bar__nav--open {
    display: block;
  }

  .app-bar__nav ul {
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    padding: var(--space-2) 0;
  }

  .app-bar__link,
  .app-bar__link--disabled {
    display: flex;
    align-items: center;
    min-height: 44px;
    padding: 0 var(--space-5);
    border-bottom: none;
  }

  .app-bar__link--active {
    box-shadow: inset 2px 0 0 var(--nav-accent);
  }

  /* The panel is already a stack, so the submenu drops into it as an indented
     group rather than floating: a popover over a full-width drawer would cover
     the rows underneath it, and on a phone there is nowhere for it to go. */
  .app-bar__item-row {
    justify-content: space-between;
    padding-right: var(--space-3);
  }

  .app-bar__item-row .app-bar__link {
    flex: 1;
  }

  .app-bar__disclosure {
    /* Comfortable thumb target; the 20px desktop size is a mouse target. */
    width: 44px;
    height: 44px;
    flex: none;
  }

  .app-bar__submenu {
    position: static;
    min-width: 0;
    padding: 0 0 var(--space-2);
    border: none;
    border-radius: 0;
    box-shadow: none;
  }

  .app-bar__sublink {
    display: flex;
    align-items: center;
    min-height: 44px;
    padding: 0 var(--space-5) 0 var(--space-8);
    border-radius: 0;
  }

  .app-bar__sublink--active {
    box-shadow: inset 2px 0 0 var(--nav-accent);
  }

  .card {
    padding: var(--space-5);
  }

  .page-header__row {
    flex-direction: column;
    align-items: flex-start;
  }

  .status-path {
    overflow-x: auto;
  }
}

@media (min-width: 721px) {
  .app-bar__nav {
    display: block;
  }
}

@media (max-width: 600px) {
  .page {
    padding: var(--space-6) var(--space-4);
  }

  .app-bar {
    padding: 0 var(--space-4);
  }

  .page-header__number,
  .page-header__subject {
    font-size: var(--text-2xl);
  }
}

/* === Case detail === */

.lifecycle {
  padding: var(--space-6) var(--space-8);
  margin-bottom: var(--space-8);
}

.lifecycle__hint {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  /* No rule above it: the hint reads as part of the tracker, and the card's own
     border is enough containment. Spacing alone separates them. */
  margin: var(--space-5) 0 0;
  font-size: var(--text-sm);
  color: var(--color-text-secondary);
}

.lifecycle__hint-marker {
  color: var(--color-link);
  font-weight: var(--weight-bold);
}

.detail-grid__main {
  display: flex;
  flex-direction: column;
  gap: 28px;
  min-width: 0;
}

.request__description {
  font-size: var(--text-md);
  line-height: 1.65;
  color: var(--color-text-secondary);
  max-width: 760px;
  white-space: pre-wrap;
}

/* Linked deal */
.linked-deal {
  display: flex;
  align-items: center;
  gap: var(--space-8);
}

.linked-deal__monogram {
  width: 64px;
  height: 64px;
  border-radius: var(--radius-lg);
  background: var(--green-900);
  color: var(--green-300);
  font-size: var(--text-lg);
  font-weight: var(--weight-bold);
  display: flex;
  align-items: center;
  justify-content: center;
  flex: none;
}

.linked-deal__body {
  min-width: 0;
  flex: 1;
}

.linked-deal__name {
  font-size: 17px;
  font-weight: var(--weight-bold);
  margin: 0;
}

.linked-deal__meta {
  margin: var(--space-2) 0 0;
}

.linked-deal__stat {
  flex: none;
  text-align: right;
}

.linked-deal__stat-label {
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: var(--tracking-caps);
  color: var(--color-text-faint);
}

.linked-deal__stat-value {
  font-size: var(--text-md);
  font-weight: var(--weight-bold);
  margin-top: var(--space-1);
}

/* Row-sized variant of the monogram tile: same tile as the case-detail Linked
   deal card, scaled for picker rows. Mostly shows a single initial — the
   majority of deal names are one word — so it must read fine with one glyph. */
.linked-deal__monogram--sm {
  width: 36px;
  height: 36px;
  border-radius: var(--radius-md);
  font-size: var(--text-sm);
}

/* Deal picker: the linked-deal searchable combobox (mock 2026-09-02). */
.deal-picker {
  position: relative;
}

.deal-picker__inputwrap {
  position: relative;
}

.deal-picker__icon {
  position: absolute;
  left: 12px;
  top: 50%;
  width: 16px;
  height: 16px;
  transform: translateY(-50%);
  color: var(--color-text-faint);
  pointer-events: none;
}

.deal-picker__input {
  /* Nested in the icon wrapper, so it doesn't stretch as a .field flex child. */
  width: 100%;
  box-sizing: border-box;
  padding-left: 36px;
}

.deal-picker__results {
  position: absolute;
  top: calc(100% + var(--space-2));
  left: 0;
  right: 0;
  z-index: 30;
}

.deal-picker__panel {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-overlay);
  overflow: hidden;
}

.deal-picker__panel--empty {
  padding: var(--space-4) var(--space-4) var(--space-5);
}

.deal-picker__header {
  margin: 0;
  padding: var(--space-3) var(--space-4) var(--space-2);
  font-size: var(--text-xs);
  font-weight: var(--weight-bold);
  text-transform: uppercase;
  letter-spacing: var(--tracking-caps);
  color: var(--color-text-faint);
}

.deal-picker__list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.deal-option {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  width: 100%;
  padding: var(--space-3) var(--space-4);
  border: none;
  background: none;
  text-align: left;
  cursor: pointer;
  font: inherit;
}

/* One highlight style for keyboard cursor and hover; the first row starts
   highlighted so Enter picks the top match. */
.deal-option--active {
  background: var(--color-surface-tint);
}

.deal-option__body {
  min-width: 0;
  flex: 1;
}

.deal-option__name {
  display: block;
  font-weight: var(--weight-bold);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.deal-option__name mark {
  background: var(--color-accent);
  color: inherit;
  border-radius: 2px;
}

.deal-option__meta {
  display: block;
  margin-top: 2px;
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Stage chip: verbatim Salesforce stage, always the same green — the value
   set is Salesforce's, so there is no curated per-stage mapping to maintain. */
.stage-chip {
  flex: none;
  padding: var(--space-1) var(--space-3);
  border-radius: var(--radius-md);
  background: var(--color-surface-tint);
  border: 1px solid var(--color-border-tint);
  font-size: var(--text-sm);
  font-weight: var(--weight-bold);
  color: var(--green-700);
  white-space: nowrap;
}

.deal-picker__footer {
  display: flex;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-3) var(--space-4);
  border-top: 1px solid var(--color-border);
  background: var(--color-surface-subtle);
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}

.deal-picker__empty-title {
  margin: 0 0 var(--space-2);
  font-weight: var(--weight-bold);
}

.deal-picker__empty-body {
  margin: 0;
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}

/* Selected: the field becomes the deal row itself, with an x to clear. */
.deal-picker__selected {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-border-input);
  border-radius: var(--radius-md);
  background: var(--color-surface);
}

.deal-picker__clear {
  flex: none;
  width: 32px;
  height: 32px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  color: var(--color-text-muted);
  font-size: var(--text-md);
  line-height: 1;
  cursor: pointer;
}

.deal-picker__clear:hover {
  color: var(--color-text);
  border-color: var(--color-border-input);
}

/* Activity composer */
.composer {
  margin-top: var(--space-6);
  padding-top: var(--space-6);
  border-top: 1px solid var(--color-divider);
}

.composer__row {
  display: flex;
  gap: var(--space-4);
  align-items: flex-start;
}

.composer__field {
  flex: 1;
  min-width: 0;
}

.composer__field .field__textarea {
  min-height: 64px;
}

/* The footer supplies the gap below the textarea; the field's own bottom margin
   would stack with it. */
.composer__field .field {
  margin-bottom: 0;
}

.composer__footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  margin-top: var(--space-3);
  flex-wrap: wrap;
}

/* Handling card (spec 2026-09-30): status and owner move together, one tile per move. */
.handling__state {
  display: flex;
  /* Baseline: the pill lines up with the first line of the owner text. */
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--space-1) var(--space-2);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  margin: 0 0 var(--space-4);
}

.handling__state > .tag {
  flex: none;
}

/* Zero basis: the owner text always shares the pill's row and wraps inside
   its own column, rather than a long name wrapping the whole column below. */
.handling__who {
  flex: 1 1 0;
  min-width: 0;
  overflow-wrap: anywhere;
}

/* Its own full-width row under both the pill and the owner. */
.handling__since {
  flex-basis: 100%;
  font-size: var(--text-xs);
}

.handling-tile {
  border: 1px solid var(--color-border-input);
  border-radius: var(--radius-lg);
  padding: var(--space-4) var(--space-5);
  transition:
    border-color 0.12s ease,
    background-color 0.12s ease;
}

/* The summary is the whole collapsed tile: a full-bleed, unstyled <summary> so
   the card reads as one target. It is keyboard-operable and announces its own
   expanded state natively, which is also why the default disclosure triangle is
   stripped: the tile itself is the affordance. */
.handling-tile__summary {
  display: block;
  width: 100%;
  list-style: none;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.handling-tile__summary::-webkit-details-marker {
  display: none;
}

.handling-tile__title {
  display: block;
  font-size: var(--text-base);
  font-weight: var(--weight-bold);
}

/* A folded tile shows only its title, so the card reads as a short list of
   moves; the explanation appears with the form once the tile is opened. */
.handling-tile__copy {
  display: none;
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  line-height: 1.55;
  margin: 0;
}

.handling-tile + .handling-tile {
  margin-top: var(--space-3);
}

/* Hover on a collapsed tile is neutral grey, not mint: mint means "this is the
   move you are making", so previewing it on hover would lie. */
.handling-tile:not([open]):hover {
  border-color: var(--neutral-400);
  background: var(--color-surface-subtle);
}

/* Whichever tile is open takes the tint; `[open]` is the native <details> state. */
.handling-tile[open] {
  border-color: var(--green-600);
  background: var(--color-surface-tint);
}

.handling-tile[open] .handling-tile__title {
  color: var(--green-600);
  margin-bottom: 7px;
}

.handling-tile[open] .handling-tile__copy {
  display: block;
  color: var(--color-text-secondary);
}

.handling-tile__form {
  margin-top: var(--space-4);
  padding-top: var(--space-4);
  border-top: 1px solid var(--color-border-tint);
}

/* Keep controls white against the tinted tile. */
.handling-tile__form .field__input,
.handling-tile__form .field__select,
.handling-tile__form .field__textarea {
  background: var(--color-surface);
}

/* Time spent sits at the left of the submit row and the button holds the right. */
.handling-tile__actions {
  display: flex;
  align-items: flex-end;
  gap: var(--space-3);
  margin-top: var(--space-4);
}

.handling__time {
  flex: none;
  margin-right: auto;
}

/* The width lives on the input, not the field, so the label (with its
   "required" marker on Close) takes the room it needs instead of wrapping. */
.handling__time-input {
  position: relative;
  /* Wide enough for a figure like 12.5 plus the suffix, and no wider. */
  width: 112px;
}

.handling__time-input .field__input {
  width: 100%;
  padding-right: 36px;
}

.handling__time-suffix {
  position: absolute;
  right: 13px;
  top: 50%;
  transform: translateY(-50%);
  font-size: var(--text-xs);
  color: var(--color-text-faint);
}

/* Handled by AIA */
.handled-by-aia {
  border-style: dashed;
  border-color: rgba(25, 80, 255, 0.35);
  background: #f7f9ff;
}

.handled-by-aia__who {
  margin: 0 0 var(--space-3);
}

/* Visibility. Each row is `<p><span marker>&gt;</span><span>sentence</span></p>` --
   exactly two flex children, so the sentence never fragments into pseudo-columns
   the way it did when every inline element inside the row was its own flex item. */
.visibility-list p {
  display: flex;
  gap: var(--space-3);
  font-size: var(--text-sm);
  line-height: 1.5;
  margin: 0 0 var(--space-3);
}

.visibility-list p:last-child {
  margin-bottom: 0;
}

.visibility-list__marker {
  color: var(--color-link);
  font-weight: var(--weight-bold);
  flex: none;
}

/* Related cases */
.related-list__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: 11px 0;
  border-bottom: 1px solid var(--color-divider);
  text-decoration: none;
  color: var(--color-text);
}

.related-list__row:last-child {
  border-bottom: none;
}

.related-list__body {
  min-width: 0;
}

.related-list__number {
  display: block;
  font-size: var(--text-base);
  font-weight: var(--weight-bold);
}

.related-list__subject {
  display: block;
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}

@media (max-width: 960px) {
  /* The rail's Case-facts card jumps above Request/Linked-deal/Activity on a
     phone (spec §7/§8: "lifecycle, facts, request, deal, activity, then action
     panels" -- facts are what a phone reader checks first). `display: contents`
     dissolves the two column wrappers so every card becomes a direct child of
     `.detail-grid`'s single-column flow in document order, and `order: -1` on
     the facts card alone moves it ahead of everything else without having to
     hand-picked an `order` for every other card. */
  .detail-grid {
    gap: var(--space-6);
  }

  .detail-grid__main,
  .rail {
    display: contents;
  }

  .rail-facts {
    order: -1;
  }
}


/* ============================== Notifications feed ============================== */

.page-header__subtext {
  margin: var(--space-1) 0 0;
  color: var(--color-text-muted);
  font-size: var(--text-sm);
}

/* Every day group, the first included, clears the tabs row above it. */
.notification-group {
  margin-top: var(--space-6);
}

.notification-group__label {
  margin: 0 0 var(--space-2);
  font-size: var(--text-xs);
  font-weight: var(--weight-bold);
  letter-spacing: var(--tracking-caps);
  color: var(--color-text-faint);
}

.notification-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.notification-list > li:not(:first-child) {
  border-top: 1px solid var(--color-divider);
}

/* Row metrics from the Notifications hi-fi: dot | icon | text | actions,
   16px gaps, the whole row a click target with the Light Green hover. */
.notification {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-4) var(--space-6) var(--space-4) var(--space-5);
  cursor: pointer;
}

/* Unread rows get the faintest mint wash so they read as "new" without
   competing with the hover colour. */
.notification--unread {
  background: #fbfffd;
}

.notification:hover {
  background: var(--color-surface-tint);
}

/* ============================== Files (issue #2, spec 2026-09-04) ============================== */

.file-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.file-list__empty {
  color: var(--color-text-muted);
  font-size: var(--text-sm);
  padding: 8px 0;
}
.file-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 10px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  min-width: 0;
}
.file-item__icon {
  display: inline-flex;
  color: var(--color-text-muted);
  flex: none;
}
.file-item__name {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--color-text);
  text-decoration: none;
}
.file-item__name:hover { text-decoration: underline; }
.file-item__size {
  flex: none;
  color: var(--color-text-muted);
  font-size: var(--text-sm);
  font-variant-numeric: tabular-nums;
}
.file-item__remove {
  flex: none;
  width: 22px;
  height: 22px;
  border: none;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--color-text-muted);
  font-size: var(--text-md);
  line-height: 1;
  cursor: pointer;
}
.file-item__remove:hover {
  color: var(--color-text);
  background: var(--color-surface-tint);
}

.file-upload { display: flex; flex-direction: column; gap: 10px; }
.file-upload__dropzone {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  padding: 20px 16px;
  border: 1.5px dashed var(--color-border-input);
  border-radius: var(--radius-md);
  background: var(--color-surface-tint);
  cursor: pointer;
  text-align: center;
  transition: border-color 120ms, background 120ms;
}
.file-upload__dropzone:hover,
.file-upload__dropzone--active {
  border-color: var(--color-accent);
  background: rgba(70, 244, 130, 0.14);
}
.file-upload__icon { color: var(--color-text-muted); }
.file-upload__browse { color: var(--color-accent); text-decoration: underline; }
.file-upload__hint { color: var(--color-text-muted); font-size: var(--text-sm); }
.file-upload__input { position: absolute; width: 1px; height: 1px; opacity: 0; overflow: hidden; }
.file-upload__pending { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 4px; }
.file-upload__pending-item {
  display: flex; gap: 10px; align-items: baseline;
  font-size: var(--text-sm); padding: 4px 10px;
}
.file-upload__pending-item--error { color: var(--color-danger); }
.file-upload__pending-name { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.file-upload__pending-size { color: var(--color-text-muted); font-variant-numeric: tabular-nums; }
.file-upload__actions { display: flex; gap: 8px; }
.file-upload__server-error { color: var(--color-danger); font-size: var(--text-sm); margin: 0; }

.file-upload__compact-row { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; }
.file-upload__compact {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  height: 32px;
  padding: 0 var(--space-4);
  border: 1px solid var(--color-border-input);
  border-radius: var(--radius-md);
  cursor: pointer;
  color: var(--color-link);
  font-weight: 600;
  font-size: var(--text-sm);
  transition: border-color 120ms, background 120ms, color 120ms;
}
.file-upload__compact:hover,
.file-upload__compact--active {
  border-color: var(--color-accent);
  background: rgba(70, 244, 130, 0.14);
  color: var(--color-link-hover);
}
.file-upload__compact-icon { font-size: 1.1em; line-height: 1; }
.file-upload__compact-hint { color: var(--color-text-muted); font-size: var(--text-sm); }

/* Rich text (components/rich_text_editor.html.j2). Jodit's own chrome is
   flattened onto our tokens. Rendered stored HTML uses .rich-text-content so
   tables/figures read the same in the editor, on read-only pages, and in the
   styleguide round-trip.
   Hi-fi 2026-09-18 §2: the toolbar is a paper-filled strip with its own border
   and the writing surface an input-bordered box under it, so the frame belongs
   to the two parts rather than to `.rich-text` -- which is why the wrapper no
   longer carries a border of its own. The editor is only mounted by the thesis
   editor and the styleguide (Cases renders read-only rich text), so the rules
   stay unscoped. */
.rich-text-mount { position: relative; border-radius: var(--radius-md); background: var(--color-surface); }
.rich-text-mount .jodit-container:not(.jodit_inline) { border: 0; border-radius: var(--radius-md); background: transparent; }
/* A wrapping flex row: Jodit's button collection keeps its natural width and
   the trailing note takes what is left, dropping to a second toolbar line on
   a narrow viewport instead of printing over the last buttons. */
.rich-text-mount .jodit-toolbar__box { background: var(--color-surface-paper); border: 1px solid var(--color-border); border-bottom: 0; border-radius: var(--radius-md) var(--radius-md) 0 0; padding: 6px; display: flex; align-items: center; flex-wrap: wrap; gap: 4px var(--space-3); }
.rich-text-mount .jodit-toolbar-editor-collection { flex: 0 1 auto; }
.rich-text-mount .jodit-toolbar-button, .rich-text-mount .jodit-toolbar-button__button { min-width: 30px; height: 28px; border-radius: var(--radius-sm); }
.rich-text-mount .jodit-wysiwyg { font-family: var(--font-sans); font-size: var(--text-md); line-height: 1.7; color: var(--color-text); min-height: 160px; }
/* The frame is on the workplace, not on the writing surface: Jodit's
   placeholder is a sibling of the surface inside the workplace and its
   plugin copies the surface's computed font and padding onto it inline
   (inline styles win over any rule here). A border on the surface alone
   would therefore offset the caret from the placeholder by exactly its
   width, which is the 1px that survived the first fix. */
.rich-text-mount .jodit-container:not(.jodit_inline) .jodit-workplace { border: 1px solid var(--color-border-input); border-radius: 0 0 var(--radius-md) var(--radius-md); background: var(--color-surface); }
/* Jodit's own `.jodit-container:not(.jodit_inline) .jodit-wysiwyg { padding: 8px }`
   outranks a two-class selector, so the writing surface's padding has to be
   written at the same weight -- until it was, the placeholder (which Jodit
   sizes from the surface) and the caret sat on different lines. The browser's
   default `p { margin: 1em 0 }` did the rest: Jodit seeds `<p><br></p>` on
   focus, which dropped the caret a further 15px (reported 2026-09-18). */
.rich-text-mount .jodit-container:not(.jodit_inline) .jodit-wysiwyg { padding: 16px 18px; }
.rich-text-mount .jodit-wysiwyg > * { margin: 0 0 var(--space-3); }
.rich-text-mount .jodit-wysiwyg > :last-child { margin-bottom: 0; }
/* Only the family and the colour: Jodit's placeholder plugin writes the
   surface's font-size, line-height and padding onto this node as inline
   styles, so anything else set here is decoration that never applies. */
.rich-text-mount .jodit-placeholder { font-family: var(--font-sans); color: var(--color-text-placeholder); }
/* The restriction is stated, not silently expressed by missing buttons: the
   template renders the note next to the mount and rich-text.js moves it into
   the toolbar box on init (the mount itself is `display: none` once Jodit has
   taken it over, and Jodit's box is built after the template has rendered).
   Without JS the note stays under the editor, which still reads. */
/* `flex-basis: 12rem` rather than `auto`: flex decides where a line breaks
   from the item's *base* size, so an `auto` basis (the note's full text width)
   pushed the note onto a second toolbar line even where there was room for it
   shrunk. It now shares the row down to 12rem of free space and only then
   takes a line of its own. */
.rich-text__note { flex: 1 1 12rem; min-width: 0; margin-left: auto; text-align: right; white-space: normal; font-size: var(--text-xs); color: var(--color-text-faint); pointer-events: none; }
/* A paste that lost its images says so in the same slot (rich-text.js): the
   section's own rule note turns red for a moment, or a red notice takes the
   slot until the next keystroke. Red because something the author pasted is
   not in the text. */
.rich-text__note { transition: color 0.2s; }
.rich-text__note--alert, .rich-text__note--notice { color: var(--color-danger); }
.rich-text-mount:focus-within { outline: var(--focus-ring); outline-offset: 2px; }
.rich-text__source[hidden] { display: none; }

.rich-text-content { font-size: var(--text-base); line-height: var(--text-line-height); color: var(--color-text); }
.rich-text-content h2 { font-size: var(--text-lg); margin: var(--space-4) 0 var(--space-2); }
.rich-text-content h3 { font-size: var(--text-md); margin: var(--space-3) 0 var(--space-2); }
.rich-text-content p, .rich-text-content ul, .rich-text-content ol { margin: 0 0 var(--space-3); }
.rich-text-content table { border-collapse: collapse; width: 100%; margin: var(--space-3) 0; }
.rich-text-content th, .rich-text-content td { border: 1px solid var(--color-border); padding: var(--space-2) var(--space-3); text-align: left; vertical-align: top; }
.rich-text-content th { background: var(--color-surface-subtle); font-weight: var(--weight-bold); }
.rich-text-content figure { margin: var(--space-4) 0; }
.rich-text-content figure img { max-width: 100%; height: auto; display: block; }
.rich-text-content figcaption { font-size: var(--text-xs); color: var(--color-text-muted); margin-top: var(--space-2); }
.styleguide-richtext__source { white-space: pre-wrap; font-size: var(--text-xs); background: var(--color-surface-subtle); padding: var(--space-3); border-radius: var(--radius-sm); }

.request-card__attachments { display: flex; flex-direction: column; gap: 10px; margin-top: 16px; }

.notification__dot-col {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 8px;
  flex: none;
  align-self: center;
  height: 36px;
}

.notification__dot {
  width: 8px;
  height: 8px;
  border-radius: var(--radius-full);
  background: var(--green-300);
}

.icon-badge {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border-radius: var(--radius-md);
  flex: none;
  align-self: center;
}

.icon-badge--tint {
  background: var(--mint);
  color: var(--green-600);
}

.icon-badge--dark {
  background: var(--green-900);
  color: var(--green-300);
}

.notification__link {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  color: inherit;
  text-decoration: none;
  min-width: 0;
  flex: 1;
}

.notification__link:hover .notification__headline {
  color: var(--color-link-hover);
}

.notification__headline {
  font-size: var(--text-base);
  color: var(--color-text);
}

.notification--unread .notification__lead {
  font-weight: var(--weight-bold);
}

.notification__case {
  color: var(--green-600);
  font-weight: var(--weight-bold);
}

.notification__meta {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.notification__subject {
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}

.notification__meta-sep {
  font-size: var(--text-sm);
  color: var(--color-text-faint);
}

.notification__time {
  font-size: var(--text-xs);
  color: var(--color-text-faint);
}

/* Trailing actions: plain "Mark read" (unread only) then bold "Open ›". */
.notification__actions {
  display: flex;
  align-items: center;
  gap: 14px;
  flex: none;
}

.notification__dismiss {
  border: none;
  background: none;
  padding: 0;
  font: inherit;
  font-size: var(--text-sm);
  color: var(--color-link);
  white-space: nowrap;
  cursor: pointer;
}

.notification__dismiss:hover {
  color: var(--color-link-hover);
  text-decoration: underline;
}

/* Impersonation picker (dev/UAT persona picker, unified 2026-08-20). */
.identity-page--impersonate { display: block; min-height: auto; padding: 0; }
.impersonate-page { max-width: 1500px; margin: 0 auto; padding: 28px 48px 72px; }
.env-pill { display: flex; align-items: center; gap: 8px; background: rgba(240,255,250,0.08); border-radius: 8px; padding: 7px 14px; font-size: 13px; color: var(--mint); }
.env-pill__dot { width: 8px; height: 8px; border-radius: 999px; background: var(--yellow-300); }
/* Rows are <form>s (each one posts the sign-in), so this is the table
   slot rendered as a grid rather than <table>; it wears the same header
   and row metrics as `.table`. */
.persona-table__header, .persona-table__row { display: grid; grid-template-columns: 40px minmax(0,1.1fr) 190px minmax(0,1.2fr) auto; gap: 16px; align-items: center; padding: 15px 24px; }
.persona-table__header { padding-top: 14px; padding-bottom: 14px; font-size: var(--text-xs); text-transform: uppercase; letter-spacing: var(--tracking-caps); color: var(--color-text-faint); border-bottom: 1px solid var(--color-border); }
.persona-table__row { border-top: 1px solid var(--color-divider); background: var(--color-surface); cursor: pointer; }
.persona-table__row:first-of-type { border-top: none; }
.persona-table__row:hover { background: var(--color-surface-tint); }
.persona-table__avatar { width: 40px; height: 40px; border-radius: 8px; background: var(--mint); color: var(--green-600); font-size: 12px; font-weight: 700; display: flex; align-items: center; justify-content: center; }
.persona-table__name { font-size: 14px; font-weight: 700; }
.persona-table__email { font-size: 13px; color: var(--neutral-500); }
.persona-table__pills { display: flex; flex-wrap: wrap; gap: 6px; }
.persona-table__no-domains { font-size: 12px; color: var(--neutral-500); }
.persona-table__signin { justify-self: end; font-size: 13px; color: var(--green-600); font-weight: 700; background: none; border: none; cursor: pointer; }
.pill { font-size: 12px; border-radius: 8px; padding: 3px 9px; white-space: nowrap; }
.pill--dark { background: var(--green-900); color: var(--mint); }
.pill--tint { background: var(--mint); color: var(--green-600); border: 1px solid var(--color-border-tint); }

/* ---- Case editing (issue #5, spec 2026-09-01) ------------------------------
   Edit mode is the detail page with field regions swapped for inputs, the
   action surfaces frozen, and one mode indicator: the sticky save bar. */

/* Icon-only pencil entry point in the header action cluster. */
.btn--icon { width: 40px; height: 40px; padding: 0; display: inline-flex; align-items: center; justify-content: center; color: var(--green-600); }
.btn--icon:hover { color: var(--green-600); }

/* Frozen action surfaces: dimmed and inert while the mode is on. */
.edit-frozen { opacity: 0.45; }

/* Room for the viewport-fixed save bar so nothing hides behind it. */
.edit-shell { padding-bottom: 88px; }

/* Case-facts rows as inputs; markers live in each field's label line. */
.facts-edit { display: flex; flex-direction: column; margin-bottom: var(--space-4); }

/* Sticky save bar: fixed to the viewport bottom, dark green so it reads as a
   mode, mirroring the app bar. Also carries the error summary on a rejected
   save -- nothing floats elsewhere. The candidate's `.submit-bar` is the same
   bar in a different role, so it shares the geometry rather than restating it;
   its own contents are in the candidate hi-fi block below. */
.save-bar, .submit-bar { position: fixed; bottom: 0; left: 0; right: 0; z-index: 40; background: var(--green-900); padding: var(--space-3) var(--space-12); display: flex; align-items: center; justify-content: space-between; gap: var(--space-5); }
.save-bar__info { display: flex; align-items: center; gap: var(--space-3); min-width: 0; }
.save-bar__icon { display: inline-flex; color: var(--green-300); }
.save-bar__text { font-size: var(--text-base); color: var(--mint); }
.save-bar__count { font-size: var(--text-sm); color: rgba(240, 255, 250, 0.65); }
.save-bar__error-dot { width: 8px; height: 8px; border-radius: var(--radius-full); background: var(--color-danger); flex: none; }
.save-bar__anchor { color: var(--green-300); }
.save-bar__anchor:hover { color: var(--mint); }
.save-bar__discard { background: none; border: 1px solid rgba(240, 255, 250, 0.3); border-radius: var(--radius-md); padding: 7px 16px; font-family: var(--font-sans); font-size: var(--text-sm); color: var(--mint); cursor: pointer; }
.save-bar__save { background: var(--green-300); border: none; border-radius: var(--radius-md); padding: 7px 16px; font-family: var(--font-sans); font-size: var(--text-sm); font-weight: 700; color: var(--green-900); cursor: pointer; }
.save-bar__save:disabled { opacity: 0.45; cursor: default; }
.save-bar__actions { display: flex; gap: var(--space-2); flex: none; }

@media (max-width: 720px) {
  /* Stack the bar on small screens: mode line above, actions right-aligned
     below -- nothing shares a cramped row with anything else. */
  .save-bar, .submit-bar { flex-direction: column; align-items: stretch; gap: var(--space-2); padding: var(--space-3) var(--space-4); }
  .save-bar__info { flex-wrap: wrap; row-gap: var(--space-1); }
  .save-bar__actions { justify-content: flex-end; }
  .edit-shell { padding-bottom: 140px; }
}

/* Discard confirm: centered over the dimmed page. */
.modal-overlay { position: fixed; inset: 0; z-index: 50; background: rgba(5, 35, 25, 0.55); display: flex; align-items: center; justify-content: center; }
.modal { background: var(--color-surface); border-radius: var(--radius-lg); padding: var(--space-6) var(--space-8); width: 360px; max-width: calc(100vw - 2 * var(--space-6)); }
.modal__title { font-size: var(--text-lg); font-weight: 700; margin-bottom: var(--space-2); }
.modal__body { font-size: var(--text-sm); color: var(--color-text-muted); margin-bottom: var(--space-4); }
.modal__actions { display: flex; gap: var(--space-2); }

/* Post-save toast, top-right for ~5s. */
.toast { position: fixed; top: 84px; right: var(--space-6); z-index: 40; display: flex; align-items: center; gap: var(--space-3); background: var(--green-900); border-radius: var(--radius-lg); padding: var(--space-3) var(--space-4); }
.toast__check { width: 22px; height: 22px; border-radius: var(--radius-full); background: var(--green-300); color: var(--green-900); display: inline-flex; align-items: center; justify-content: center; flex: none; }
.toast__check svg { width: 12px; height: 12px; }
.toast__text { font-size: var(--text-base); color: var(--mint); }
.toast__link { font-size: var(--text-sm); font-weight: 700; color: var(--green-300); text-decoration: none; }
.toast__link:hover { color: var(--mint); }

/* In edit mode the Title field lives in the header's caller slot; stretch the
   slot and the field so the input fills the row beside the case number. */
.edit-shell .page-header__actions { flex: 1; min-width: 0; }
.edit-shell .page-header__actions .field { width: 100%; margin: 0; }
/* In edit mode the title block holds only the case number; the input, not the
   number, takes the row's free space. */
.edit-shell .page-header__title-block { flex: none; }
/* Center the case number on the Title *input*, not the whole field: the label
   row sits above the input, so flex centering alone would land ~12px high.
   Offset = label row (label + 6px gap, ~28px with the actions offset) plus
   half the input/heading height difference. */
.edit-shell .page-header__title-block { margin-top: 30px; }

/* Dirty-field cue (case editing, decided 2026-09-01): the label-line "changed"
   marker plus a green border while the value differs from the stored one. The
   marker slot is shared with "required" -- field_label hides the requirement
   hint while changed shows. Error red always wins over dirty green. */
.field__changed { text-transform: none; letter-spacing: 0; color: var(--green-600); font-weight: var(--weight-bold); }
.field--dirty:not(.field--error) .field__input,
.field--dirty:not(.field--error) .field__select,
.field--dirty:not(.field--error) .field__textarea { border-color: var(--green-600); }

/* Edited-event change rows in the activity stream (spec 2026-09-01 panel E):
   one "> Field old -> new" line per changed field; the description's old body
   hides behind a reveal instead of printing a diff inline. */
.edit-changes { list-style: none; margin: var(--space-2) 0 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-1); }
.edit-changes__row { display: flex; gap: var(--space-2); font-size: var(--text-base); }
.edit-changes__marker { color: var(--green-600); font-weight: 700; flex: none; }
.edit-changes__arrow { color: var(--color-text-muted); }
.edit-changes__toggle { background: none; border: none; padding: 0; cursor: pointer; font-family: var(--font-sans); font-size: var(--text-base); font-weight: 700; color: var(--green-600); }
.edit-changes__toggle:hover { color: var(--green-700); }
.edit-changes__previous { display: block; margin-top: var(--space-2); padding: var(--space-2) var(--space-3); border: 1px solid var(--color-border); border-radius: var(--radius-md); color: var(--color-text-secondary); white-space: pre-wrap; }

/* Thesis (spec 2026-09-16 §8): the version editor is one scroll with a sticky
   rail of every section; the review view swaps the rail for a candidate
   sidebar. `.text-danger` is a small pre-existing gap (used already by
   `_list_table.html.j2` with no rule) closed here since this module leans on
   it heavily. */
.text-danger { color: var(--color-danger); }
/* Start-thesis live email match (hi-fi §4): the success helper needs the same
   bold-green treatment as `.hint-list`'s marker but inline, not a list. */
.text-success { color: var(--color-success); }

/* The rail's progress track (hi-fi 2026-09-18 §2 `.progress-pill`): 6px, fully
   rounded, tinted rather than neutral because it sits on a mint card. The
   pre-hi-fi `.thesis-progress` is the same track, so it shares the rule
   instead of drifting from it. */
.progress-pill, .thesis-progress { height: 6px; border-radius: var(--radius-full); background: rgba(40, 120, 65, 0.14); overflow: hidden; }
.progress-pill > div, .thesis-progress div { height: 100%; border-radius: inherit; background: var(--green-600); }

.section-footer--over { color: var(--color-danger); }

.thesis-thread { border-top: 1px solid var(--color-divider); margin-top: 28px; padding-top: 22px; display: flex; flex-direction: column; gap: 22px; }
.thesis-thread__earlier summary { cursor: pointer; color: var(--color-text-muted); font-size: var(--text-sm); }

/* Appendices (C-5, staff side) and the inline image panel: both dashed and
   muted on purpose -- supplementary material must not read as a primary
   affordance. The candidate's appendices live in a `.card--dashed` rail card. */
.thesis-appendices { border: 1px dashed var(--color-border); border-radius: var(--radius-md); padding: var(--space-4); display: flex; flex-direction: column; gap: var(--space-3); }

/* The panel's box is written with the rest of the hi-fi vocabulary further
   down; only its placement under the editor belongs here. The legacy rules
   that styled its bare labels and inputs are gone with the markup they
   styled. `thesis-image-panel` stays the sole root class -- it is the hook
   the editor JS and the "one panel per visuals-allowed section" tests use,
   and a second class on the root would break the literal-attribute count. */
/* A `display` of anything but `none` beats the user agent's
   `[hidden] { display: none }`, so the panel has to hide itself explicitly:
   without this it sat open under every
   visuals-allowed section from page load, and Insert could be clicked before
   the image button had ever named an editor to insert into -- which is where
   "panel._insert is not a function" came from (reported 2026-09-18). */
.thesis-image-panel[hidden] { display: none; }

/* Approval (S-6): the two states of the approve page, and the PDF card the
   overview shows once a thesis is approved. Ready is tinted and bordered in
   the success green, blocked keeps the dashed "incomplete" treatment. */
.thesis-callout { border-radius: var(--radius-md); padding: var(--space-4); display: flex; flex-direction: column; gap: var(--space-2); margin-bottom: var(--space-5); }
.thesis-callout--ready { background: var(--color-surface-tint); border: 1px solid var(--color-success); }
.thesis-callout--blocked { border: 1px dashed var(--color-danger); }
.thesis-callout ul { margin: 0; padding-left: var(--space-5); }

.thesis-pdf-card { display: flex; gap: var(--space-3); align-items: flex-start; }
.thesis-pdf-card__icon { width: var(--space-4); height: var(--space-5); border: 1px solid var(--color-border); border-radius: var(--radius-sm); background: var(--color-surface-tint); flex: none; display: inline-flex; align-items: center; justify-content: center; }
.thesis-pdf-card__icon svg { width: 14px; height: 14px; color: var(--green-600); }
/* The approved rail's document tile is the same card on mint: the tile turns
   white so it still reads as a sheet of paper against the tint. */
.card--tint .thesis-pdf-card__icon { background: var(--color-surface); border-color: var(--color-border-tint-strong); }

/* Version comparison (S-3): two independently scrollable panes kept in sync
   by proportional scrollTop (see compare.html.j2's inline script), so panes
   of different length still track each other sensibly. */
.thesis-compare { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-6); align-items: start; }
.thesis-compare__pane { max-height: 70vh; overflow: auto; display: flex; flex-direction: column; gap: var(--space-5); padding-right: var(--space-2); }
.thesis-compare__section { border: 1px solid var(--color-border); border-radius: var(--radius-lg); padding: var(--space-4); }
.thesis-compare__section--changed { border-left: 3px solid var(--color-accent); }
.thesis-compare__picker { display: flex; align-items: center; gap: var(--space-2); }
/* The screen-share switch: right-aligned on the first line under the page
   header, pulled up into the header's bottom margin so it reads as part of the
   header rather than a new section. On a `.thesis-status` row it shares the
   line with the status chip instead. The segmented control's caption sits
   inline to its left here rather than stacked above it, so the bar stays one
   slim line; scoped to the bar, the macro is unchanged for other callers. */
.hide-internal-bar { display: flex; justify-content: flex-end; margin: calc(-1 * var(--space-4)) 0 var(--space-3); }
.thesis-status > .hide-internal-bar { margin: 0 0 0 auto; }
.hide-internal-bar .field { flex-direction: row; align-items: center; gap: var(--space-2); margin: 0; }
.diff-ins { background: var(--color-accent); }
.diff-del { text-decoration: line-through; color: var(--color-text-faint); }

/* ---- Thesis hi-fi ---- */
/* Shared vocabulary for the 2026-09-18 hi-fi pass (spec §2): rail/detail layout
   modifiers, status circles, stepper, section/version/comment rows and the
   small odds and ends (`.text-caps`, `.list-legend`) the mock relies on that
   had no rule yet. Values come from tokens.css; the rgba(40,120,65,*) tints
   the mock hand-carries are kept literal for one-off alphas and promoted to a
   sibling token (`--color-border-tint-strong`) only where reused 2+ times. */

.text-caps { font-size: var(--text-xs); text-transform: uppercase; letter-spacing: var(--tracking-caps); color: var(--color-text-faint); font-weight: 700; margin: 0; }

.detail-stack { display: flex; flex-direction: column; gap: 28px; min-width: 0; }

.card--rail { padding: 26px 28px; }
.card__title--md { font-size: var(--text-lg); font-weight: 700; margin: 0; }

.hint-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 11px; font-size: var(--text-sm); line-height: 1.55; color: var(--color-text-secondary); }
.hint-list li { display: flex; gap: var(--space-2); }
.hint-list li::before { content: "\203A"; font-weight: 700; color: var(--green-600); flex: none; }

/* The one line of prose under the page title (hi-fi part 2 §3): it explains
   the whole screen to the candidate, so it sits with the header rather than in
   a card, and is measure-capped so it stays one readable paragraph. */
.page-subtitle { font-size: var(--text-sm); color: var(--color-text-muted); max-width: 640px; margin-top: var(--space-2); }

/* The bottom margin is the `<p>` default made explicit: the row became a
   `<div>` where it carries the hide-internal form (a form cannot sit in a
   `<p>`), and must keep the spacing the paragraph had. */
.thesis-status { display: flex; align-items: center; gap: var(--space-3); margin: 14px 0 1em; font-size: var(--text-sm); color: var(--color-text-muted); }
.thesis-status .text-danger { font-weight: 700; }
/* Late Hiring's "you are editing someone else's thesis" line on the staff
   editor: one quiet tinted row, not a card -- it is a reminder, not content.
   It sits between the status row (which keeps its own bottom margin) and the
   section grid, so its bottom margin is the grid's own 28px row gap. */
.author-banner {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin: 0 0 28px;
  padding: 10px 16px;
  border: 1px solid var(--color-border-tint);
  border-radius: var(--radius-md);
  background: var(--color-surface-tint);
  font-size: var(--text-sm);
  line-height: 1.5;
  color: var(--color-text-secondary);
}
.author-banner svg { width: 15px; height: 15px; flex: none; color: var(--green-600); }

.step-circle { display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 20px; border-radius: var(--radius-full); flex: none; }
.step-circle--done { background: var(--green-600); position: relative; }
.step-circle--done::after { content: ""; position: absolute; left: 5px; top: 5px; width: 8px; height: 4px; border: solid var(--color-surface); border-width: 0 0 2px 2px; transform: rotate(-45deg); }
.step-circle--open { border: 2px solid var(--color-border-tint-strong); }
.step-circle--deferred { border: 2px dashed rgba(40, 120, 65, 0.3); }
/* Over the limit: an empty red ring, not a filled state -- the section has
   content, so a tick would be a lie and a cross would read as an error the
   candidate cannot clear by writing less. */
.step-circle--over { border: 2px solid var(--color-danger); }

.stepper { display: flex; align-items: center; }
.stepper__step { display: flex; align-items: center; gap: var(--space-2); }
.stepper__node { display: inline-flex; align-items: center; justify-content: center; width: 22px; height: 22px; border-radius: var(--radius-full); flex: none; }
.stepper__node--current { background: var(--green-300); box-shadow: 0 0 0 3px rgba(70, 240, 130, 0.32); }
.stepper__node--done { background: var(--green-600); }
.stepper__node--future { border: 2px solid var(--color-border-tint-strong); }
.stepper__label { font-size: var(--text-base); color: var(--color-text-muted); }
.stepper__label--current { font-weight: 700; color: var(--color-text); }
.stepper__label--future { color: var(--color-text-muted); }
.stepper__link { flex: 1; height: 2px; background: rgba(40, 120, 65, 0.18); margin: 0 var(--space-4); }
.stepper__hint { font-size: var(--text-sm); border-top: 1px solid rgba(40, 120, 65, 0.14); padding-top: var(--space-3); margin-top: var(--space-3); }
.stepper__hint::before { content: "\203A"; font-weight: 700; color: var(--green-600); margin-right: var(--space-2); }

/* The progress card is the one flush card: its head, bar and table each own
   their horizontal padding so the row dividers reach the card's edges. */
.card--flush { padding: 0; overflow: hidden; }
.progress-card__head { padding: 24px 36px 18px; display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-4); }
.progress-card__count { font-size: var(--text-sm); }
.progress-card__bar { padding: 0 36px; margin-bottom: 14px; }

/* The grid lives on each row rather than on the container (which would make
   the columns one grid and confine the row dividers to the padding box) --
   that is what gives the mock's full-bleed `border-top` per row. */
.section-table__header, .section-table__row { display: grid; grid-template-columns: 32px 1fr 140px 120px; gap: 16px; align-items: center; }
.section-table__header { font-size: var(--text-xs); text-transform: uppercase; letter-spacing: var(--tracking-caps); color: var(--color-text-faint); font-weight: 700; padding: 0 36px 10px; }
.section-table__row { font-size: var(--text-sm); padding: 13px 36px; border-top: 1px solid var(--color-divider); }
.section-table__row:last-child { padding-bottom: 20px; }
/* The owner's open-version table carries two more columns than the staff one
   (To address, and the row's own "Open ›"), so the grid is a modifier on the
   table rather than a second block. */
.section-table--owner .section-table__header, .section-table--owner .section-table__row { grid-template-columns: 32px 1fr 140px 120px 90px; }
/* Three columns before V1: nothing is written, so there is nothing to address
   and nowhere for a row to link. */
.section-table--plain .section-table__header, .section-table--plain .section-table__row { grid-template-columns: 32px 1fr 140px; }
.section-table__go { text-align: right; font-size: var(--text-sm); font-weight: 700; color: var(--green-600); }
.section-table__num--over { font-weight: 700; color: var(--color-danger); }
.section-table__num--due { font-weight: 700; color: var(--green-600); }
.section-table__row[data-href] { cursor: pointer; }
.section-table__row[data-href]:hover { background: var(--color-surface-tint); }
.section-table__row--faint, .section-table__row--faint a { color: var(--color-text-faint); }
.section-table__num { text-align: right; }
.section-table__title a { color: inherit; text-decoration: none; }
.section-table__title a:hover { color: var(--color-link); }

.section-card { padding: 32px 36px; }
/* The card is a column of parts (head, guidance, editor, status, thread)
   rather than flowing prose, so the gap belongs to the card. */
.section-card > * + * { margin-top: var(--space-3); }
.section-card__head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-4); margin-bottom: 6px; }
.section-card__no { font-size: var(--text-base); font-weight: 700; color: var(--green-600); margin-right: var(--space-3); }
.section-card__title { font-size: var(--text-xl); font-weight: 700; }
.section-card__words { font-size: var(--text-sm); color: var(--color-text-muted); white-space: nowrap; }
.section-edited { font-size: var(--text-xs); color: var(--color-text-muted); }
/* Over the limit the whole counter goes red and bold -- the number and its
   budget are one statement, and a red number beside a grey budget reads as a
   styling accident. */
.section-card__words.text-danger { color: var(--color-danger); font-weight: 700; }
/* The red border is derived from the status line as well as set on the card:
   autosave swaps the status line, the counter and the rail row, but never the
   <section> itself, so a card that goes over the limit between page loads
   would keep a neutral border if the static class were the only source. Both
   are kept -- the class is what a full render emits, `:has` is what keeps it
   true after a save. */
.section-card--over, .section-card:has(.section-footer--over) { border-color: var(--color-danger); }
/* Section guidance: a collapsible outline above the editor, open by default
   (_guidance.html.j2). Faint and small like the one-line objective it
   replaced, so the writer's own text stays the loudest thing on the card;
   bold and the two bullet levels come from the catalogue's guidance editor. */
.section-guidance { margin: 0 0 20px; font-size: var(--text-sm); color: var(--color-text-faint); }
.section-guidance > summary { display: inline-flex; align-items: center; gap: var(--space-2); cursor: pointer; font-weight: 600; color: var(--color-text-secondary); list-style: none; }
.section-guidance > summary::-webkit-details-marker { display: none; }
.section-guidance__chevron { flex: none; transform: rotate(-90deg); transition: transform 0.12s ease; }
.section-guidance__chevron svg { display: block; width: 14px; height: 14px; }
.section-guidance[open] .section-guidance__chevron { transform: rotate(0deg); }
.section-guidance__body { margin-top: var(--space-2); font-size: var(--text-sm); line-height: 1.6; color: var(--color-text-faint); }
.section-guidance__body strong { color: var(--color-text-secondary); }
.section-guidance__body p, .section-guidance__body ul { margin: 0 0 var(--space-2); }
.section-guidance__body ul { padding-left: 1.25em; }
.section-guidance__body ul ul { list-style-type: circle; margin: 0; }
/* Breathing room between bullets, not after the last one: the gap belongs to
   the pair, so `li + li` (and a nested list keeps the same gap from the line
   that introduces it). --space-1 (4px) is the scale's step nearest the 5px
   asked for; the catalogue's editor mirrors it so what staff write is what
   candidates see. */
.section-guidance__body li + li, .section-guidance__body li > ul,
.catalogue-edit .jodit-wysiwyg li + li, .catalogue-edit .jodit-wysiwyg li > ul { margin-top: var(--space-1); }
.section-card__body { font-size: var(--text-md); line-height: 1.7; color: var(--color-text-secondary); max-width: 760px; }

.version-row { display: grid; grid-template-columns: 36px 1fr 70px; gap: 16px; align-items: start; }
.version-row__badge { width: 36px; height: 36px; border-radius: var(--radius-md); display: inline-flex; align-items: center; justify-content: center; font-size: var(--text-sm); font-weight: 700; flex: none; }
.version-row__badge--accent { background: var(--green-300); color: var(--green-900); }
.version-row__badge--neutral { background: var(--neutral-50); color: var(--color-text-secondary); }
.version-row__title { font-size: var(--text-base); font-weight: 700; margin: 0; }
.version-row__meta { font-size: var(--text-xs); color: var(--color-text-faint); margin: 0; }
.version-row__line { font-size: var(--text-base); line-height: 1.6; color: var(--color-text-secondary); margin: 6px 0 0; }
.version-row--next { color: var(--color-text-faint); }
.version-row:hover { background: var(--color-surface-tint); margin: 0 -36px; padding: 0 36px; }
.version-row[data-href] { cursor: pointer; }
.version-list { display: flex; flex-direction: column; }
.version-list .version-row { padding-bottom: 18px; }
.version-list .version-row + .version-row { padding-top: 18px; border-top: 1px solid var(--color-divider); }
.version-list__foot { margin: var(--space-4) 0 0; }
.version-row .chevron-link { font-size: var(--text-sm); font-weight: 700; justify-self: end; }

/* Stepper banner and rail cards: the mock's paddings and its 16px rail card
   titles (`.card__title--md`), plus the small person/·note blocks the rail
   uses for the reviewer. */
/* The banner's own margins, not a rule on `.detail-grid`: the grid is shared
   with every other detail page, and in edit mode the banner is wrapped in
   `.edit-frozen` (opacity only, no BFC) so these collapse through the wrapper
   and the gap holds either way. 26px above / 32px below = the mock's banner
   section `padding: 26px 48px 0` against the body's `32px 48px 56px`. */
.stepper-card { padding: 24px 32px; margin: 26px 0 32px; }
.rail-card__title { margin-bottom: 18px; }
.rail-card__title--icon { display: flex; align-items: center; gap: var(--space-2); margin-bottom: 14px; }
.rail-card__title--icon svg { width: 15px; height: 15px; color: var(--green-600); }
.rail-person { display: flex; align-items: center; gap: var(--space-3); }
.rail-person__name { font-size: var(--text-base); font-weight: 700; margin: 0; }
.rail-person__meta { font-size: var(--text-xs); color: var(--color-text-muted); margin: 0; }
.rail-invite {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-2);
  margin-top: var(--space-4);
}
.rail-note { font-size: var(--text-xs); line-height: 1.5; margin: 6px 0 0; }
.rail-note svg { width: 13px; height: 13px; vertical-align: -1px; }
.rail-person + .fact-list, .rail-person + .rail-note { margin-top: var(--space-4); }

/* The bubble, not the root, carries the visible frame: a published comment
   reads as ordinary text (spec §7), so `.comment--internal` is a hook for the
   root only and the dashed treatment stays on `.comment__bubble--internal`. */
.comment { display: grid; grid-template-columns: 36px 1fr; gap: 16px; }
.comment__bubble { border-radius: var(--radius-md); padding: 14px 16px; }
/* Spec 2026-10-13: the dashed mint frame now means "unpublished" (written for
   the candidate, not yet released); an internal note -- never released -- is a
   solid neutral surface with a lock, so the two cannot be mistaken. */
.comment__bubble--unpublished { background: var(--color-surface-paper); border: 1px dashed rgba(40, 120, 65, 0.35); }
.comment__bubble--internal { background: var(--neutral-100); border: 1px solid var(--color-border); }
.comment__lock svg { width: 12px; height: 12px; vertical-align: -1px; margin-right: 4px; color: var(--color-text-faint); }
.comment__head { display: flex; align-items: baseline; flex-wrap: wrap; gap: 10px; }
.comment__author { font-size: var(--text-base); font-weight: 700; margin: 0; }
.comment__meta { font-size: var(--text-xs); color: var(--color-text-faint); }
.comment__body { font-size: var(--text-base); line-height: 1.6; color: var(--color-text-secondary); margin: 6px 0 0; }
.comment__actions { display: flex; align-items: center; gap: var(--space-3); margin-top: var(--space-3); }
/* A thread is a top-level comment plus its replies (spec 2026-10-14): tighter
   than the 22px between threads, so a reply reads as belonging to the comment
   above it. Replies indent by the parent's avatar column + gap (36 + 16), so
   the reply's avatar sits under the parent's bubble; one level, never deeper. */
.comment-thread { display: flex; flex-direction: column; gap: 12px; }
.comment--reply { margin-left: 52px; grid-template-columns: 28px 1fr; gap: 12px; }
.comment--reply .comment__bubble { border: 1px solid var(--color-border); padding: 10px 14px; }
/* The thread's one Reply button sits under its lowest reply, lined up with
   the reply bubbles (52 indent + 28 avatar + 12 gap) -- the column the
   composer's textarea opens in when it is clicked. */
.comment-thread__reply { margin-left: 92px; }

/* The composer is the same 36px/1fr grid as a comment, so the viewer's avatar
   lines up with the authors' above it; the fields are a nested column because
   the grid's second track holds one child. */
.thesis-composer { display: grid; grid-template-columns: 36px 1fr; gap: 16px; border-top: 1px solid var(--color-divider); padding-top: 22px; }
.thesis-composer__fields { display: flex; flex-direction: column; gap: 14px; min-width: 0; }
/* One row: visibility control + category chip on the left, "Post comment"
   pushed to the far right. `justify-content: space-between` (the pre-hi-fi
   rule) only works with exactly two flex children -- the row has three
   (control, select, button) -- so the button instead gets its own
   `margin-left: auto`, and the row packs its remaining children to the start. */
.thesis-composer__row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 14px; }
.thesis-composer__row > .btn { margin-left: auto; padding: 0 var(--space-3); }
/* Visibility control, category and "Post comment" share one line at thread
   width (owner, 2026-10-13): the control's caption is screen-reader-only
   (`label_class="sr-only"`), `.field`'s bottom margin would pull the control
   off the row's centre line, and the segments are a notch tighter than the
   form default. The fit was 2px at full size, so the category select may give
   up a little width (down to 6rem) before anything wraps. */
.thesis-composer__row .field { margin: 0; }
.thesis-composer__row .segmented { gap: 2px; }
.thesis-composer__row .segmented__option { padding: 6px 8px; }
/* The category select reads as a filter chip, not a full-width form field:
   `.field__select` is `width: 100%`/block by default (it's meant for a
   one-per-row form layout), so the composer overrides both to auto width and
   an inline flex box that sizes to its content. */
.thesis-composer__row .field__select--sm { width: auto; display: inline-flex; flex: 1 1 6rem; min-width: 0; max-width: max-content; }
.thesis-composer .field__textarea { min-height: 64px; }
/* The thread supplies its own 22px gap; a composer that is the thread's only
   child would otherwise sit under a divider with nothing above it. */
.thesis-thread > .thesis-composer:first-child { border-top: 0; padding-top: 0; }
/* The reply composer sits where the next reply will appear: same indent and
   28px avatar column, no divider (the thread's own top rule is enough). Two
   buttons share the row, so only the first gets the push to the right. */
.thesis-composer--reply { border-top: 0; padding-top: 0; margin-left: 52px; grid-template-columns: 28px 1fr; gap: 12px; }
.thesis-composer--reply .thesis-composer__row > .btn + .btn { margin-left: 0; }

/* Rail index rows: the Sections list and the two comment tallies are the same
   divider-separated row, so they share one class rather than two near-copies. */
.rail-index { list-style: none; margin: 0; padding: 0; }
.rail-index__row { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); padding: 5px 0; font-size: var(--text-base); }
/* Only the tinted Sections card separates its rows (mock): the two tallies on
   a white card read as a pair, not as a list. */
.card--tint .rail-index__row { padding: 9px 0; border-bottom: 1px solid rgba(40, 120, 65, 0.12); }
.card--tint .rail-index__row:last-child { border-bottom: 0; padding-bottom: 0; }
.rail-index__label, .rail-index__row a { display: flex; align-items: center; gap: 10px; color: inherit; text-decoration: none; min-width: 0; }
.rail-index__row a:hover { color: var(--color-link); }
.rail-index__no { font-weight: 700; color: var(--green-600); flex: none; }
.rail-index__count { display: inline-flex; align-items: center; gap: 6px; font-size: var(--text-xs); color: var(--color-text-secondary); flex: none; }
.rail-index__dot { width: 6px; height: 6px; border-radius: var(--radius-full); background: var(--green-600); flex: none; display: inline-block; }
.rail-index__dot--over { background: var(--color-danger); }
.rail-index__count--over { color: var(--color-danger); font-weight: 700; }
/* The editor rail states three different things on its right edge (a live
   count, "V2", "Final version"), so the row emphasis carries which sections
   are the candidate's to write now. */
.rail-index__row--strong a { font-weight: 700; }
.rail-index__row--faint, .rail-index__row--faint .rail-index__count { color: var(--color-text-faint); }
.rail-index__row--faint .rail-index__no { color: var(--color-text-faint); }
.rail-progress__head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-3); margin-bottom: 12px; }
.rail-progress__count { font-size: var(--text-xs); color: var(--color-text-muted); }
.rail-progress .progress-pill, #rail-progress .progress-pill { margin-bottom: 14px; }
.card--rail .rail-index + .rail-note { margin-top: 14px; }

.list-legend { display: flex; align-items: center; gap: var(--space-2); font-size: var(--text-xs); color: var(--color-text-muted); margin-top: 14px; }
.list-legend__dot { width: 8px; height: 8px; border-radius: var(--radius-full); background: var(--color-danger); flex: none; }

.rail--sticky { position: sticky; top: 24px; }

.form-card { max-width: 720px; }
.form-actions { display: flex; gap: var(--space-3); margin-top: var(--space-8); }

.field__select--sm { padding: 6px 10px; font-size: var(--text-sm); }

.publish-row { display: grid; grid-template-columns: 16px 36px 1fr; gap: 16px; align-items: start; padding: 20px 0; border-top: 1px solid var(--color-divider); }
.publish-row.is-unselected { opacity: 0.55; }
.publish-row__ref { color: var(--green-600); }
.publish-row__stays { margin-top: var(--space-2); }
.form-actions__note { margin-left: auto; }

.checklist { display: flex; flex-direction: column; gap: var(--space-3); }
.checklist__row { display: flex; align-items: center; gap: 10px; font-size: var(--text-sm); }
.checklist__foot { font-size: var(--text-xs); color: var(--color-text-muted); margin-top: var(--space-4); padding-top: var(--space-3); border-top: 1px solid rgba(40, 120, 65, 0.14); }

/* The edit mode's rail fields sit under a frozen fact list; without this the
   first label crowds the last fact row. */
.card--rail .edit-frozen + .field, .card--rail .fact-list + .field { margin-top: var(--space-4); }
.card--rail .field:last-child { margin-bottom: 0; }

/* ---- Candidate hi-fi (part 2 spec §2) ---- */
/* The vocabulary the candidate screens are assembled from (tasks 3-6 consume
   these). Same rule as the block above: tokens, with the mock's one-off
   on-mint and on-dark alphas kept literal. */

/* A published comment reads as ordinary text with a green frame; marking it
   addressed downgrades the frame to neutral and swaps the button for a footer,
   so state is carried by border + tag + footer and nothing else moves. */
.comment--published .comment__bubble { border: 1px solid var(--green-600); }
.comment--addressed .comment__bubble { border: 1px solid var(--neutral-300); }
.comment__foot { font-size: var(--text-xs); color: var(--color-text-faint); margin: var(--space-3) 0 0; }

.card--dashed { border: 1px dashed var(--neutral-400); border-radius: var(--radius-lg); padding: 22px 28px; }
.well { background: var(--color-surface-paper); border: 1px solid var(--color-border); border-radius: var(--radius-md); }

/* Previous-version fold: inert reference text, so it is muted on paper and has
   no toolbar, counter or hover affordance beyond the summary row. */
.version-fold > summary { display: flex; align-items: baseline; gap: var(--space-2); padding: 12px 16px; cursor: pointer; font-size: var(--text-base); font-weight: 700; list-style: none; }
.version-fold > summary::-webkit-details-marker { display: none; }
.version-fold[open] > summary { border-bottom: 1px solid var(--color-border); }
/* One `chevron-down` asset, turned: closed points right, open points down.
   The rotation is on the closed state so the icon's own orientation is the
   open one -- that is the state the name describes. */
.version-fold__chevron { color: var(--color-text-faint); flex: none; transform: rotate(-90deg); transition: transform 0.12s ease; }
.version-fold__chevron svg { display: block; width: 14px; height: 14px; }
.version-fold[open] .version-fold__chevron { transform: rotate(0deg); }
.version-fold__meta { font-weight: 400; color: var(--color-text-faint); font-size: var(--text-sm); }
.version-fold__body { padding: 20px 16px 22px; font-size: var(--text-base); line-height: 1.7; color: var(--color-text-muted); }
.version-fold__foot { display: flex; align-items: center; gap: var(--space-2); border-top: 1px solid var(--color-border); padding: 12px 16px; font-size: var(--text-xs); color: var(--color-text-faint); }
.version-fold__foot svg { width: 13px; height: 13px; }

/* Save status left, comment tally right -- one line, so the section never
   grows a second row of chrome. */
.section-status { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-4); margin-top: var(--space-2); font-size: var(--text-xs); color: var(--color-text-faint); }
.section-status .text-danger { font-weight: 700; }

/* Submit bar: the save bar's fixed geometry (shared selector above) with the
   candidate's own contents. The disabled pill is the mock's dimmed on-dark
   treatment, not a `.btn` variant -- opacity on green-300 would still read as
   an available action. */
/* The bar is one <form> so the confirm dialog can be its submitter; the form
   therefore carries the row layout the bar itself would otherwise have. */
.submit-bar__form { flex: 1; display: flex; align-items: center; justify-content: space-between; gap: var(--space-5); }
.submit-bar__info { display: flex; align-items: center; gap: var(--space-3); }
.submit-bar__dot { width: 8px; height: 8px; border-radius: var(--radius-full); background: var(--color-danger); flex: none; }
.submit-bar__dot--ok { background: var(--green-300); }
.submit-bar__text { font-size: var(--text-base); font-weight: 700; color: var(--mint); }
.submit-bar__reason { font-size: var(--text-sm); color: rgba(240, 255, 250, 0.65); margin: 2px 0 0; }
.submit-bar__button { background: var(--green-300); border: none; border-radius: var(--radius-md); padding: 7px 16px; font-family: var(--font-sans); font-size: var(--text-sm); font-weight: 700; color: var(--green-900); cursor: pointer; }
.submit-bar__button:disabled { background: rgba(240, 255, 250, 0.14); color: rgba(240, 255, 250, 0.5); cursor: default; }

.tabs-underline__count { display: inline-block; margin-left: 6px; background: var(--mint); color: var(--green-600); border-radius: var(--radius-md); padding: 1px 6px; font-size: var(--text-2xs); font-weight: 700; }

/* The approved state's tag: filled rather than outlined, because approval is
   terminal and the chip is the page's one piece of status colour. No dot. */
.tag--filled { background: var(--green-900); border-color: var(--green-900); color: var(--mint); }
.tag--filled .tag__dot { display: none; }

/* Sections that open with a later version: one dashed card for all of them,
   never an editor that cannot be submitted. */
.locked-ahead { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--space-4); padding: 24px 36px; }
.locked-ahead__title { font-size: var(--text-md); font-weight: 700; color: var(--color-text-secondary); margin: 0; }
.locked-ahead__list { font-size: var(--text-sm); line-height: 1.6; color: var(--color-text-muted); margin: 6px 0 0; }
/* One group per version the catalogue opens sections at: a catalogue with
   V2 and V3 sections gets two headings, not one that averages them. */
.locked-ahead__group + .locked-ahead__group { margin-top: 14px; }
.locked-ahead__note { font-size: var(--text-sm); line-height: 1.6; color: var(--color-text-muted); margin: 10px 0 0; }
.locked-ahead svg { width: 18px; height: 18px; color: var(--color-text-faint); flex: none; }

/* Insert image: the image button's toolbar popup (2026-10-12), so it is
   sized for a dropdown -- one column, no card chrome of its own (Jodit's popup
   draws the frame). Required markers stay muted until Insert is attempted;
   `.is-invalid` is then set on the failing field only. */
.thesis-image-panel { width: min(420px, calc(100vw - 32px)); background: var(--color-surface); padding: 16px 18px; }
.image-panel__title { font-size: var(--text-md); font-weight: 700; margin: 0; }
.image-panel__helper { font-size: var(--text-xs); color: var(--color-text-faint); margin: 4px 0 0; }
.image-panel__grid { display: grid; grid-template-columns: 1fr; gap: 14px; margin: 16px 0; }
.image-panel__grid > .field { margin-bottom: 0; }
/* The File field spans the grid; its control is a row (button + chosen name)
   under the label, which is why the flex row is its own element. */
.image-panel__file { grid-column: 1 / -1; }
.image-panel__file-row { display: flex; align-items: center; gap: var(--space-3); font-size: var(--text-sm); color: var(--color-text-secondary); }
/* `position: relative` keeps the sr-only file input inside the label, so
   focusing it does not scroll the page to wherever an absolute box lands. */
.image-panel__choose { position: relative; border: 1px solid var(--green-600); border-radius: var(--radius-md); background: var(--color-surface); padding: 8px 14px; font-family: var(--font-sans); font-size: var(--text-sm); color: var(--green-600); cursor: pointer; }
/* Chosen state: preview, then name and size, then a small Replace. The
   preview is capped so a tall chart does not push Insert off the popup. */
/* Jodit caps popup content at 450px and scrolls inside it, which hid Insert
   below the fold once a preview was showing; this popup may use the viewport
   (less the fixed submit bar) and only scrolls on a genuinely short screen. */
.jodit-popup__content:has(> .thesis-image-panel) { --jd-popup-max-height: calc(100vh - 120px); }
.image-panel__preview { display: block; width: 100%; max-height: 150px; object-fit: contain; background: var(--color-surface-tint); border: 1px solid var(--color-border); border-radius: var(--radius-md); margin-bottom: var(--space-3); }
.image-panel__name { flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere; color: var(--color-text-secondary); }
.image-panel__name strong { font-size: var(--text-md); font-weight: 600; color: var(--color-text); }
.image-panel__file-row.is-chosen .image-panel__choose { border: 0; background: none; padding: 0; text-decoration: underline; flex: none; }
.image-panel__choose:has(input:focus-visible) { outline: var(--focus-ring); outline-offset: 2px; }
.image-panel__actions { display: flex; align-items: center; gap: var(--space-2); }
/* Only the marker and the control turn red (mock C2b): the label names the
   field and the helper explains it -- neither becomes untrue when the field is
   empty, and reddening them spends the error colour on text that is not the
   error. The failing field's own message is a `.form-error`. */
.thesis-image-panel .is-invalid .field__required { color: var(--color-danger); }
.thesis-image-panel .is-invalid input, .thesis-image-panel .is-invalid textarea { border-color: var(--color-danger); }

/* Call log: one row per version, the value faint when the call has not
   happened yet. */
.call-log { display: flex; flex-direction: column; }
.call-log__row { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-3); padding: 7px 0; font-size: var(--text-sm); }
.call-log__row + .call-log__row { border-top: 1px solid var(--color-divider); }
.call-log__value { color: var(--color-text-muted); }
.call-log__value--pending { color: var(--color-text-faint); }

/* Comments & versions (C3, hi-fi part 2 §6). Two stacked table cells and a
   rail-width versions row. The caption under the card reuses `.list-legend`
   with no `__dot` child: it is the same 12px muted line, and there is no
   threshold here to colour. */
.comment-cell { display: block; min-width: 0; }
.comment-cell__head { display: block; font-weight: var(--weight-bold); }
.comment-cell__sub { display: block; font-size: var(--text-xs); color: var(--color-text-faint); margin-top: 2px; }
.comment-cell__body { display: block; }
.comment-cell__body--open { font-weight: var(--weight-bold); }
.comment-cell__meta { display: block; font-size: var(--text-xs); color: var(--color-text-muted); margin-top: 2px; }
.comment-cell__states { display: inline-flex; flex-wrap: wrap; gap: 6px; }
.rail-version { display: grid; grid-template-columns: 1fr auto; column-gap: var(--space-3); align-items: center; padding: 10px 0; }
.rail-version + .rail-version { border-top: 1px solid var(--color-divider); }
.rail-version__head { display: flex; align-items: center; gap: var(--space-2); font-size: var(--text-base); font-weight: var(--weight-bold); }
.rail-version__meta { grid-column: 1; font-size: var(--text-xs); color: var(--color-text-muted); margin: 3px 0 0; }
.rail-version .chevron-link { grid-column: 2; grid-row: 1 / span 2; font-size: var(--text-sm); font-weight: var(--weight-bold); }
.card--rail .rail-version:first-of-type { padding-top: 0; }

/* Mirrors the existing `.detail-grid` collapse (§7 media queries, 960px):
   `.rail` is a flex column regardless of viewport, so nothing to override
   here other than dropping the sticky pin once the rail falls below the
   main column instead of beside it. */
@media (max-width: 960px) {
  .rail--sticky { position: static; top: auto; }
}

/* Catalogue edit: each section is a <tbody> of its fields and a full-width
   guidance row (catalogue.html.j2), so the guidance editor has the room an
   outline needs. */
.catalogue-edit__section ~ .catalogue-edit__section { border-top: 2px solid var(--color-border); }
.catalogue-edit__guidance > td { padding-top: 0; }
.catalogue-edit__label { display: block; font-size: var(--text-xs); font-weight: 600; color: var(--color-text-secondary); margin-bottom: 4px; }
.catalogue-edit .rich-text-mount .jodit-wysiwyg { min-height: 120px; }
/* Titles are long sentences now ("Market Analysis of the Industry & Specific
   Geography"): the title column takes 40%, about twice its old share, and the
   numeric and yes/no fields shrink to fit the rest. */
.catalogue-edit th:first-child { width: 40%; }
/* The form is the last thing on the page: its actions need room under them,
   or they sit on the viewport's bottom edge. */
.catalogue-edit__actions { display: flex; align-items: center; gap: var(--space-3); margin: var(--space-4) 0 var(--space-12); }
.catalogue-edit td input, .catalogue-edit td select { width: 100%; min-width: 0; box-sizing: border-box; }
