/*
 * The Kitchen — Out Sauce Portal
 * Design System: Clean flat + batch colour accent
 */

/* CSS Variables - Design System */
:root {
  /* Batch Colours */
  --os-aqua: #00F5D4;
  --os-lilac: #C4A8FF;
  --os-peach: #FFBE98;
  --os-blue: #6B8CFF;
  --os-coral: #FF6B6B;

  /* Active batch colour (set by JS) */
  --batch: var(--os-aqua);
  --batch-subtle: color-mix(in srgb, var(--batch) 15%, transparent);
  --batch-hover: color-mix(in srgb, var(--batch) 25%, transparent);

  /* Backgrounds */
  --bg-primary: #F5F5F5;
  --bg-card: #FFFFFF;
  --bg-sidebar: #0A0A0A;
  --bg-sidebar-hover: #1A1A1A;

  /* Text */
  --text-primary: #0A0A0A;
  --text-body: #4A4E5A;
  --text-secondary: #888888;
  --text-muted: #9CA3AF;
  --text-on-sidebar: #FFFFFF;
  --text-on-sidebar-muted: #888888;

  /* Borders */
  --border-default: #E5E7EB;
  --border-strong: #D1D5DB;
  --border-weight: 2px;

  /* Semantic */
  --color-success: #34D399;
  --color-danger: #EF4444;
  --color-warning: #FBBF24;
  --color-info: #94A3B8;

  /* Typography */
  --font-display: 'Archivo Black', 'Arial Black', Impact, sans-serif;
  --font-mono: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
  --font-body: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;

  /* Spacing */
  --spacing-xs: 4px;
  --spacing-sm: 8px;
  --spacing-md: 16px;
  --spacing-lg: 24px;
  --spacing-xl: 32px;

  /* Sizing */
  --button-height: 40px;
  --button-height-lg: 48px;
  --icon-button-size: 40px;
  --header-height: 60px;
  --sidebar-width: 260px;
  --sidebar-collapsed-width: 72px;

  /* Borders */
  --border-radius-sm: 6px;
  --border-radius-md: 8px;
  --border-radius-lg: 12px;
  --border-radius-xl: 20px;

  /* Effects — flat only, no glows */
  --shadow-card: 0 1px 3px rgba(0, 0, 0, 0.06);
  --shadow-elevated: 0 4px 12px rgba(0, 0, 0, 0.08);
  --transition-fast: 150ms ease-out;
  --transition-medium: 300ms ease-out;

  /* POC token aliases (used by dashboard + shared components) */
  --block-bg: #0A0A0A;
  --block-text: #FFFFFF;
  --block-muted: #555555;
  --block-tag: #888888;
  --block-surface: rgba(255,255,255,0.06);
  --content-bg: #FFFFFF;
  --content-text: #1A1D26;
  --content-secondary: #4A4E5A;
  --content-muted: #6B7085;
  --content-border: #E5E7EB;
  --content-surface: #F7F7F8;
  --table-stripe: #FAFBFC;
  --success: #34D399;
  --danger: #EF4444;
  --warning: #FBBF24;
  --info: #94A3B8;
}

/* Base Styles */
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  font-family: var(--font-body);
  background: var(--bg-primary);
  color: var(--text-body);
  min-height: 100vh;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

/* Monospace class for data elements */
.mono {
  font-family: var(--font-mono);
}

/* App Shell Layout */
.app-shell {
  display: flex;
  min-height: 100vh;
}

.app-content {
  flex: 1;
  display: flex;
  flex-direction: column;
  margin-left: var(--sidebar-width);
  transition: margin-left var(--transition-medium);
  min-width: 0;
  /* `clip` (not `hidden`) so this does NOT become a scroll container: `hidden`
     makes the computed overflow-y `auto`, which hijacks `position: sticky` for
     descendants (e.g. the chaptered TOC) and traps their internal scroll. `clip`
     still suppresses horizontal bleed but keeps the viewport as the scroll root. */
  overflow-x: clip;
}

.app-main {
  flex: 1;
  /* FIXES-1 — the padding is a token so the full-bleed components (which escape
     it with a negative margin) follow it when it changes below 768px. Every
     `calc(-1 * var(--app-main-padding, var(--spacing-xl)))` in application.css and
     brand.css reads this; the fallback covers a component rendered outside .app-main. */
  --app-main-padding: var(--spacing-xl);
  padding: var(--app-main-padding);
}

/* FIXES-1 — below 768px the sidebar is an off-canvas drawer (sidebar.css), so the
   content column drops its 260px reservation and the main padding halves. Kept here,
   beside the rules it overrides, because the :app bundle is concatenated and a media
   rule in another file wins or loses on bundle order. */
@media (max-width: 768px) {
  .app-content {
    margin-left: 0;
  }
  .app-main {
    --app-main-padding: var(--spacing-md);
  }
}

/* Buttons — `.btn-primary` / `.btn-secondary` evicted in D7.45 (CQ-23
   §6.4 task 3 — closes Phase 3). All authenticated-app call-sites now
   use `brand-btn brand-btn--{primary,secondary}` from brand.css. The
   sole survivor (`survey_responses/show.html.erb` on the public survey
   layout, which does not load brand.css) carries an inline `<style>`
   block that fully overrides any base `.btn-primary` cascade — safe. */

/* Cards */
.card {
  background: var(--bg-card);
  border: 1px solid var(--border-default);
  border-radius: var(--border-radius-lg);
  padding: var(--spacing-lg);
}

/* Form Inputs */
input, textarea, select {
  width: 100%;
  height: var(--button-height);
  padding: 0 var(--spacing-md);
  background: var(--bg-card);
  color: var(--text-primary);
  border: 1px solid var(--border-default);
  border-radius: var(--border-radius-sm);
  font-family: var(--font-body);
  transition: all var(--transition-fast);
}

input:hover, textarea:hover, select:hover {
  border-color: var(--border-strong);
}

input:focus, textarea:focus, select:focus {
  outline: none;
  border-color: var(--batch);
}

input::placeholder, textarea::placeholder {
  color: var(--text-muted);
}

/* Full-bleed: break out of .app-main padding */
.body-content {
  margin-left: calc(-1 * var(--app-main-padding, var(--spacing-xl)));
  margin-right: calc(-1 * var(--app-main-padding, var(--spacing-xl)));
}

/* ══════════════════════════════════════
   BODY CONTENT
   ══════════════════════════════════════ */
.body-content {
  padding: 36px 48px 60px;
  background: var(--content-surface);
}

.section-header {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 20px;
  padding-top: 4px;
}

.section-header-accent {
  width: 4px;
  height: 22px;
  background: var(--batch);
  border-radius: 2px;
  flex-shrink: 0;
  transition: background-color 0.5s ease;
}

.section-header-text {
  font-family: var(--font-display);
  font-size: 14px;
  color: var(--content-text);
  letter-spacing: 0.01em;
}

.section-description {
  font-size: 14px;
  color: var(--content-secondary);
  margin-bottom: 20px;
  line-height: 1.6;
}

.section-label {
  font-family: var(--font-mono);
  font-size: 9px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--content-muted);
}

.divider {
  height: 1px;
  background: var(--content-border);
  margin: 36px 0;
}

/* ══════════════════════════════════════
   TABLES
   ══════════════════════════════════════ */
.table-wrap {
  border: 1px solid var(--content-border);
  overflow-x: auto;
  background: var(--content-bg);
}

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

.data-table thead tr {
  background: var(--block-bg);
}

.data-table thead th {
  padding: 12px 16px;
  text-align: left;
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--block-tag);
  white-space: nowrap;
}

.data-table tbody tr {
  border-bottom: 1px solid var(--content-border);
  border-left: 3px solid transparent;
  transition: background-color 0.15s, border-color 0.15s;
}

.data-table tbody tr:last-child { border-bottom: none; }
.data-table tbody tr:nth-child(even) { background: var(--table-stripe); }
.data-table tbody tr:hover {
  background: color-mix(in srgb, var(--batch) 6%, transparent);
  border-left-color: var(--batch);
}

.data-table tbody td {
  padding: 14px 16px;
  font-size: 14px;
  color: var(--content-secondary);
  vertical-align: middle;
}

.data-table .col-name {
  font-weight: 600;
  color: var(--content-text);
}

/* ══════════════════════════════════════
   BADGES
   ══════════════════════════════════════ */
.badge {
  display: inline-block;
  padding: 4px 10px;
  font-family: var(--font-body);
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  white-space: nowrap;
}

.badge-success { background: #E8F5E9; color: #2E7D32; }
.badge-batch { background: var(--batch); color: #0A0A0A; transition: background-color 0.5s ease; }

/* ══════════════════════════════════════
   DARK BLOCKS & ACTION CARDS
   ══════════════════════════════════════ */
.dark-block {
  background: var(--block-bg);
  padding: 24px 28px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 32px;
  gap: 16px;
}

.block-title {
  font-family: var(--font-display);
  font-size: 14px;
  color: var(--batch);
  margin-bottom: 4px;
  transition: color 0.5s ease;
}

.block-body {
  font-family: var(--font-body);
  font-size: 13px;
  color: rgba(255,255,255,0.55);
}

.action-cards {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 16px;
}

.action-card {
  background: var(--block-bg);
  padding: 32px 28px;
  cursor: pointer;
  transition: all 0.2s ease;
  border: 1px solid transparent;
  text-decoration: none;
  display: block;
}

.action-card:hover {
  border-color: var(--batch);
  transform: translateY(-2px);
  box-shadow: 0 4px 16px rgba(0,0,0,0.12);
}

.action-card-icon {
  font-size: 28px;
  margin-bottom: 16px;
  display: block;
  color: var(--batch);
  transition: color 0.5s ease;
}

.action-card-title {
  font-family: var(--font-display);
  font-size: 15px;
  color: #FFFFFF;
  text-transform: uppercase;
  letter-spacing: 0.01em;
  margin-bottom: 8px;
}

.action-card-desc {
  font-family: var(--font-body);
  font-size: 13px;
  color: rgba(255,255,255,0.5);
  line-height: 1.5;
}

/* ══════════════════════════════════════
   RESPONSIVE
   ══════════════════════════════════════ */
@media (max-width: 900px) {
  .kpi-grid { grid-template-columns: repeat(2, 1fr); }
  .action-cards { grid-template-columns: 1fr; }
}

@media (max-width: 768px) {
  .body-content { padding: 24px; }
  .kpi-grid { grid-template-columns: 1fr; }
  .dark-block { flex-direction: column; align-items: flex-start; }
}

/* ══════════════════════════════════════
   CONVERSATION LAYOUT
   ══════════════════════════════════════ */
.conversation-layout {
  display: flex;
  flex-direction: column;
  /* viewport - app-header - app-main padding (top+bottom) - page hero
     box (~210px hero + 32px margin-bottom). Hero now lives in
     content_for(:hero) above the layout (D8 alignment with the
     dm-layout pattern), so the math mirrors .dm-layout. */
  height: calc(100vh - var(--header-height) - var(--spacing-xl) * 2 - 210px);
  min-height: 500px;
  background: var(--content-bg, var(--bg-card));
  border: 1px solid var(--content-border, var(--border-default));
  /* Full-bleed to the content edges, aligning with the full-bleed
     brand-compact-hero above (which breaks out of app-main padding). */
  margin-left: calc(-1 * var(--app-main-padding, var(--spacing-xl)));
  margin-right: calc(-1 * var(--app-main-padding, var(--spacing-xl)));
  overflow: hidden;
}

/* Embedded variant — for thread chrome that lives inside an already-sized
   flex parent (e.g. .dm-conversation-pane inside .dm-layout). Drops the
   standalone box (viewport-height, side margins, border) so the parent
   pane provides the bounds. Locked as part of CQ-23 §10.3 #1 (thread
   chrome design candidate). */
.conversation-layout--embedded {
  height: 100%;
  min-height: 0;
  margin-left: 0;
  margin-right: 0;
  border: none;
  background: transparent;
}

/* <turbo-frame> is inline by default — without this, the embedded
   .conversation-layout's height:100% has nothing to resolve against
   and the composer floats to natural-content height. */
.dm-conversation-pane > turbo-frame {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
}

/* Thread chrome — D8 messaging unification. The bespoke dark
   .conversation-header was replaced by a page-level brand-compact-hero
   rendered via content_for(:hero), identical to every other rebranded
   page. The message stream / composer live in .conversation-layout
   below it. */

/* DM identity bar — per-thread chrome inside dm-conversation-pane.
   Lighter than the page hero (which sits above the whole dm-layout);
   mono eyebrow + display-font name. */
.dm-identity-bar {
  padding: 14px 24px;
  background: var(--content-bg, var(--bg-card));
  border-bottom: 1px solid var(--content-border, var(--border-default));
  flex-shrink: 0;
}

.dm-identity-bar-eyebrow {
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--content-muted, var(--text-muted));
  margin-bottom: 4px;
}

.dm-identity-bar-title {
  font-family: var(--font-display);
  font-size: 18px;
  color: var(--content-text, var(--text-primary));
  letter-spacing: -0.01em;
  line-height: 1.1;
}

/* ══════════════════════════════════════
   PINNED ANNOUNCEMENTS
   ══════════════════════════════════════
   D7.5: .pinned-banner/.pinned-header/.pinned-icon/.pinned-body/
   .pinned-footer/.pinned-meta/.pinned-unpin removed — broadcasts/
   _broadcast_banner now renders a brand-kit `_status_card`.
   .pinned-area kept for conversations/show wrapper border.        */
.pinned-area {
  flex-shrink: 0;
  border-bottom: 1px solid var(--content-border, var(--border-default));
}

/* ══════════════════════════════════════
   MESSAGE STREAM
   ══════════════════════════════════════ */
.message-stream {
  flex: 1;
  overflow-y: auto;
  padding: 0 24px;
  display: flex;
  flex-direction: column;
  background: var(--content-bg, var(--bg-card));
}

.date-separator {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 16px 0 8px;
}

.date-separator-line {
  flex: 1;
  height: 1px;
  background: var(--content-border, var(--border-default));
}

.date-separator-label {
  font-family: var(--font-mono);
  font-size: 9px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--content-muted, var(--text-muted));
  flex-shrink: 0;
}

.message-group {
  display: flex;
  gap: 12px;
  padding: 10px 12px;
  border-left: 3px solid transparent;
  transition: background-color 0.12s ease, border-color 0.12s ease;
  /* #21 — positioning context for the hover-revealed admin delete control. */
  position: relative;
}

.message-group:hover {
  background: var(--content-surface, var(--bg-primary));
  border-left-color: var(--batch);
}

.message-group + .message-group {
  border-top: 1px solid var(--content-border, var(--border-default));
}

.message-group.consecutive {
  padding-top: 2px;
  padding-left: 63px;
  border-top: none;
}

.message-group.consecutive:hover {
  border-left-color: var(--batch);
}

.avatar-circle {
  width: 38px;
  height: 38px;
  border-radius: 50%;
  background: var(--batch);
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-display);
  font-size: 12px;
  color: #0A0A0A;
  flex-shrink: 0;
  transition: background-color 0.5s ease;
}

.avatar-circle.sm {
  width: 34px;
  height: 34px;
  font-size: 11px;
}

/* #35 — per-speaker colour ("Bright Spectrum" palette, operator pick).
   speaker_colour_class hashes author_id -> one of 8 slots and stamps a
   .speaker-N class on the .message-group of human-author kinds
   (_text/_file_link/_kb_card). Avatar fill + author name then read the
   slot's custom props, giving each person a stable differentiator.
   Deliberately distinct from the 5 batch/system colours so the speaker
   palette and the batch theme never get confused. System kinds
   (hold/complete/kb_announce) carry no .speaker-* class and keep their
   semantic avatar colours. Name tints are the darker, saturated variants
   for contrast on the white (.conversation-layout) thread. */
.message-group.speaker-0 { --speaker-bg: #EF476F; --speaker-fg: #0A0A0A; --speaker-name: #C42852; }
.message-group.speaker-1 { --speaker-bg: #06D6A0; --speaker-fg: #0A0A0A; --speaker-name: #067D5E; }
.message-group.speaker-2 { --speaker-bg: #FFD166; --speaker-fg: #0A0A0A; --speaker-name: #9A7B1E; }
.message-group.speaker-3 { --speaker-bg: #118AB2; --speaker-fg: #0A0A0A; --speaker-name: #0C6486; }
.message-group.speaker-4 { --speaker-bg: #F78C6B; --speaker-fg: #0A0A0A; --speaker-name: #C25638; }
.message-group.speaker-5 { --speaker-bg: #9B5DE5; --speaker-fg: #0A0A0A; --speaker-name: #6E37AE; }
.message-group.speaker-6 { --speaker-bg: #83E377; --speaker-fg: #0A0A0A; --speaker-name: #3E8A33; }
.message-group.speaker-7 { --speaker-bg: #4CC9F0; --speaker-fg: #0A0A0A; --speaker-name: #1A7C9C; }

.message-group[class*="speaker-"] .avatar-circle {
  background: var(--speaker-bg);
  color: var(--speaker-fg);
}

.message-group[class*="speaker-"] .message-author {
  color: var(--speaker-name);
}

/* COMPOSE-2 — the shared hover slot. Every per-message control (Admin Delete
   today; Edit in Q5, Quote in Q6) lives inside it, so the reveal is written
   once instead of once per control.

   :focus-within on the SLOT, not :focus-visible on each control, so a keyboard
   user tabbing into the slot reveals all of it rather than just the one button
   they landed on.

   top/right reproduce the #21 delete control's own former offsets EXACTLY
   (8px / 10px), not the 6px / 8px sketched in the slice doc: the control is
   the slot's only child, so any other pair would move it, and COMPOSE-2 claims
   zero visible change. button_to wraps its button in a <form>, which is the
   flex item here, so reset that form's box. */
.message-actions {
  position: absolute;
  top: 8px;
  right: 10px;
  display: flex;
  gap: 4px;
  opacity: 0;
  transition: opacity 0.12s ease;
}

.message-group:hover .message-actions,
.message-actions:focus-within {
  opacity: 1;
}

.message-actions form {
  margin: 0;
}

/* #21 — admin delete control. Box styling only: the slot above owns where it
   sits and when it appears. */
.message-delete-control {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  padding: 0;
  border: 1px solid var(--content-border, #E5E7EB);
  border-radius: 50%;
  background: var(--content-bg, #FFFFFF);
  color: var(--content-muted, #6B7085);
  cursor: pointer;
  transition: color 0.12s ease, border-color 0.12s ease;
}

.message-delete-control:hover {
  color: var(--danger, #EF4444);
  border-color: var(--danger, #EF4444);
}

/* COMPOSE-5 — the Edit control. Same 26px circle as the delete control beside
   it, so the slot reads as one row of affordances rather than a set of
   unrelated buttons. Box styling only: `.message-actions` owns where these sit
   and when they appear, and re-adding position/opacity here would fight it. */
.message-edit-control {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  padding: 0;
  border: 1px solid var(--content-border, #E5E7EB);
  border-radius: 50%;
  background: var(--content-bg, #FFFFFF);
  color: var(--content-muted, #6B7085);
  cursor: pointer;
  transition: color 0.12s ease, border-color 0.12s ease;
}

.message-edit-control:hover {
  color: var(--content-text, var(--text-primary));
  border-color: var(--content-text, var(--text-primary));
}

/* The admin-only History control. A word, not a glyph: there is no icon that
   reads as "previous versions of this text" without a caption, and this is an
   admin surface where an unambiguous label beats a compact one. */
.message-history-control {
  display: inline-flex;
  align-items: center;
  height: 26px;
  padding: 0 8px;
  border: 1px solid var(--content-border, #E5E7EB);
  border-radius: 13px;
  background: var(--content-bg, #FFFFFF);
  color: var(--content-muted, #6B7085);
  font-family: var(--font-mono);
  font-size: 9px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  text-decoration: none;
  cursor: pointer;
  transition: color 0.12s ease, border-color 0.12s ease;
}

.message-history-control:hover {
  color: var(--content-text, var(--text-primary));
  border-color: var(--content-text, var(--text-primary));
}

/* COMPOSE-5 — the inline edit form, in place of the message body. It sits
   INSIDE .message-content (A.18), so it inherits the content column's width and
   needs no box of its own beyond a little breathing room. */
.message-edit {
  margin-top: 4px;
}

.message-edit__errors {
  font-family: var(--font-body);
  font-size: 12px;
  color: var(--danger, #EF4444);
  margin-bottom: 6px;
}

.message-edit__errors p {
  margin: 0 0 2px;
}

.message-edit__actions {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 6px;
}

.message-edit__save {
  font-family: var(--font-mono);
  font-size: 9px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  padding: 6px 14px;
  border: none;
  border-radius: 2px;
  background: var(--content-text, var(--text-primary));
  color: var(--content-bg, #FFFFFF);
  cursor: pointer;
}

.message-edit__cancel {
  font-family: var(--font-mono);
  font-size: 9px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--content-muted, var(--text-muted));
  text-decoration: none;
  cursor: pointer;
}

.message-edit__cancel:hover {
  color: var(--content-text, var(--text-primary));
}

/* COMPOSE-5 (D8) — the admin revision history, under the message it belongs to.
   Indented and muted so it reads as an annotation on that message rather than
   as more thread. */
.message-revisions {
  margin-top: 8px;
  padding: 8px 10px;
  border-left: 2px solid var(--content-border, #E5E7EB);
  background: var(--content-surface, #F7F7F8);
}

.message-revisions__head {
  margin-bottom: 6px;
}

.message-revisions__title {
  font-family: var(--font-mono);
  font-size: 9px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--content-muted, var(--text-muted));
}

.message-revisions__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.message-revisions__meta {
  display: flex;
  align-items: baseline;
  gap: 8px;
  font-family: var(--font-mono);
  font-size: 9px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--content-muted, var(--text-muted));
}

.message-revisions__body,
.message-revisions__empty {
  font-family: var(--font-body);
  font-size: 12.5px;
  color: var(--content-secondary, var(--text-body));
  line-height: 1.55;
}

.message-revisions__body p { margin: 0; }
.message-revisions__body p + p { margin-top: 6px; }

.message-revisions__empty-body {
  font-style: italic;
  color: var(--content-muted, var(--text-muted));
}

/* #21 — soft-delete tombstone. Muted, italic, no per-speaker colour. */
.message-group--tombstone .tombstone-avatar {
  background: var(--content-surface, #F7F7F8);
  color: var(--content-muted, #6B7085);
}

.message-group--tombstone .tombstone-text {
  color: var(--content-muted, #6B7085);
  font-style: italic;
  font-weight: 500;
}

.message-content {
  flex: 1;
  min-width: 0;
}

.message-meta {
  display: flex;
  align-items: baseline;
  gap: 8px;
  margin-bottom: 2px;
}

.message-author {
  font-family: var(--font-body);
  font-size: 13px;
  font-weight: 600;
  color: var(--content-text, var(--text-primary));
}

.message-timestamp {
  font-family: var(--font-mono);
  font-size: 9px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--content-muted, var(--text-muted));
}

/* COMPOSE-5 (D7) — the "(edited)" marker, beside the timestamp. Deliberately
   NOT uppercase and NOT letter-spaced like .message-timestamp: it is a note
   about the message, not another piece of the byline, and the lower-case
   parenthetical is what keeps it from reading as a second timestamp. The time
   itself is on the `title` and the `aria-label`, never on screen — the whole
   point of the marker is that it is small enough to ignore.

   `margin-left` rather than relying on .message-meta's 8px gap: this sits
   tight against the timestamp it qualifies, closer than the gap between
   independent items in that row. */
.message-edited {
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--content-muted, var(--text-muted));
  margin-left: 4px;
}

/* ANYWHERE-4 — "via link" provenance badge, beside the timestamp in the meta
   row. Same Space Mono metadata voice as .message-timestamp so it reads as part
   of the byline rather than as content, with the batch accent to mark it as the
   one thing in that row that is not routine. Inline, no fixed height, so it
   cannot shift the row. */
.message-meta__via {
  font-family: var(--font-mono);
  font-size: 9px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--content-text, var(--text-primary));
  background: var(--batch-subtle);
  padding: 1px 5px;
  white-space: nowrap;
}

.message-body {
  font-family: var(--font-body);
  font-size: 13.5px;
  color: var(--content-secondary, var(--text-body));
  line-height: 1.6;
}

.message-body p { margin-bottom: 0; }
.message-body p + p { margin-top: 8px; }

/* COMPOSE-3 — a RICH message body. `LinkableContent#rendered_formatted_body_html`
   wraps its output in `.trix-content`, the same wrapper Action Text emits for
   the knowledge base and the guide, so all of `actiontext.css` applies: the
   accent border on a blockquote, accent list markers, the `<u>` rule that has
   finally got a tag to match, the `<pre>` box, the image cap.

   These four rules are the delta between "an article" and "a line in a chat
   thread". 🔴 `.trix-content` sets its own `font`, `line-height` and
   `color: var(--text-body)`, and `.trix-content * { margin: 0; padding: 0 }`
   zeroes EVERY block gap — without the first two rules a rich message renders
   in the article's colour with every paragraph glued to the next. `inherit`
   rather than a repeat of `.message-body`'s values so the two can never drift,
   and so a message on a `--content-*` surface keeps that surface's colour. */
.message-body > .trix-content {
  font-family: inherit;
  font-size: inherit;
  line-height: inherit;
  color: inherit;
}

/* Block rhythm, matching `.message-body p + p` exactly: div, ul, ol,
   blockquote, pre and h1 are all top-level Trix blocks and all need the gap. */
.message-body > .trix-content > * + * { margin-top: 8px; }

/* The quote keeps its accent bar and takes the bubble's colour. */
.message-body > .trix-content blockquote { color: inherit; }

/* `.trix-content h1` carries `margin-bottom: 0.5em` for article typography;
   in a thread the sibling rule above already owns the spacing. */
.message-body > .trix-content h1 { margin-bottom: 0; }

.mention-highlight {
  background: var(--batch-subtle);
  color: var(--content-text, var(--text-primary));
  font-weight: 600;
  padding: 1px 6px;
  font-size: 13px;
  transition: background-color 0.5s ease;
}

/* File attachment cards.

   DMFIX-1 restores the approved messaging POC
   (public/messaging-design-poc.html:416-501, :1196-1203). The 2 Apr 2026 ERB
   translation shrank every value ~10% and, fatally, wrote <span> for the name
   and the size where the POC had <div>. That left .attachment-info an inline
   formatting context, so the overflow/text-overflow below never applied and a
   long filename painted straight through the download link. The fix is the
   flex column here plus the <div>s in the partial; .preview-info at ~:2560 is
   the same shape done correctly, in this same file.

   D2 — the card takes .file-card's visual language (8px radius, 1px border)
   because it is the best-looking surface in the app. That is a KNOWN,
   DELIBERATE divergence from .rebrand/POC-DESIGN-SYSTEM.md principles #1 and
   #6 and from every other attachment surface, all of which are square.
   DMFIX-3 records it. Do not resolve it here and do not change any other
   component to match. */
.attachment-file {
  /* Badge tints, deliberately scoped to this block rather than :root — they
     restate no existing token, and the blast radius should stay on this card.
     Values are #31's .file-card badge map so the staged upload card and the
     sent card agree. */
  --file-badge-pdf: #DC2626;
  --file-badge-sheet: #16A34A;
  --file-badge-doc: #2563EB;
  --file-badge-default: #6B7280;

  display: flex;
  align-items: center;
  /* The card is a flex item of the partial's column wrapper, so inline-flex
     was blockified and align-items: stretch pushed every card to the full
     300px. flex-start lets a short-named file shrink to its content. */
  align-self: flex-start;
  gap: 10px;
  border: 1px solid var(--content-border, var(--border-default));
  border-radius: var(--border-radius-md);
  /* A surface of its own: --content-bg resolves to #FFFFFF inside the thread,
     which is also #FFFFFF, so the card used to be defined by its border alone. */
  background: var(--content-surface, #F7F7F8);
  padding: 10px 14px;
  /* No margin-top: the wrapper already supplies margin-top and gap of 6px,
     and flex-item margins do not collapse, so this was doubling it. */
  /* 🔴 The 100% arm is load-bearing and was found in the browser pass. Because
     align-self above sizes the card to its content, a fit-content card can
     exceed a narrow container, which stretch used to prevent: measured 360px
     inside a 106px frame at a 560px viewport, overflowing by 254px. Clamping
     to the container restores that guarantee without giving up shrink-to-fit. */
  max-width: min(360px, 100%);
  cursor: pointer;
  transition: border-color 0.5s ease;
  text-decoration: none;
}

.attachment-file:hover { border-color: var(--batch); }

/* The sibling .attachment-thumb-btn has had this since the paste feature; the
   card relied on the UA default. */
.attachment-file:focus-visible {
  outline: 2px solid var(--batch, #00F5D4);
  outline-offset: 2px;
}

.attachment-icon {
  width: 40px;
  height: 40px;
  border-radius: var(--border-radius-sm);
  background: var(--batch-subtle);
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 700;
  color: var(--content-text, var(--text-primary));
  letter-spacing: 0.06em;
  flex-shrink: 0;
  transition: background-color 0.5s ease;
}

/* D4 — the five categories from badgeCategory(), replacing the two-state
   PDF/FILE guess and the retired inline red (Flat-UI "alizarin", in no palette
   in this application) that beat the stylesheet on the PDF arm alone: change
   the batch colour and the FILE chip re-tinted while the PDF chip did not. */
.attachment-icon--pdf {
  color: var(--file-badge-pdf);
  background: color-mix(in srgb, var(--file-badge-pdf) 12%, transparent);
}

.attachment-icon--sheet {
  color: var(--file-badge-sheet);
  background: color-mix(in srgb, var(--file-badge-sheet) 12%, transparent);
}

.attachment-icon--doc {
  color: var(--file-badge-doc);
  background: color-mix(in srgb, var(--file-badge-doc) 12%, transparent);
}

.attachment-icon--image {
  color: var(--content-muted, var(--text-muted));
  background: var(--content-border, var(--border-default));
}

.attachment-icon--default {
  color: var(--file-badge-default);
  background: color-mix(in srgb, var(--file-badge-default) 12%, transparent);
}

/* 🔴 The whole defect. min-width: 0 alone left this a block box with two
   inline children; as a column flex container its children are blockified and
   .attachment-name's overflow rules come alive. */
.attachment-info {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.attachment-name {
  font-family: var(--font-body);
  font-size: 13px;
  font-weight: 500;
  color: var(--content-text, var(--text-primary));
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.attachment-size {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--content-muted, var(--text-muted));
  letter-spacing: 0.06em;
  /* No text-transform: a size already reads "2.08 MB". */
}

.attachment-download {
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.08em;
  /* --batch on white is 1.40:1 at this weight, well under WCAG AA. Muted is
     ~4.9:1, and the hover below gives back the emphasis. */
  color: var(--content-muted, var(--text-muted));
  margin-left: auto;
  flex-shrink: 0;
  transition: color 0.5s ease;
  text-decoration: none;
}

.attachment-file:hover .attachment-download { color: var(--content-text, var(--text-primary)); }

.attachment-image {
  margin-top: 6px;
  display: block;
}

.attachment-image img {
  max-width: 280px;
  max-height: 180px;
  display: block;
  border: 1px solid var(--content-border, var(--border-default));
  object-fit: cover;
  /* A failed/unauthorized fetch (cross-visibility viewer hitting the
     authenticated inline endpoint) collapses to a quiet alt-text box
     instead of a broken-image glyph. */
  background: var(--content-surface, #F7F7F8);
  color: var(--content-muted, #6B7085);
  font-size: 12px;
}

/* DMFIX-2 — the image caption the approved POC designed and that never
   shipped. Mono, muted and margin-top per the POC, but at DMFIX-1's type
   scale: DMFIX-1 moved .attachment-size to 11px, so the POC's literal 9px
   would read as a third type size in one card. Match the card, not the
   ten-month-old literal.

   🔴 These are NEW sibling selectors on purpose. .attachment-image,
   .attachment-image img and .attachment-thumb-btn* are SHARED with the
   job-thread Document arm (refs/_file_link.html.erb) and are built in
   JavaScript by scan_status_controller.js. Adding a sibling is safe; editing
   those is not, and only the legacy DM arm renders this footer. */
.attachment-image-footer {
  display: flex;
  align-items: baseline;
  gap: 6px;
  /* Held to the thumbnail's own max-width so a long filename cannot run past
     the image the way it used to run past the download link on the file
     card. This is the defect DMFIX-1 just fixed; do not reintroduce it. */
  max-width: 280px;
  margin-top: 4px;
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.06em;
  color: var(--content-muted, var(--text-muted));
}

/* min-width: 0 is what lets a flex item shrink below its content width, and
   without it the ellipsis is dead — the same mechanism that broke the file
   card, one level down. The untruncated name is on title=. */
.attachment-image-name {
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.attachment-image-sep {
  flex-shrink: 0;
}

/* Never shrinks: the download affordance is the whole point of the footer,
   so the filename gives way before it does. */
.attachment-image-download {
  flex-shrink: 0;
  font-weight: 700;
  color: var(--content-muted, var(--text-muted));
  text-decoration: none;
  transition: color 0.5s ease;
}

.attachment-image-download:hover { color: var(--content-text, var(--text-primary)); }

.attachment-image-download:focus-visible {
  outline: 2px solid var(--batch, #00F5D4);
  outline-offset: 2px;
}

/* Load older messages */
.load-older {
  display: flex;
  justify-content: center;
  padding: 14px 0 6px;
}

.load-older-btn {
  font-family: var(--font-mono);
  font-size: 9px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--content-muted, var(--text-muted));
  background: var(--content-surface, var(--bg-primary));
  border: 1px solid var(--content-border, var(--border-default));
  padding: 6px 18px;
  cursor: pointer;
  text-decoration: none;
  transition: border-color 0.5s ease, color 0.15s, background 0.15s;
}

.load-older-btn:hover { border-color: var(--batch); color: var(--content-text, var(--text-primary)); background: var(--batch-subtle); }

/* Typing indicator */
.typing-indicator {
  padding: 4px 24px;
  font-family: var(--font-body);
  font-size: 12px;
  font-style: italic;
  color: var(--content-muted, var(--text-muted));
  min-height: 24px;
  flex-shrink: 0;
  background: var(--content-bg, var(--bg-card));
}

/* New messages indicator */
.new-messages-indicator {
  display: flex;
  justify-content: center;
  padding: 6px 0;
  flex-shrink: 0;
  background: var(--content-bg, var(--bg-card));
}

.new-messages-pill {
  font-family: var(--font-mono);
  font-size: 9px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: #0A0A0A;
  background: var(--batch);
  border: none;
  padding: 4px 14px;
  cursor: pointer;
  transition: background-color 0.5s ease;
}

.new-messages-pill:hover { opacity: 0.85; }

/* ══════════════════════════════════════
   MESSAGE COMPOSER
   ══════════════════════════════════════ */
.message-composer {
  display: flex;
  align-items: flex-end;
  gap: 8px;
  padding: 10px 24px 12px;
  background: var(--block-bg);
  flex-shrink: 0;
  /* COMPOSE-1 / D12 — the composer cap, as a token so a page that scrolls can
     opt out in one line. `lh` inside an unregistered custom property resolves
     at the USE site (.message-textarea, 20.25px), not here (24px): 10 lines +
     18px padding + 2px border = 222.5px, which is 49% of the 498px DM panel.
     Caps are in `lh`, never `px`, so they scale with text size (WCAG 1.4.4). */
  --composer-max-height: calc(10lh + 18px + 2px);
}

/* 📌 FIXES-5 — the magic-link reply page's composer
   (`.reply-link-composer textarea`, brand.css) is still a TEXTAREA and still
   grows with `field-sizing: content`, with its OWN selector and its own cap,
   deliberately sharing no rule with this one: the two composers sit on
   different pages and a later edit to either should not silently move the
   other. COMPOSE-4 turned THIS one into a `<trix-editor>` and left that one
   alone, which is the same call COMPOSE-1 made. */

/* 🔴 COMPOSE-4 — the composer is a `<trix-editor>`, and this rule carries
   COMPOSE-1's sizing tokens onto it unchanged: the same resting height
   (1 line + 18px padding + 2px border = 40.25px, today's measured height to
   the pixel), the same `--composer-max-height` token, the same `lh` units so
   the cap scales with text size (WCAG 1.4.4 / F80), the same `overflow-y:
   auto` so a capped box scrolls inside itself.

   🔬 The selector is `trix-editor.message-editor` (specificity 0,1,1) and that
   beats `actiontext.css`'s `trix-editor { min-height: 10em }` (0,0,1) on
   SPECIFICITY, not on sheet order (A.23) — which matters because the `:app`
   bundle is alphabetical and `actiontext.css` is emitted FIRST. Weaken this
   selector to `.message-editor` and the composer silently becomes ten lines
   tall on every surface.

   Most of what COMPOSE-1 needed to make a textarea grow is GONE, not disabled:
   the `.grow-wrap` grid mirror, its `::after` replicated value, the
   `@supports (field-sizing: content)` block and `message_composer_controller#grow`.
   A `<trix-editor>` is a block element; it grows to its content with no mirror,
   no cap-on-both-halves and no JS, on every browser.

   🔴 The ONE piece of Q1's apparatus that had to survive is its placeholder
   cap, immediately below. It is a different selector for the same reason. */
trix-editor.message-editor {
  flex: 1;
  min-width: 0;
  font-family: var(--font-body);
  font-size: 13.5px;
  color: #1A1A1A;
  background: #FFFFFF;
  border: 1px solid rgba(255,255,255,0.12);
  border-radius: 0;
  padding: 9px 14px;
  outline: none;
  line-height: 1.5;
  min-height: calc(1lh + 18px + 2px);
  max-height: var(--composer-max-height);
  overflow-y: auto;
  transition: border-color 0.5s ease;
}

/* The field also carries `trix-content` (see `messages/_editor_field`), so the
   in-editor blockquote bar, list markers, `<pre>` box, `<u>` rule and link
   colour all come from `actiontext.css` for free — what you type looks like
   what you send. Two deltas are needed, both the same ones COMPOSE-3 needed on
   the render side:

   1. `.trix-content` sets `color: var(--text-body)`; this box is white with
      its own `#1A1A1A`, and the rule above wins on specificity (0,1,1 vs
      0,1,0). Stated here so nobody "simplifies" it away.
   2. `.trix-content * { margin: 0 }` zeroes EVERY block gap, so without the
      rule below a two-paragraph draft is glued together while you write it and
      then opens up the moment you send it. Matches
      `.message-body > .trix-content > * + *` exactly. */
/* 🔴 COMPOSE-4 — Q1's `:placeholder-shown` cap, ported to Trix. This is NOT
   redundant with `min-height`, and it was measured after being wrongly deleted:
   Trix paints its placeholder as `%t:empty::before { content: attr(placeholder) }`
   (trix.js:13398), which is REAL rendered content and therefore WRAPS. 🔬 At a
   311.75px-wide editor (the full thread in a 500px window) the 55-character
   placeholder ran to two lines and the EMPTY composer stood at 60.5px instead
   of 40.25px — the identical defect A.22 recorded for the textarea, with the
   identical cause and a different selector. At 1440px the placeholder fits on
   one line and the bug is invisible, which is how it nearly shipped.

   `:empty` is Trix's own test, so this rule and the placeholder it caps can
   never disagree. It caps a box that by definition holds no user text; the
   moment a character is typed the element is no longer `:empty` and the cap
   lifts. Fixes the copy instead if you want the second line back. */
trix-editor.message-editor:empty { max-height: calc(1lh + 18px + 2px); }

trix-editor.message-editor > * + * { margin-top: 8px; }

/* 3. The quote. `actiontext.css` already gives it the `--batch` left rule
      (`border-left-width: 0.3em`), so it is visible while you write it — but
      it also greys the text to `--text-secondary`, and COMPOSE-3's render pass
      deliberately puts that back to `inherit` (`.message-body >
      .trix-content blockquote`). Matching it here is what stops a quote
      changing colour at the moment you press SEND.
      📌 Slice-4's doc also asked for a `--content-surface` FILL here. Not
      built: the rendered message has no fill, so adding one in the editor
      alone would reintroduce exactly the compose/send mismatch this rule
      exists to remove. COMPOSE-6 adds the `cite` line. */
trix-editor.message-editor blockquote { color: inherit; }

/* COMPOSE-4 — the wrapper exists only so Trix's toolbar has somewhere to go:
   🔬 `<trix-editor>` inserts its `<trix-toolbar>` as its own PREVIOUS SIBLING
   (trix.js:13672), which without this would make the toolbar a flex item of
   `.composer-input-row`, sitting between the attach button and the text box.
   A flex COLUMN puts it above the editor, where the KB article editor already
   puts it, and keeps the row at three items. */
.composer-editor-wrap {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-width: 0;
}

/* COMPOSE-1 / D12 — where the PAGE scrolls, the bound is on the STREAM and the
   composer is uncapped. 🔴 THIS RULE LIVES HERE ON PURPOSE, NOT IN
   `jobs/show.html.erb`'s body <style>: `knowledge_base/show.html.erb` uses the
   same `.job-conversation-card` class and gets nothing from that <style>. The
   job card's own flex rules stay in that <style>. Half the rules in each file is
   the intended shape — tidying them into one place silently kills the KB page's
   rule. The stream takes the bound because `.message-stream { flex: 1 }` would
   otherwise resolve against 50 messages (MessagePagination::DEFAULT_LIMIT). */
.job-conversation-card .message-stream {
  max-height: 55vh;
  min-height: 240px;
}

.job-conversation-card .message-composer {
  --composer-max-height: none;
}

@media (max-width: 768px) {
  /* On a phone the message list is already off-screen, so unbounded growth
     only makes the page long. */
  .job-conversation-card .message-composer {
    --composer-max-height: calc(10lh + 18px + 2px);
  }
}

trix-editor.message-editor:focus { border-color: var(--batch); box-shadow: none; }

/* `<trix-editor>` has no `::placeholder` — Trix paints the placeholder with a
   `::before` on the empty editor and `actiontext.css` colours it with
   `--text-muted`, which is a DARK-theme token on a white box. Same value the
   textarea used. */
trix-editor.message-editor:empty::before { color: rgba(26, 26, 26, 0.45); }

.composer-btn {
  width: 38px;
  min-width: 38px;
  height: 38px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: 1px solid rgba(255,255,255,0.15);
  border-radius: 0;
  color: rgba(255,255,255,0.45);
  cursor: pointer;
  flex-shrink: 0;
  transition: border-color 0.5s ease, color 0.15s;
}

.composer-btn:hover { border-color: var(--batch); color: var(--block-text, #FFFFFF); }

.composer-send {
  width: auto;
  height: 38px;
  padding: 0 20px;
  background: var(--batch);
  border: none;
  border-radius: 0;
  font-family: var(--font-display);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: #0A0A0A;
  cursor: pointer;
  flex-shrink: 0;
  transition: background-color 0.5s ease;
}

.composer-send:hover { opacity: 0.9; }

/* ── COMPOSE-4 / D3 — the formatting toolbar ──────────────────────────────
   The toolbar is HIDDEN until the toggle is clicked, and both the toggle and
   the toolbar exist only for a wide viewport with a FINE POINTER THAT HOVERS.
   A phone therefore never sees either one, which is the whole of this lane's
   mobile posture for the composer (Appendix B: there is no mobile posture in
   the spec map and this lane does not invent one). Campfire's shape, from
   once-campfire's actiontext.css / composer.css.

   🔴 The open state is a class on `#message_form`, the Stimulus controller's
   own element, NOT on `.message-composer`. `#message_form` is wholly replaced
   on every send (A.1) and `.message-composer` is its parent and survives, so
   this spelling is what makes the toolbar collapse after SEND with no teardown
   code. Do not "tidy" the modifier up to the wrapper.

   🔴 The two media features are not decoration. `min-width` alone would show a
   formatting bar on a tablet in landscape, where every button is a 26px target
   and `trix-active` state has no hover to reveal it; `any-hover`/`pointer`
   alone would show it in a 320px-wide DM pane where the three button groups
   do not fit and `.trix-button-row`'s `overflow-x: auto` turns the composer
   into a horizontally scrolling strip. Both, or neither. */
.message-composer trix-toolbar { display: none; }
.composer-format-toggle { display: none; }

@media (min-width: 900px) and (any-hover: hover) and (pointer: fine) {
  .composer-format-toggle { display: flex; }
  .message-composer .composer--toolbar-open trix-toolbar { display: block; }
}

.composer-format-toggle[aria-pressed="true"] {
  border-color: var(--batch);
  color: var(--block-text, #FFFFFF);
}

/* The toolbar sits on the composer's dark bar while its own buttons are
   styled for a light card (actiontext.css, shared with the KB article editor).
   Two adjustments, and no more than two: pull the 10px group gap in to the 8px
   the composer row uses, and stop `justify-content: space-between` flinging
   the groups to opposite ends of a full-width row now that file-tools is
   hidden and only three remain. */
.message-composer trix-toolbar .trix-button-row {
  justify-content: flex-start;
  gap: 8px;
}

.message-composer trix-toolbar .trix-button-group { margin-bottom: 8px; }
.message-composer trix-toolbar .trix-button-group:not(:first-child) { margin-left: 0; }
.message-composer trix-toolbar .trix-button-group-spacer { display: none; }

/* D5 — no Trix attachments, so no attach affordance to click. The file-tools
   GROUP is the whole of it in a default toolbar; the second selector is belt
   and braces for a Trix upgrade that regroups the buttons.
   🔴 `--history-tools` (undo / redo) STAYS VISIBLE. Clinton asked for it
   explicitly, and Campfire hides it — so a later session copying Campfire's
   stylesheet more faithfully would remove it. Do not. */
.message-composer .trix-button-group--file-tools { display: none !important; }
.message-composer .trix-button--icon-attach { display: none; }

/* ══════════════════════════════════════
   DM SPLIT-PANE
   ══════════════════════════════════════ */
.dm-layout {
  display: flex;
  /* viewport - app-header - app-main padding (top+bottom) - page hero
     box (~210px hero + 32px margin-bottom). The page-level compact-hero
     anchors /messages now; the layout fills the rest of the viewport.
     Full-bleed to the content edges, aligning with the full-bleed
     brand-compact-hero above. */
  height: calc(100vh - var(--header-height) - var(--spacing-xl) * 2 - 210px);
  min-height: 500px;
  margin-left: calc(-1 * var(--app-main-padding, var(--spacing-xl)));
  margin-right: calc(-1 * var(--app-main-padding, var(--spacing-xl)));
  border: 1px solid var(--content-border, var(--border-default));
  background: var(--content-bg, var(--bg-card));
  overflow: hidden;
}

.dm-conversation-pane {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-width: 0;
  border-right: 1px solid var(--content-border, var(--border-default));
}

/* ══════════════════════════════════════
   THREAD LIST SIDEBAR
   ══════════════════════════════════════ */
.thread-list-sidebar {
  width: 320px;
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
}

/* D8 — page hero owns "+ New Message"; the rail just labels the list.
   No dark band; clean mono section label flush with brand typography. */
.thread-list-rail-head {
  padding: 18px 20px 12px;
  flex-shrink: 0;
  border-bottom: 1px solid var(--content-border, var(--border-default));
  background: var(--content-bg, var(--bg-card));
}

.thread-list-rail-label {
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--content-muted, var(--text-muted));
}

.thread-list {
  flex: 1;
  overflow-y: auto;
  background: var(--content-bg, var(--bg-card));
}

.thread-item {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 12px 20px;
  border-bottom: 1px solid var(--content-border, var(--border-default));
  cursor: pointer;
  border-left: 3px solid transparent;
  text-decoration: none;
  color: inherit;
  transition: background-color 0.15s, border-color 0.5s ease;
}

.thread-item:hover { background: var(--content-surface, var(--bg-primary)); }

.thread-item.active {
  background: var(--batch-subtle);
  border-left-color: var(--batch);
}

.thread-item.unread .thread-item-name { font-weight: 700; }

.thread-item-body { flex: 1; min-width: 0; }

.thread-item-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 2px;
}

.thread-item-name {
  font-family: var(--font-body);
  font-size: 13px;
  font-weight: 500;
  color: var(--content-text, var(--text-primary));
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.thread-item-time {
  font-family: var(--font-mono);
  font-size: 9px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--content-muted, var(--text-muted));
  flex-shrink: 0;
  margin-left: 8px;
}

.thread-item-preview {
  font-family: var(--font-body);
  font-size: 12px;
  color: var(--content-muted, var(--text-muted));
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.thread-unread-badge {
  background: var(--batch);
  color: #0A0A0A;
  font-family: var(--font-mono);
  font-size: 9px;
  font-weight: 700;
  padding: 2px 7px;
  flex-shrink: 0;
  align-self: center;
  margin-left: 6px;
  transition: background-color 0.5s ease;
}

/* D7.9 — Flex-fill wrapper that centers the shared _empty_state primitive
   inside the .dm-conversation-pane when no thread is active. Replaces the
   bespoke .dm-empty-state + .dm-empty-icon + .dm-empty-title + .dm-empty-body
   + .dm-empty-btn family (CQ-23 §6.1). */
.dm-empty-state-wrap {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
}

/* D7.9 — Sidebar's "No conversations yet" fallback. Tiny class-based
   replacement for the previous inline-style block; _empty_state would be
   visually heavy in the 320px sidebar column. */
.thread-list-empty {
  padding: 32px 20px;
  text-align: center;
  color: var(--content-muted, var(--text-muted));
}

.thread-list-empty-text {
  font-family: var(--font-body);
  font-size: 13px;
  margin: 0 0 8px;
}

.thread-list-empty-link {
  font-family: var(--font-body);
  font-size: 13px;
  color: var(--batch);
  text-decoration: none;
}

.thread-list-empty-link:hover { text-decoration: underline; }

/* ══════════════════════════════════════
   NEW REQUEST FORM
   ══════════════════════════════════════ */
.request-layout {
  max-width: 680px;
  margin: 0 auto;
  padding: 32px 0 48px;
}

.request-hero {
  background: var(--block-bg);
  padding: 24px 28px;
  margin-bottom: 24px;
  border-left: 4px solid var(--batch);
  transition: border-color 0.5s ease;
}

.request-hero-label {
  font-family: var(--font-mono);
  font-size: 8px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--batch);
  margin-bottom: 6px;
  transition: color 0.5s ease;
}

.request-hero-title {
  font-family: var(--font-display);
  font-size: 24px;
  color: var(--block-text, #FFFFFF);
  letter-spacing: -0.02em;
  text-transform: uppercase;
  margin-bottom: 6px;
}

.request-hero-subtitle {
  font-family: var(--font-body);
  font-size: 13px;
  color: rgba(255,255,255,0.55);
  line-height: 1.5;
}

.request-error-box {
  border: 1px solid var(--danger, var(--color-danger));
  border-left: 4px solid var(--danger, var(--color-danger));
  background: rgba(239,68,68,0.04);
  padding: 12px 16px;
  margin-bottom: 20px;
}

.request-error-title {
  font-family: var(--font-mono);
  font-size: 9px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--danger, var(--color-danger));
  margin-bottom: 4px;
}

.request-error-list {
  list-style: none;
  padding: 0;
}

.request-error-list li {
  font-family: var(--font-body);
  font-size: 13px;
  color: var(--danger, var(--color-danger));
  padding: 1px 0;
}

.request-error-list li::before { content: "\2022  "; }

.request-card {
  background: var(--content-bg, var(--bg-card));
  border: 1px solid var(--content-border, var(--border-default));
  padding: 28px;
}

.request-section-label {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 20px;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--content-border, var(--border-default));
}

.request-section-label::before {
  content: '';
  width: 4px;
  height: 22px;
  background: var(--batch);
  border-radius: 2px;
  flex-shrink: 0;
  transition: background-color 0.5s ease;
}

.request-section-label-text {
  font-family: var(--font-display);
  font-size: 14px;
  color: var(--content-text, var(--text-primary));
  letter-spacing: 0.01em;
}

.form-field { margin-bottom: 22px; }

.form-label {
  display: flex;
  align-items: center;
  gap: 4px;
  font-family: var(--font-body);
  font-size: 13px;
  font-weight: 600;
  color: var(--content-text, var(--text-primary));
  margin-bottom: 6px;
}

.form-required { color: var(--danger, var(--color-danger)); font-size: 12px; }

.form-hint {
  font-family: var(--font-body);
  font-size: 12px;
  color: var(--content-muted, var(--text-muted));
  margin-top: 4px;
}

.form-input,
.form-select,
.form-textarea {
  width: 100%;
  height: auto;
  font-family: var(--font-body);
  font-size: 14px;
  color: var(--content-text, var(--text-primary));
  background: var(--content-bg, var(--bg-card));
  border: 1px solid var(--content-border, var(--border-default));
  border-radius: 0;
  padding: 10px 14px;
  outline: none;
  transition: border-color 0.5s ease;
  appearance: none;
}

.form-input:focus,
.form-select:focus,
.form-textarea:focus { border-color: var(--batch); }

.form-select {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath fill='%236B7085' d='M0 0l6 8 6-8z'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 14px center;
  padding-right: 36px;
  cursor: pointer;
}

.form-textarea { resize: vertical; line-height: 1.6; }

.form-file-zone {
  width: 100%;
  border: 2px dashed var(--content-border, var(--border-default));
  background: var(--content-surface, var(--bg-primary));
  padding: 24px;
  text-align: center;
  cursor: pointer;
  transition: border-color 0.5s ease, background 0.15s;
  position: relative;
}

.form-file-zone:hover { border-color: var(--batch); background: var(--batch-subtle); }

.form-file-zone-icon {
  font-size: 24px;
  color: var(--content-muted, var(--text-muted));
  margin-bottom: 6px;
}

.form-file-zone-text {
  font-family: var(--font-body);
  font-size: 13px;
  color: var(--content-muted, var(--text-muted));
}

.form-file-zone-text strong {
  color: var(--content-text, var(--text-primary));
  font-weight: 600;
}

.form-file-zone input[type="file"] {
  position: absolute;
  inset: 0;
  opacity: 0;
  cursor: pointer;
  width: 100%;
  height: 100%;
}

.request-actions {
  display: flex;
  align-items: center;
  gap: 16px;
  margin-top: 8px;
  padding-top: 24px;
  border-top: 1px solid var(--content-border, var(--border-default));
}

.btn-cancel {
  font-family: var(--font-mono);
  font-size: 9px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--content-muted, var(--text-muted));
  background: transparent;
  border: none;
  cursor: pointer;
  text-decoration: none;
  transition: color 0.15s;
}

.btn-cancel:hover { color: var(--content-text, var(--text-primary)); }

/* ══════════════════════════════════════
   NEW DM FORM
   ══════════════════════════════════════ */
.dm-new-page {
  max-width: 680px;
  margin: 0 auto;
  padding: 32px 0 48px;
}

/* Scrollbar */
::-webkit-scrollbar {
  width: 8px;
  height: 8px;
}

::-webkit-scrollbar-track {
  background: var(--bg-primary);
}

::-webkit-scrollbar-thumb {
  background: var(--border-strong);
  border-radius: 4px;
}

::-webkit-scrollbar-thumb:hover {
  background: var(--text-muted);
}

/* Animations */
@keyframes fadeSlideIn {
  from {
    opacity: 0;
    transform: translateY(20px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

@keyframes expandIn {
  from {
    opacity: 0;
    transform: scale(0.95);
  }
  to {
    opacity: 1;
    transform: scale(1);
  }
}

@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}

/* Utility Classes */
.text-muted {
  color: var(--text-muted);
}

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

.coming-soon {
  opacity: 0.5;
  position: relative;
}

.coming-soon::after {
  content: "Coming Soon";
  font-size: 10px;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.5px;
  margin-left: var(--spacing-sm);
}

/* Flash Messages — branded flat banners (D5.3) */
.brand-flash {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px 20px;
  margin-bottom: var(--spacing-lg);
  background: var(--content-bg);
  border: 1px solid var(--content-border);
  border-left-width: 4px;
  font-family: var(--font-body);
  font-size: 14px;
  line-height: 1.5;
  color: var(--content-text);
}

.brand-flash::before {
  flex-shrink: 0;
  font-family: var(--font-mono);
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  padding: 4px 9px;
}

.brand-flash-notice {
  border-left-color: var(--color-success);
}

.brand-flash-notice::before {
  content: "Notice";
  background: color-mix(in srgb, var(--color-success) 16%, transparent);
  color: #047857;
}

.brand-flash-alert,
.brand-flash-error {
  border-left-color: var(--color-danger);
}

.brand-flash-alert::before {
  content: "Alert";
  background: color-mix(in srgb, var(--color-danger) 12%, transparent);
  color: var(--color-danger);
}

.brand-flash-error::before {
  content: "Error";
  background: color-mix(in srgb, var(--color-danger) 12%, transparent);
  color: var(--color-danger);
}

/* Pagination — branded pagy series-nav chrome (D5.3) */
.pagination-wrapper {
  margin-top: var(--spacing-lg);
  padding: var(--spacing-md);
  display: flex;
  justify-content: center;
}

.pagination-wrapper nav {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--spacing-xs);
}

.pagination-wrapper nav a,
.pagination-wrapper nav span {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 36px;
  height: 36px;
  padding: 0 10px;
  border: 1px solid var(--content-border);
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 700;
  color: var(--content-secondary);
  text-decoration: none;
  transition: all var(--transition-fast);
}

.pagination-wrapper nav a:not([aria-disabled="true"]):hover {
  border-color: var(--batch);
  background: var(--batch-subtle);
  color: var(--content-text);
}

.pagination-wrapper nav a.current,
.pagination-wrapper nav span.current,
.pagination-wrapper nav [aria-current="page"] {
  background: var(--block-bg);
  border-color: var(--block-bg);
  color: var(--batch);
}

.pagination-wrapper nav a[aria-disabled="true"]:not([aria-current="page"]) {
  color: var(--text-muted);
  cursor: default;
}

.pagination-wrapper nav .gap {
  border-color: transparent;
  color: var(--text-muted);
}

/* Empty states — branded placeholder for empty listings (D5.3) */
.empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 56px 32px;
  background: var(--content-surface);
  border: 1px solid var(--content-border);
}

.empty-state-title {
  margin: 0;
  font-family: var(--font-mono);
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--content-text);
}

.empty-state-body {
  margin: 10px auto 0;
  max-width: 440px;
  font-family: var(--font-body);
  font-size: 14px;
  line-height: 1.6;
  color: var(--content-muted);
}

.empty-state .brand-btn--primary {
  margin-top: 20px;
}

/* Toast Notifications */
.toast-container {
  position: fixed;
  bottom: 20px;
  right: 20px;
  z-index: 9999;
  display: flex;
  flex-direction: column-reverse;
  gap: 8px;
  pointer-events: none;
}

.toast-notification {
  background: var(--bg-card);
  border: var(--border-weight) solid var(--batch);
  border-radius: 8px;
  padding: 12px 16px;
  min-width: 300px;
  max-width: 400px;
  opacity: 0;
  transform: translateX(100%);
  transition: all 0.3s ease;
  pointer-events: auto;
  box-shadow: var(--shadow-elevated);
}

.toast-visible {
  opacity: 1;
  transform: translateX(0);
}

.toast-hiding {
  opacity: 0;
  transform: translateX(100%);
}

.toast-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 6px;
}

.toast-title {
  color: var(--batch);
  font-weight: 600;
  font-size: 0.9375rem;
}

.toast-close {
  background: transparent;
  border: none;
  color: var(--text-muted);
  font-size: 1.5rem;
  line-height: 1;
  cursor: pointer;
  padding: 0;
  width: 24px;
  height: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: color var(--transition-fast);
}

.toast-close:hover {
  color: var(--text-primary);
}

.toast-body {
  color: var(--text-secondary);
  font-size: 0.875rem;
  line-height: 1.4;
}

.toast-warning { border-color: var(--warning, #FBBF24); }
.toast-warning .toast-title { color: var(--warning, #FBBF24); }
.toast-error { border-color: var(--danger, #EF4444); }
.toast-error .toast-title { color: var(--danger, #EF4444); }

/* ══════════════════════════════════════
   Auth pages — page-local chrome only (CQ-23 closeout).
   Inputs, labels, buttons, errors all live on brand-kit primitives
   (brand-form-*, brand-btn--*). What remains here is the per-page
   header (title + subtitle outside the card), the "or" divider that
   splits the email/password form from the SSO button, the SSO
   button's row layout, and the footer link rhythm.
   ══════════════════════════════════════ */
.auth-page {
  width: 100%;
}

.auth-page-header {
  margin-bottom: 24px;
}

.auth-page-title {
  font-family: var(--font-display, 'Archivo Black', 'Arial Black', Impact, sans-serif);
  font-size: 22px;
  color: var(--content-text, #0A0A0A);
  margin-bottom: 8px;
}

.auth-page-subtitle {
  font-size: 14px;
  color: var(--content-secondary, #4A4E5A);
  margin: 0;
}

/* J1 (2FA-05) — the OTP page carries TWO subtitles: which account is being
   signed in to, then the how-to. `.auth-page-subtitle` is `margin: 0`, so
   without this they butt together and read as one run-on sentence. Written as
   an adjacent-sibling rule rather than a modifier class so any future
   double-subtitle inherits the spacing instead of re-finding this. */
.auth-page-subtitle + .auth-page-subtitle {
  margin-top: var(--spacing-sm, 8px);
}

.auth-divider {
  text-align: center;
  padding: 16px 0;
  color: var(--content-muted, #6B7085);
  font-size: 13px;
  position: relative;
}

.auth-divider::before,
.auth-divider::after {
  content: '';
  position: absolute;
  top: 50%;
  width: 40%;
  height: 1px;
  background: var(--content-border, #E5E7EB);
}

.auth-divider::before { left: 0; }
.auth-divider::after { right: 0; }

/* Auth pages keep prominent full-width submit + SSO buttons — the
   form lives inside a narrow 448px right-panel column. */
.auth-page .brand-form-actions {
  display: block;
}

.auth-page .brand-form-actions .brand-btn {
  width: 100%;
}

.auth-sso-btn {
  width: 100%;
}

.auth-hint {
  text-align: center;
  font-size: 12px;
  color: var(--content-muted, #9CA3AF);
  margin-top: 8px;
}

.auth-link {
  text-align: center;
  margin-top: 16px;
  font-size: 13px;
  color: var(--content-muted, #6B7085);
}

.auth-link a {
  color: var(--batch, #00F5D4);
  text-decoration: none;
  font-weight: 500;
  transition: color 0.5s ease;
}

/* ══════════════════════════════════════
   Surveys — Out Sauce
   ══════════════════════════════════════ */

.surveys-page .page-header h1 {
  font-family: var(--font-display, 'Archivo Black', sans-serif);
  color: #0A0A0A;
}

.surveys-page .data-table th {
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.08em;
  color: #6B7280;
}

.surveys-page .data-table td {
  font-family: var(--font-body, 'Inter', sans-serif);
  font-size: 13px;
  color: #0A0A0A;
}

.surveys-page .data-table tbody tr:hover {
  border-left: 3px solid var(--batch, #00F5D4);
}

.surveys-page .surveys-table-card {
  background: #FFFFFF;
  border: 1px solid #E5E7EB;
}

.survey-show-page .survey-form-card {
  background: #FFFFFF;
  border: 1px solid #E5E7EB;
}

.survey-banner {
  background: #0A0A0A;
  border-bottom: 3px solid var(--batch, #00F5D4);
}

.survey-banner-text h3 {
  font-family: var(--font-display, 'Archivo Black', sans-serif);
  color: rgba(255, 255, 255, 0.9);
}

.survey-banner-text p {
  font-family: var(--font-mono);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: rgba(255, 255, 255, 0.5);
}

.survey-card {
  background: #FFFFFF;
  border: 1px solid #E5E7EB;
}

.survey-card .meta-label {
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.08em;
  color: #6B7280;
}

/* W4-3 message kind components — file_link, kb_card, system_kb_announce */

/* T13 — file_link kind: subtle <hr> + paperclip + filename hyperlink */
.message-file-link {
  display: inline-flex;
  align-items: center;
  color: var(--batch, #00F5D4);
  font-size: 13px;
  text-decoration: none;
  padding: 2px 0;
}

.message-file-link:hover {
  text-decoration: underline;
}

.message-file-link--unavailable {
  color: rgba(255, 255, 255, 0.4);
  font-style: italic;
  pointer-events: none;
}

.message-file-link-name {
  font-family: var(--font-mono);
  font-size: 12px;
}

.message-file-link-status {
  font-family: var(--font-mono);
  text-transform: uppercase;
  letter-spacing: 0.5px;
}

/* Screenshot thumbnails (paste + lightbox feature). The clickable wrapper is
   a <button>, never an <a> — an anchor inside the DM turbo frame would get
   intercepted and swap the pane with the image response. Sizing rides on the
   existing .attachment-image img rules; if a rule misbehaves on job threads
   only, check jobs.css (loads after this bundle, alphabetical) and use the
   two-class specificity trick documented at the breadcrumb avatar below. */
.attachment-thumb-btn {
  border: 0;
  padding: 0;
  background: none;
  cursor: pointer;
  display: block;
}

.attachment-thumb-btn:focus-visible {
  outline: 2px solid var(--batch, #00F5D4);
  outline-offset: 2px;
}

/* Reserved-box fallback when blob metadata (width/height) isn't analyzed yet —
   stops late image decode pushing the reading position under newest-at-top. */
.attachment-thumb-btn--reserve {
  min-height: 120px;
}

/* Fixed "Scanning..." placeholder for a pending Document thumbnail; the
   scan-status controller swaps it for the real image once the scan lands. */
.attachment-thumb--scanning {
  width: 280px;
  height: 120px;
  margin-top: 6px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--content-border, #E5E7EB);
  color: var(--content-muted, #6B7085);
  font-family: var(--font-mono);
  font-size: 12px;
}

/* T26/T27 — kb_card and system_kb_announce card. Renders inside the (light,
   theme-stable) message thread, so it uses content-* surface tokens. Sub-
   element selectors are scoped under .kb-card to avoid colliding with the
   identically-named .kb-card-title used by the KB index article card. */
.kb-card {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 8px 12px;
  background: var(--content-surface, #F7F7F8);
  border: 1px solid var(--content-border, #E5E7EB);
  color: var(--content-text, #1A1D26);
  text-decoration: none;
  max-width: 480px;
}

.kb-card:hover {
  background: color-mix(in srgb, var(--batch) 8%, var(--content-surface, #F7F7F8));
  border-color: var(--batch, #00F5D4);
}

.kb-card--unavailable {
  pointer-events: none;
  opacity: 0.5;
}

.kb-card .kb-card-icon {
  flex-shrink: 0;
  width: 24px;
  height: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--content-secondary, #4A4E5A);
}

.kb-card .kb-card-body {
  display: flex;
  flex-direction: column;
  gap: 2px;
  flex: 1;
  min-width: 0;
}

.kb-card .kb-card-title {
  font-size: 13px;
  font-weight: 600;
  color: var(--content-text, #1A1D26);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.kb-card .kb-card-scope {
  font-family: var(--font-mono);
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: var(--content-muted, #6B7085);
}

.kb-card .kb-card-scope--firm { color: var(--content-secondary, #4A4E5A); }
.kb-card .kb-card-scope--shared { color: var(--content-secondary, #4A4E5A); }
.kb-card .kb-card-scope--os_internal { color: var(--danger, #EF4444); }

/* ══════════════════════════════════════
   THE JOB-THREAD BREADCRUMB (REWORK-6 / Q12.3)

   One block, serving every system event in a thread. It replaces three
   near-identical blocks (.hold-*, .complete-*, and the kb-announce avatar),
   and it covers the two kinds — system_reassign and system_pipeline — that
   shipped with NO css at all and rendered as round aqua avatars outside the
   family entirely. Adding quote and rework to the old shape would have made
   seven skeletons and seven chances to forget the stylesheet entry.

   Q12.6 — roughly eleven events against four semantic colours plus the brand
   aqua. Colour therefore carries only the COARSE family and the icon + label
   carry the actual distinction:

     warning  needs someone to act        hold placed, quote submitted/revised,
                                          rework lodged, rework classified
     success  a good outcome landed       job complete, quote accepted,
                                          rework closed
     info     neutral, nobody must act    work resumed, job reassigned,
                                          upcoming pipeline
     danger   a bad outcome landed        quote rejected
     batch    knowledge, not job state    new KB article

   ⚠️ The green collision Q12.6 names (system_complete and hold-resumed
   identical, separated only by the glyph) is fixed by moving RESUME to slate,
   not by moving completion off green: completion is THE good-outcome event in
   a job thread, and a resume is a return to the status quo rather than an
   achievement. --info and --danger were both unused before this.

   Styles live here rather than inline in the partial so the partial emits no
   <style> sibling, which would break the .message-group + .message-group
   adjacency (divider) selector.
   ══════════════════════════════════════ */

/* Square, semantic-tinted "avatar" so a breadcrumb reads as a system event at
   a glance against the round initials avatars of people. Two-class specificity so it
   wins over the single-class .avatar-circle redefined in jobs.css on job-thread
   conversations; size is left to inherit so it matches the thread's others. */
.message-breadcrumb .avatar-circle.breadcrumb-avatar {
  border-radius: 0;
  background: var(--breadcrumb-colour, var(--content-text));
  color: #0A0A0A;
}

.breadcrumb-label {
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--breadcrumb-colour, var(--content-secondary));
}

.breadcrumb-body {
  font-family: var(--font-body);
}

.breadcrumb-headline {
  margin: 0;
  font-size: 13.5px;
  font-weight: 600;
  color: var(--content-text);
  line-height: 1.4;
}

.breadcrumb-detail {
  margin: 2px 0 0;
  font-size: 13px;
  color: var(--content-secondary);
  line-height: 1.5;
}

/* Free multi-paragraph body (the contractor's completion note). */
.breadcrumb-body p:not(.breadcrumb-headline):not(.breadcrumb-detail) {
  margin: 0;
  font-size: 13.5px;
  color: var(--content-secondary);
  line-height: 1.6;
}

.breadcrumb-body p:not(.breadcrumb-headline):not(.breadcrumb-detail) + p {
  margin-top: 8px;
}

/* One custom property per variant — the two rules above read it, so a new
   breadcrumb family is one line here and nothing else. */
.message-breadcrumb--hold            { --breadcrumb-colour: var(--warning); }
.message-breadcrumb--resume          { --breadcrumb-colour: var(--info); }
.message-breadcrumb--complete        { --breadcrumb-colour: var(--success); }
.message-breadcrumb--reassign        { --breadcrumb-colour: var(--info); }
.message-breadcrumb--pipeline        { --breadcrumb-colour: var(--info); }
.message-breadcrumb--kb-announce     { --breadcrumb-colour: var(--batch); }
.message-breadcrumb--quote           { --breadcrumb-colour: var(--warning); }
.message-breadcrumb--quote-accepted  { --breadcrumb-colour: var(--success); }
.message-breadcrumb--quote-rejected  { --breadcrumb-colour: var(--danger); }
.message-breadcrumb--rework          { --breadcrumb-colour: var(--warning); }
.message-breadcrumb--rework-closed   { --breadcrumb-colour: var(--success); }

/* --info and --danger are light enough / dark enough respectively that the
   #0A0A0A glyph above needs help only on danger. */
.message-breadcrumb--quote-rejected .avatar-circle.breadcrumb-avatar {
  color: #FFFFFF;
}

/* ══════════════════════════════════════
   File-upload preview list (REG-03 — shared by message composer +
   new-request form). Lifted from app/views/messages/_form.html.erb so
   the new-request file picker can reuse the same Stimulus controller
   (file_upload_controller.js). Form-specific blocks (.composer-input-row,
   .preview-category for T13) stay inline at messages/_form.html.erb.
   ══════════════════════════════════════ */
.file-preview-area {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-bottom: 8px;
}

.file-preview-area:empty { margin-bottom: 0; }

.file-preview-item {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 10px;
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid rgba(255, 255, 255, 0.12);
}

.preview-image {
  width: 48px;
  height: 48px;
  object-fit: cover;
  flex-shrink: 0;
}

.preview-icon {
  font-size: 1.5rem;
  flex-shrink: 0;
  width: 48px;
  text-align: center;
  color: rgba(255, 255, 255, 0.6);
}

.preview-icon.pdf { color: #e74c3c; }

.preview-info {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.preview-name {
  color: #FFFFFF;
  font-size: 12px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.preview-size {
  color: rgba(255, 255, 255, 0.4);
  font-size: 10px;
  font-family: var(--font-mono);
}

.preview-remove {
  width: 24px;
  height: 24px;
  background: transparent;
  border: 1px solid rgba(255, 255, 255, 0.2);
  color: rgba(255, 255, 255, 0.5);
  font-size: 14px;
  line-height: 1;
  cursor: pointer;
  flex-shrink: 0;
}

.preview-remove:hover {
  border-color: var(--batch);
  color: var(--batch);
}

/* ZIP-4 / D4 — "<name> is already added.", the attach box's own refusal when
   the same file is picked, dropped or pasted twice before sending.
   🔴 It lives HERE, beside the preview rules it renders into, rather than
   inline at messages/_form.html.erb, because BOTH consumers of
   file_upload_controller.js raise it: the dark job composer and the light
   new-request form (D18 gives that form this check and nothing else). The
   colour is the token, so it reads on either surface.
   `flex-basis: 100%` is for the card layout below, which turns the area into a
   wrapping ROW: without it the line would sit beside a card as a third column
   instead of under the list. */
.file-preview-error {
  flex-basis: 100%;
  margin: 0;
  color: var(--danger);
  font-size: 12px;
}

/* ══════════════════════════════════════
   #31 — Horizontal file cards (new-job "Attach Files" only).
   Opted into via .file-preview-area--cards + the controller's
   card-style value; the dark message composer keeps the row layout
   above. Each staged file is a bordered card with a coloured
   file-extension badge (or image thumbnail), filename, and size.
   ══════════════════════════════════════ */
.file-preview-area--cards {
  flex-direction: row;
  flex-wrap: wrap;
  gap: 12px;
}

.file-card {
  position: relative;
  width: 130px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  padding: 18px 12px 14px;
  background: #FFFFFF;
  border: 1px solid var(--content-border);
  border-radius: 8px;
  text-align: center;
}

.file-card-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 54px;
  height: 54px;
  border-radius: 6px;
  font-family: var(--font-mono);
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.02em;
  overflow: hidden;
  flex-shrink: 0;
}

.file-card-badge img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.file-card--pdf   .file-card-badge { color: #DC2626; background: color-mix(in srgb, #DC2626 12%, transparent); }
.file-card--sheet .file-card-badge { color: #16A34A; background: color-mix(in srgb, #16A34A 12%, transparent); }
.file-card--doc   .file-card-badge { color: #2563EB; background: color-mix(in srgb, #2563EB 12%, transparent); }
.file-card--image .file-card-badge { color: var(--text-muted); background: var(--content-border); }
.file-card--default .file-card-badge { color: #6B7280; background: color-mix(in srgb, #6B7280 12%, transparent); }

.file-card-name {
  width: 100%;
  font-size: 12px;
  font-weight: 500;
  color: var(--content-text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.file-card-size {
  font-size: 11px;
  color: var(--text-muted);
  font-family: var(--font-mono);
}

.file-card-remove {
  position: absolute;
  top: 5px;
  right: 5px;
  width: 22px;
  height: 22px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  background: transparent;
  border: none;
  border-radius: 4px;
  color: var(--text-muted);
  font-size: 16px;
  line-height: 1;
  cursor: pointer;
}

.file-card-remove:hover {
  color: #DC2626;
  background: color-mix(in srgb, #DC2626 10%, transparent);
}

/* New-request form-specific dropzone wrapper (REG-03) — visible "Choose
   Files" affordance + drag-and-drop hint. Composer reuses .composer-input-row
   for its own dropzone and is unaffected. */
.file-upload-dropzone {
  /* #30 — match the form inputs above (1.5px solid var(--content-border)) so the
     drop area reads as clearly present; kept dashed for the drop-zone affordance. */
  border: 1.5px dashed var(--content-border);
  padding: var(--spacing-md);
  background: var(--bg-card);
  transition: border-color var(--transition-fast), background var(--transition-fast);
}

.file-upload-dropzone.drag-over {
  border-color: var(--batch);
  background: var(--batch-subtle);
}

.file-upload-trigger {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-sm);
  padding: var(--spacing-sm) var(--spacing-md);
  cursor: pointer;
  color: var(--text-primary);
  font-weight: 500;
}

.file-upload-trigger:hover { color: var(--batch); }

.file-upload-hint {
  color: var(--text-muted);
  font-size: 12px;
  font-weight: 400;
}

/* ══════════════════════════════════════
   LIGHTBOX (screenshot paste feature)

   One page-level native <dialog> host (shared/_lightbox, layout-rendered in
   the authed branch next to the toast host). Opened by click@document
   delegation on [data-lightbox-src] thumbnails; closed by Esc (native),
   backdrop click, and turbo:before-cache. Flat design doctrine: no radius,
   2px border, no glow.
   ══════════════════════════════════════ */

.lightbox-dialog {
  /* Native <dialog> centres via `margin: auto`; the global `* { margin: 0 }`
     reset kills that — restore it explicitly (feedback-dialog precedent). */
  inset: 0;
  margin: auto;
  padding: 0;
  border: 2px solid #0A0A0A;
  background: #FFFFFF;
}

.lightbox-dialog::backdrop {
  background: rgba(10, 10, 10, 0.55);
  cursor: pointer;
}

.lightbox-dialog img {
  display: block;
  max-width: 90vw;
  max-height: 85vh;
  cursor: default;
}

/* ══════════════════════════════════════
   COMPOSE-6 (D11) — QUOTE REPLY

   The excerpt is `<blockquote>…</blockquote><cite>Name wrote <a>#</a></cite>`,
   and every selector below keys off the BARE tags (A.25): Trix's parser drops
   `class` / `data-*` on blocks and the sanitiser drops `data-*` everywhere, so
   there is nothing else to key off. Appended, not reflowed — the earlier
   COMPOSE-3 / COMPOSE-4 blockquote rules above stay where they are and these
   win on specificity or source order, deliberately.
   ══════════════════════════════════════ */

/* The Quote button. Same 26px circle as Edit and Delete beside it, so the slot
   reads as one row. Box styling only: `.message-actions` owns where it sits and
   when it appears. */
.message-quote-control {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  padding: 0;
  border: 1px solid var(--content-border, #E5E7EB);
  border-radius: 50%;
  background: var(--content-bg, #FFFFFF);
  color: var(--content-muted, #6B7085);
  cursor: pointer;
  transition: color 0.12s ease, border-color 0.12s ease;
}

.message-quote-control:hover {
  color: var(--batch);
  border-color: var(--batch);
}

/* The excerpt in a RENDERED message and in the EDITOR, styled by ONE rule so a
   quote does not change shape at the moment SEND is pressed (COMPOSE-4's
   parity principle). `.message-body > .trix-content blockquote` (0,2,1) and
   `trix-editor.message-editor blockquote` (0,2,1) above both set only `color`,
   and this rule matches at the same specificity later in the file, so it wins
   for the colour too. `actiontext.css`'s `.trix-content * { margin: 0; padding:
   0 }` (0,1,0) loses to all of these, but the spacing still has to be explicit
   here or the quote sits flush against the text above it. `border-radius: 0` is
   the flat house rule. */
.message-body blockquote,
.message-body > .trix-content blockquote,
trix-editor.message-editor blockquote {
  margin: 8px 0;
  padding: 8px 12px;
  border-left: 3px solid var(--batch);
  background: var(--content-surface);
  color: var(--content-secondary);
  border-radius: 0;
  font-size: 13px;
}

.message-body blockquote p + p { margin-top: 6px; }

/* The attribution line. The UA default for `<cite>` is italic and nothing in
   `actiontext.css` styles it, hence `font-style: normal` said out loud. Mono
   eyebrow, like the History control and the meta line's other labels.
   ⚠️ `--font-mono` resolves to Inter today (Appendix B). */
.message-body cite,
trix-editor.message-editor cite {
  display: block;
  margin-top: 6px;
  font-style: normal;
  font-family: var(--font-mono);
  font-size: 9px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--content-muted);
}

.message-body cite a,
trix-editor.message-editor cite a {
  color: inherit;
  text-decoration: underline;
  text-underline-offset: 2px;
}

/* The landing flash when a cite link is followed (`quote_anchor_controller`).
   A keyframe rather than a transition: the class is removed after ~2s, and a
   `transition` declared on the class itself stops applying the moment the
   class goes, so it would snap back instead of fading. */
.message-group--flash {
  animation: message-group-flash 2s ease-out;
}

@keyframes message-group-flash {
  0%   { background: var(--batch-subtle); }
  60%  { background: var(--batch-subtle); }
  100% { background: transparent; }
}

/* ══════════════════════════════════════
   COMPOSE-7 (D11) — SELECT-TEXT-TO-QUOTE

   The popover `selection_quote_controller` shows beside a text selection
   inside one message body. A SECOND PRODUCER for the excerpt above; nothing
   about the excerpt's own styling changes here.
   ══════════════════════════════════════ */

/* 🔴 `position: fixed` and a child of `<body>`, not of the stream. The
   controller writes `top`/`left` on every show and owns them — do NOT add a
   static offset here, it would fight the controller. `.message-stream` is
   `overflow-y: auto` and would clip an absolutely-positioned box (app-shell
   HOW-3a), which is the same reason `.brand-listbox__panel` is fixed.

   `z-index: 60` matches that panel deliberately: same tier, same reason, both
   controller-positioned fixed boxes over page content, and both below the
   modal layer (1000) and the mobile sidebar drawer (300).

   `translateX(-50%)` is what makes the controller's `left` the CENTRE of the
   box. Secondary-button spec (brand.css `.brand-btn--secondary`): transparent,
   `--content-border`, mono eyebrow type, hover to `--batch`. Square corners are
   the flat house rule.
   ⚠️ `--font-mono` resolves to Inter today (Appendix B). */
.quote-popover {
  position: fixed;
  z-index: 60;
  transform: translateX(-50%);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 24px;
  padding: 0 10px;
  background: var(--content-bg);
  color: var(--content-text);
  border: 1px solid var(--content-border);
  border-radius: 0;
  font-family: var(--font-mono);
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  white-space: nowrap;
  cursor: pointer;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
  transition: color 0.12s ease, border-color 0.12s ease;
}

.quote-popover:hover {
  color: var(--batch);
  border-color: var(--batch);
}

.quote-popover:focus-visible {
  outline: 2px solid var(--batch);
  outline-offset: 2px;
}

/* 🔴 Required, not tidy-up. `hidden` sets `display: none` at UA specificity
   (0,0,0) and the rule above sets `display: inline-flex` at (0,1,0), so the
   attribute alone does nothing to this element. */
.quote-popover[hidden] {
  display: none;
}
