/* Iris — stylesheet
   See /brandbook.md for the full design system rationale. Theme: "Iris" —
   the eye, the flower, the rainbow goddess. Dark, near-black bases with a
   signature violet→teal iridescent accent used sparingly (focus glows,
   active states, brand wordmark, user bubble) — never as decorative
   background wash. */

:root {
  /* Color — neutral scale */
  --bg: #0a0a0f;
  --bg-elevated: #121319;
  --bg-elevated-2: #191b23;
  --bg-elevated-3: #21242f;
  --border: #2a2d3a;
  --border-strong: #383c4c;
  --text: #eceef4;
  --text-muted: #9a9db0;
  --text-faint: #5c5f70;

  /* Color — signature iridescent accent (violet → teal), the "iris" motif.
     --accent is the default single-color reference used anywhere a flat
     color is required (borders, icons, links); --accent-2 is the teal pole
     of the gradient, used only in gradient pairs, never alone, so it never
     competes with --accent as "the" brand color. */
  --accent: #a78bfa;       /* violet */
  --accent-2: #5eead4;     /* teal */
  --accent-dim: #3d3467;   /* muted violet — user message bubble bg */
  --accent-soft: rgba(167, 139, 250, 0.14);
  --gradient-iris: linear-gradient(135deg, #a78bfa 0%, #818cf8 45%, #5eead4 100%);
  --glow-accent: 0 0 0 3px rgba(167, 139, 250, 0.22);

  --success: #4ade80;
  --warning: #fbbf24;
  --danger: #fb7185;

  /* Type */
  --font-display: "Fraunces", ui-serif, Georgia, serif;
  --font-sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
    "Helvetica Neue", Arial, sans-serif;
  --font-mono: "JetBrains Mono", ui-monospace, "SF Mono", "Cascadia Code",
    "Roboto Mono", monospace;

  --text-xs: 0.75rem;
  --text-sm: 0.875rem;
  --text-base: 1rem;
  --text-lg: 1.1875rem;
  --text-xl: 1.5rem;

  --leading-tight: 1.2;
  --leading-normal: 1.55;

  --tracking-tight: -0.015em;
  --tracking-wide: 0.04em;

  /* Spacing */
  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-3: 0.75rem;
  --space-4: 1rem;
  --space-6: 1.5rem;
  --space-8: 2rem;

  /* Radius */
  --radius-sm: 8px;
  --radius-md: 14px;
  --radius-lg: 20px;
  --radius-full: 999px;

  /* Shadows */
  --shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.35);
  --shadow-md: 0 12px 32px rgba(0, 0, 0, 0.45);

  /* Transitions */
  --ease-standard: cubic-bezier(0.4, 0, 0.2, 1);
  --transition-fast: 0.15s var(--ease-standard);
  --transition-base: 0.2s var(--ease-standard);
  --transition-slow: 0.35s var(--ease-standard);

  /* Scrollbars — subtle at rest, visible on hover/scroll, never the bright
     default OS/browser scrollbar which reads as jarring on this dark theme. */
  --scrollbar-thumb: rgba(236, 238, 244, 0.14); /* --text at low alpha */
  --scrollbar-thumb-hover: rgba(167, 139, 250, 0.4); /* accent-tinted on hover */
}

@media (prefers-reduced-motion: reduce) {
  :root {
    --transition-fast: 0s;
    --transition-base: 0s;
    --transition-slow: 0s;
  }
}

* {
  box-sizing: border-box;
}

html, body {
  height: 100%;
}

body {
  margin: 0;
  /* Faint, fixed radial wash in the two accent hues — reads as ambient
     studio lighting, not a decorative gradient background. Extremely low
     alpha, fixed (never scrolls), CSS-only (no image asset). */
  background:
    radial-gradient(1200px 700px at 12% -10%, rgba(167, 139, 250, 0.07), transparent 60%),
    radial-gradient(1000px 600px at 100% 10%, rgba(94, 234, 212, 0.05), transparent 55%),
    var(--bg);
  background-attachment: fixed;
  color: var(--text);
  font-family: var(--font-sans);
  font-size: var(--text-base);
  line-height: var(--leading-normal);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

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

::selection {
  background: rgba(167, 139, 250, 0.35);
  color: var(--text);
}

/* Scrollbars — applied wherever content scrolls: chat log, assets drawer
   grid, lightbox (if content ever overflows there), and as a body-wide
   fallback. Firefox via scrollbar-width/scrollbar-color; Chromium/Safari
   via ::-webkit-scrollbar. Track is always transparent; thumb is a subtle
   translucent version of --text so it reads correctly against any of the
   dark background tokens, brightening slightly on hover for discoverability. */

* {
  scrollbar-width: thin;
  scrollbar-color: var(--scrollbar-thumb) transparent;
}

*::-webkit-scrollbar {
  width: 10px;
  height: 10px;
}

*::-webkit-scrollbar-track {
  background: transparent;
}

*::-webkit-scrollbar-thumb {
  background-color: var(--scrollbar-thumb);
  border-radius: var(--radius-full);
  border: 2px solid transparent;
  background-clip: padding-box;
}

*::-webkit-scrollbar-thumb:hover {
  background-color: var(--scrollbar-thumb-hover);
}

/* Layout */

.app {
  display: flex;
  flex-direction: column;
  height: 100vh;
  height: 100dvh;
}

.app-header {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid var(--border);
  background: var(--bg-elevated);
  flex-shrink: 0;
}

.app-header__brand {
  font-family: var(--font-display);
  font-size: var(--text-xl);
  font-weight: 600;
  font-optical-sizing: auto;
  letter-spacing: var(--tracking-tight);
  /* Signature gradient wordmark treatment — the one place the full
     violet→teal gradient renders as text fill rather than a border/glow
     accent, per brandbook "Asset Guidelines" (no logo mark yet). */
  background: var(--gradient-iris);
  background-clip: text;
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
}

.app-main {
  position: relative;
  flex: 1;
  display: flex;
  min-height: 0;
}

/* Session sidebar
   Persistent left-hand session list, mirrors the assets drawer's collapse/
   open pattern (fixed overlay on narrow viewports, in-flow column on wide
   ones — see .drawer below for the equivalent right-hand pattern this is
   modeled on) so the two panels behave consistently. Collapse state is
   persisted to localStorage under "iris.sidebar_open" (see iris.js),
   mirroring "iris.drawer_open". */

.sidebar {
  position: fixed;
  top: 0;
  left: 0;
  bottom: 0;
  width: min(280px, 100vw);
  background: var(--bg-elevated);
  border-right: 1px solid var(--border);
  transform: translateX(-100%);
  transition: transform var(--transition-base);
  display: flex;
  flex-direction: column;
  z-index: 20;
  box-shadow: var(--shadow-md);
}

.sidebar.is-open {
  transform: translateX(0);
}

@media (min-width: 768px) {
  .sidebar {
    position: relative;
    width: 260px;
    transform: none;
    display: none;
  }

  .sidebar.is-open {
    display: flex;
  }
}

.sidebar__header {
  padding: var(--space-4);
  border-bottom: 1px solid var(--border);
  flex-shrink: 0;
}

.sidebar__new-btn {
  width: 100%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
}

.sidebar__list {
  flex: 1;
  overflow-y: auto;
  padding: var(--space-2);
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.sidebar__empty {
  color: var(--text-faint);
  font-size: var(--text-sm);
  padding: var(--space-3);
}

.sidebar__item {
  display: block;
  width: 100%;
  text-align: left;
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  padding: var(--space-2) var(--space-3);
  cursor: pointer;
  color: var(--text-muted);
  transition: background var(--transition-fast), color var(--transition-fast),
    border-color var(--transition-fast);
}

.sidebar__item:hover {
  background: var(--bg-elevated-2);
  color: var(--text);
}

.sidebar__item:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.sidebar__item.is-active {
  background: var(--accent-soft);
  border-color: var(--accent);
  color: var(--text);
}

.sidebar__item-title {
  display: block;
  font-size: var(--text-sm);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.sidebar__item-date {
  display: block;
  font-size: var(--text-xs);
  color: var(--text-faint);
  margin-top: 2px;
}

/* Chat
   .chat spans the full width available next to the drawer (it is NOT the
   width-constrained column) so its scroll container's scrollbar sits at the
   true edge of that space. The readability-width column
   (~760-820px, brandbook "Spacing System") is an inner wrapper
   (.chat__log-inner / .chat__composer, both centered via margin:0 auto)
   nested inside the full-width scroller/footer — this is what keeps the
   scrollbar from cutting mid-screen at wide viewports instead of hugging
   the drawer's edge. */

.chat {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-width: 0;
  max-width: 100%;
}

.chat__log {
  flex: 1;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  align-items: center;
}

.chat__log-inner {
  width: 100%;
  max-width: 820px;
  padding: var(--space-6) var(--space-4);
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.msg {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  max-width: 70ch;
  animation: msg-appear var(--transition-slow);
}

@keyframes msg-appear {
  from { opacity: 0; transform: translateY(4px); }
  to { opacity: 1; transform: translateY(0); }
}

@media (prefers-reduced-motion: reduce) {
  .msg {
    animation: none;
  }
}

.msg--user {
  align-self: flex-end;
  align-items: flex-end;
}

.msg--assistant {
  align-self: flex-start;
  align-items: flex-start;
}

.msg__bubble {
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-md);
  white-space: pre-wrap;
  word-wrap: break-word;
}

/* Assistant messages render directly on the canvas — no bubble chrome
   (no background/border/shadow). This is intentional and asymmetric with
   user messages: only the user's own messages keep the bubble treatment
   below, so assistant text reads as part of the page rather than boxed
   chat-app UI (brandbook.md "Assistant messages are chrome-free"). */

.msg--user .msg__bubble {
  background: var(--accent-dim);
  border: 1px solid rgba(167, 139, 250, 0.25);
}

.msg__meta {
  font-size: var(--text-xs);
  color: var(--text-faint);
  padding: 0 var(--space-1);
}

/* User message attachment previews — shown inline in the user's own sent
   bubble (same turn, no round trip: rendered from the client-side File
   object via createObjectURL). Mirrors the assistant-side .artifact
   treatment at a smaller scale since these sit alongside the user's own
   short message text rather than being the message's main content. */

.msg__attachments {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-bottom: var(--space-2);
  justify-content: flex-end;
}

.msg--user .msg__attachments {
  justify-content: flex-end;
}

.msg__attachment-thumb {
  display: block;
  width: 96px;
  height: 96px;
  object-fit: cover;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  cursor: zoom-in;
  transition: transform var(--transition-fast), border-color var(--transition-fast);
}

.msg__attachment-thumb:hover {
  transform: scale(1.02);
  border-color: var(--accent);
}

.msg__attachment-chip {
  align-self: center;
}

.msg__attachment-chip-name {
  max-width: 160px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.msg__text {
  white-space: pre-wrap;
  word-wrap: break-word;
}

/* System notices — a distinct lane from chat messages: not an assistant
   bubble, not user-authored. Used for connection errors and the
   low-credits/needs_topup notice. Centered, full-width-ish within the
   message column, with a left accent bar instead of the bubble treatment so
   it reads clearly as "the app talking", not "Iris talking". */

.system-notice {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-3);
  align-self: stretch;
  max-width: 70ch;
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-sm);
  border-left: 3px solid var(--border);
  background: var(--bg-elevated-2);
  font-size: var(--text-sm);
  box-shadow: var(--shadow-sm);
}

.system-notice__label {
  flex: 1;
  min-width: 0;
}

.system-notice--error {
  border-left-color: var(--danger);
}

.system-notice--error .system-notice__label {
  color: var(--danger);
}

.system-notice--topup {
  border-left-color: var(--warning);
}

.system-notice--topup .system-notice__label {
  color: var(--warning);
}

.system-notice__action {
  flex-shrink: 0;
}

/* Tool activity chips */

.chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-1) var(--space-3);
  border-radius: var(--radius-full);
  background: var(--bg-elevated-2);
  border: 1px solid var(--border);
  color: var(--text-muted);
  font-size: var(--text-xs);
  align-self: flex-start;
  max-width: fit-content;
  transition: border-color var(--transition-fast), color var(--transition-fast);
}

.chip--active {
  color: var(--text);
  /* Universal "in progress" cue for every active tool chip, not just
     LONG_RUNNING_TOOLS — a bare dot pulse (below) reads as static/dead in
     peripheral vision, which is what prompted this: a 3-image generation
     turn (~40s+ of back-to-back generate_image calls, each individually
     too short to qualify as "long-running") had nothing but a plain
     "started" chip with no visible life in between. Reuses the same
     chip-glow keyframes as .chip--long-running (below) at the same
     intensity — long-running chips layer their own elapsed-counter/
     reassurance copy on top, but the glow itself is now the baseline for
     ANY chip mid-flight, not an escalation reserved for slow tools. */
  border-color: var(--accent);
  animation: chip-glow 2.4s ease-in-out infinite;
}

.chip--active .chip__dot {
  animation: chip-pulse 1.4s ease-in-out infinite;
}

@media (prefers-reduced-motion: reduce) {
  .chip--active .chip__dot {
    animation: none;
  }
  .chip--active {
    animation: none;
  }
}

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

.chip__dot {
  display: inline-block;
}

@keyframes chip-pulse {
  0%, 100% { opacity: 0.35; }
  50% { opacity: 1; }
}

/* Elapsed-seconds counter on long-running tool chips (e.g. generate_video,
   which can take 2-10 minutes) — keeps a long wait feeling alive instead of
   a frozen "…". Muted color so it reads as secondary detail, not the label
   itself. Empty (no layout shift) for tools that don't opt in. */

.chip__elapsed {
  color: var(--text-faint);
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}

/* Reassurance subtext for known-slow tools (LONG_RUNNING_REASSURANCE in
   iris.js) — e.g. "This can take up to 10 minutes — hang tight." Sits after
   the elapsed counter, on its own visual register (italic, --text-faint) so
   it reads as a helpful aside, not part of the status label itself. Empty
   string (no layout shift) unless the tool opts in. */

.chip__reassurance {
  color: var(--text-faint);
  font-style: italic;
}

.chip__reassurance:not(:empty)::before {
  content: "— ";
}

/* Long-running tool chips (generate_video, etc.) previously got their own
   full-chip glow pulse here, layered on top of .chip--active. That glow is
   now the universal .chip--active treatment above (every in-progress chip
   gets it, not just long-running ones), so .chip--long-running no longer
   needs its own animation rule — it still exists as a class (see iris.js:
   setChipState/isLongRunningTool) purely to gate the elapsed-seconds
   counter and reassurance subtext, which remain long-running-only. Kept
   the shared keyframes name (chip-glow) since .chip--active references it. */

@keyframes chip-glow {
  0%, 100% { box-shadow: 0 0 0 0 rgba(167, 139, 250, 0); }
  50% { box-shadow: 0 0 0 3px rgba(167, 139, 250, 0.22); }
}

/* "Thinking…" indicator — fills the gap between sending a message and the
   first visible sign of life (first token delta, tool_activity, asset
   reference, or error/topup event), so the assistant bubble never sits
   completely empty/idle-looking while the agent is still deciding what to
   do. Reuses the same chip + pulsing-dot treatment as tool-activity chips
   for visual consistency, removed as soon as real content arrives. */

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

/* Tool-activity visibility toggle — hides chips app-wide when off.
   .chip--asset-ref is excluded: it's an artifact-dedupe reference, not tool
   activity, so it stays visible regardless of the toggle. */

body.tools-hidden .chip:not(.chip--asset-ref):not(.chip--thinking) {
  display: none;
}

/* Asset reference chip — replaces a markdown-embedded <img> that duplicates
   an asset already rendered via asset_reference this turn (artifacts render
   once, at creation). Reuses the base .chip pill styling; button element for
   keyboard access, click scrolls to + lightboxes the original render. */

.chip--asset-ref {
  border: none;
  cursor: pointer;
  font: inherit;
  color: var(--accent);
  transition: color 0.15s ease, border-color 0.15s ease;
}

.chip--asset-ref:hover,
.chip--asset-ref:focus-visible {
  color: var(--accent-2);
}

.chip--asset-ref:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* Specialist sub-agent activity cards (agent_start/agent_end SSE events) —
   "one director + isolated sub-agent workers" architecture. Distinct from
   the plain .chip pill used for tool_activity: a delegated agent call is a
   bigger unit of work (its own model, its own tool uses inside it), so it
   gets a small bordered card rather than a one-line pill. Collapses to a
   one-line summary on agent_end, matching the chip's started→settled
   pattern but with its own visual weight. See docs/webui-templates-index.md
   "Director + sub-agent worker usage UI". */

.agent-card {
  align-self: flex-start;
  max-width: min(420px, 100%);
  background: var(--bg-elevated-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: var(--space-2) var(--space-3);
  font-size: var(--text-xs);
  transition: border-color var(--transition-fast), background var(--transition-fast);
}

.agent-card--active {
  border-color: var(--accent);
  animation: chip-glow 2.4s ease-in-out infinite;
}

@media (prefers-reduced-motion: reduce) {
  .agent-card--active {
    animation: none;
  }
}

.agent-card__head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  color: var(--text);
  font-weight: 500;
}

.agent-card__dot {
  color: var(--accent);
  animation: chip-pulse 1.4s ease-in-out infinite;
}

@media (prefers-reduced-motion: reduce) {
  .agent-card__dot {
    animation: none;
  }
}

.agent-card__summary {
  color: var(--text-muted);
  margin-top: var(--space-1);
  /* Defensive clamp: result_summary is model-authored free text and the JS
     already trims it to ~one sentence, but this caps runaway/unpunctuated
     text at 3 lines instead of letting it balloon the card. */
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.agent-card--done {
  border-color: var(--border);
}

.agent-card--done .agent-card__badge {
  color: var(--success);
}

.agent-card--failed {
  border-color: var(--danger);
}

.agent-card--failed .agent-card__badge {
  color: var(--danger);
}

.agent-card__line {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  color: var(--text-muted);
}

.agent-card__badge {
  flex-shrink: 0;
  font-weight: 600;
}

.agent-card__oneliner strong {
  color: var(--text);
  font-weight: 500;
}

body.tools-hidden .agent-card {
  display: none;
}

/* Inline artifacts */

.artifact {
  display: block;
  max-width: 320px;
  width: 100%;
  border-radius: var(--radius-md);
  border: 1px solid var(--border);
  cursor: zoom-in;
  margin-top: var(--space-2);
  box-shadow: var(--shadow-sm);
  transition: border-color var(--transition-fast), transform var(--transition-fast),
    box-shadow var(--transition-fast);
}

.artifact:hover {
  border-color: var(--accent);
  transform: translateY(-1px);
  box-shadow: var(--shadow-md);
}

/* Video artifacts (generate_video / Seedance / assemble_video) — same
   max-width/radius/border treatment as image artifacts, but native <video
   controls> instead of click-to-lightbox: the browser's built-in
   play/scrub/volume/fullscreen controls already cover that interaction, so
   overriding cursor/click would just fight the controls. No autoplay, ever.

   Bug fix (2026-08-27, live dogfooding report: "library is weird the bottom
   has cramped videos on top of each other"): unlike .drawer__thumb (fixed
   aspect-ratio:1 box) and .msg__attachment-thumb (fixed 96x96), .artifact-
   video previously had NO explicit height or aspect-ratio — only .artifact's
   shared width:100%/max-width:320px. An <img> tolerates that fine (browsers
   generally still reserve/render at natural size once the element itself is
   in the DOM), but a <video> with preload="metadata" has no CSS-computed
   height until metadata actually loads over the network, so it renders at
   the UA default (~150px, browser-dependent) then jumps once metadata
   arrives. With multiple video artifacts landing close together in one
   assistant turn (the new assemble_video tool makes multi-video turns more
   common), that pre-metadata collapse read as "cramped/overlapping" during
   the load race. Fix: give the artifact a fixed 16:9 aspect-ratio box up
   front (landscape is the common case) so it reserves correct, stable space
   immediately, same pattern as the drawer/attachment thumbs. object-fit:
   contain (not cover, unlike the square thumbs) so a portrait/9:16 video
   letterboxes inside the 16:9 box instead of being cropped — acceptable
   since native <video controls> is the primary interaction here, not a
   cropped preview tile. max-height keeps very tall/portrait sources from
   ever visually dominating the chat column even before metadata resolves. */

.artifact-video {
  cursor: default;
  background: #000;
  aspect-ratio: 16 / 9;
  max-height: 400px;
  object-fit: contain;
}

/* Markdown content inside assistant bubbles */

.msg--assistant .msg__bubble {
  white-space: normal;
}

.msg__bubble > *:first-child {
  margin-top: 0;
}

.msg__bubble > *:last-child {
  margin-bottom: 0;
}

.msg__bubble p {
  margin: 0 0 var(--space-3);
  white-space: pre-wrap;
}

.msg__bubble h1,
.msg__bubble h2,
.msg__bubble h3,
.msg__bubble h4,
.msg__bubble h5,
.msg__bubble h6 {
  margin: var(--space-4) 0 var(--space-2);
  line-height: var(--leading-tight);
  font-family: var(--font-display);
  font-weight: 600;
  letter-spacing: var(--tracking-tight);
}

.msg__bubble h1 { font-size: var(--text-lg); }
.msg__bubble h2 { font-size: var(--text-lg); }
.msg__bubble h3,
.msg__bubble h4,
.msg__bubble h5,
.msg__bubble h6 { font-size: var(--text-base); }

.msg__bubble ul,
.msg__bubble ol {
  margin: 0 0 var(--space-3);
  padding-left: var(--space-6);
}

.msg__bubble li {
  margin-bottom: var(--space-1);
}

.msg__bubble blockquote {
  margin: 0 0 var(--space-3);
  padding-left: var(--space-3);
  border-left: 2px solid var(--border);
  color: var(--text-muted);
}

.msg__bubble hr {
  border: none;
  border-top: 1px solid var(--border);
  margin: var(--space-4) 0;
}

.msg__bubble table {
  width: 100%;
  margin: 0 0 var(--space-3);
  border-collapse: collapse;
  font-size: var(--text-sm);
  overflow-x: auto;
  display: block;
}

.msg__bubble thead,
.msg__bubble tbody {
  display: table;
  width: 100%;
  table-layout: fixed;
}

.msg__bubble th,
.msg__bubble td {
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--border);
  text-align: left;
  vertical-align: top;
}

.msg__bubble th {
  background: var(--bg-elevated-2);
  color: var(--text);
  font-weight: 600;
}

.msg__bubble tbody tr:nth-child(even) {
  background: var(--bg-elevated-2);
}

.msg__bubble code {
  font-family: var(--font-mono);
  font-size: 0.9em;
  background: var(--bg-elevated-2);
  padding: 0.1em 0.35em;
  border-radius: var(--radius-sm);
}

.msg__bubble pre {
  margin: 0 0 var(--space-3);
  padding: var(--space-3);
  background: var(--bg-elevated-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  overflow-x: auto;
}

.msg__bubble pre code {
  background: none;
  padding: 0;
}

.msg__bubble a {
  color: var(--accent);
  text-underline-offset: 2px;
  transition: color var(--transition-fast);
}

.msg__bubble a:hover {
  color: var(--accent-2);
}

.msg__bubble .md-img {
  display: block;
  max-width: 320px;
  width: 100%;
  border-radius: var(--radius-md);
  border: 1px solid var(--border);
  cursor: zoom-in;
  margin: var(--space-2) 0;
  transition: border-color var(--transition-fast);
}

.msg__bubble .md-img:hover {
  border-color: var(--accent);
}

/* Composer
   .chat__composer-outer spans full width (matches .chat__log's role) and
   centers .chat__composer at the same max-width as .chat__log-inner, so the
   input/send button align with the message column above them. */

.chat__composer-outer {
  display: flex;
  justify-content: center;
  border-top: 1px solid var(--border);
  background: linear-gradient(to bottom, transparent, var(--bg) 40%);
  flex-shrink: 0;
}

.chat__composer-wrap {
  width: 100%;
  max-width: 820px;
}

.chat__composer {
  display: flex;
  align-items: flex-end;
  gap: var(--space-2);
  width: 100%;
  padding: var(--space-4);
}

.chat__attach-btn {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* Pending attachments row — sits above the composer input row, inside the
   same centered/width-matched wrapper. One chip per pasted/selected file. */

.attachments-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-4) 0;
}

.attachments-row[hidden] {
  display: none;
}

.attachment-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-1) var(--space-2) var(--space-1) var(--space-3);
  border-radius: var(--radius-full);
  background: var(--bg-elevated-2);
  border: 1px solid var(--border);
  font-size: var(--text-xs);
  color: var(--text-muted);
  max-width: 240px;
}

.attachment-chip__name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 140px;
}

.attachment-chip__status {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 14px;
  height: 14px;
  flex-shrink: 0;
}

/* Spinner for in-flight uploads */
.attachment-chip__spinner {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  border: 2px solid var(--border);
  border-top-color: var(--accent);
  animation: attachment-spin 0.7s linear infinite;
}

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

@media (prefers-reduced-motion: reduce) {
  .attachment-chip__spinner {
    animation: none;
  }
}

.attachment-chip--ready .attachment-chip__status {
  color: var(--success);
}

.attachment-chip--error {
  border-color: var(--danger);
  color: var(--danger);
}

.attachment-chip--error .attachment-chip__status {
  color: var(--danger);
}

.attachment-chip__remove {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  border-radius: var(--radius-full);
  border: none;
  background: transparent;
  color: var(--text-faint);
  cursor: pointer;
  font-size: var(--text-xs);
  line-height: 1;
  padding: 0;
}

.attachment-chip__remove:hover {
  color: var(--text);
}

.attachment-chip__remove:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.chat__input {
  flex: 1;
  resize: none;
  max-height: 200px;
  background: var(--bg-elevated-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: var(--space-3) var(--space-4);
  line-height: var(--leading-normal);
  transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}

.chat__input:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: var(--glow-accent);
}

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

/* Buttons */

.btn {
  border-radius: var(--radius-sm);
  border: 1px solid transparent;
  padding: var(--space-2) var(--space-4);
  font-weight: 500;
  cursor: pointer;
  transition: background var(--transition-fast), border-color var(--transition-fast),
    color var(--transition-fast), box-shadow var(--transition-fast),
    transform var(--transition-fast);
}

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

.btn--primary {
  background: var(--gradient-iris);
  color: #0a0a0f;
  font-weight: 600;
}

.btn--primary:hover {
  filter: brightness(1.08);
  box-shadow: var(--glow-accent);
}

.btn--primary:active {
  transform: translateY(1px);
}

.btn--primary:disabled {
  background: var(--text-faint);
  filter: none;
  box-shadow: none;
  cursor: not-allowed;
}

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

.btn--secondary:hover {
  border-color: var(--accent);
  color: var(--accent);
  background: var(--accent-soft);
}

.btn--xs {
  padding: var(--space-1) var(--space-2);
  font-size: var(--text-xs);
}

.btn--icon {
  padding: var(--space-2) var(--space-3);
  font-size: var(--text-sm);
}

.btn--active {
  border-color: var(--accent);
  color: var(--accent);
  background: var(--accent-soft);
}

/* Stop button — only shown while a turn is actively streaming (toggled via
   `hidden` in iris.js: streamChat), sits next to #chat-send in the composer.
   Uses .btn--secondary as its base (not --danger): stopping a turn is a
   normal, expected user action, not an error state, matching the same
   reasoning as "New session" above. The "Stopping…" state (post-click,
   pre-`stopped` event) is a plain disabled state, not restyled further —
   the label change alone communicates it, per the interrupt's known delay
   (see docs/backend-routes.md "Stop / interrupt": the stream doesn't end
   instantly, so it must read as "in progress" rather than "done" or "broken"). */

.chat__stop-btn:disabled {
  color: var(--text-faint);
  border-color: var(--border);
  cursor: not-allowed;
}

/* "Stopped" indicator chip — appended into the assistant's turn when a
   `stopped` SSE event ends it, distinguishing a user-interrupted turn from
   a normal `done` completion in the transcript (see iris.js: streamChat
   `stopped` case). Reuses the base .chip pill but intentionally NOT
   .chip--active (no pulse — the turn is over, not in progress) and not
   .chip--error (interrupting isn't a failure) — a quiet, static label. */

.chip--stopped {
  color: var(--text-faint);
  font-style: italic;
}

/* Sign-out control — header button, `.btn--secondary.btn--icon` like
   Tools/Library/New session, placed just before the credits widget (which
   is `margin-left: auto` and pushes everything after it, including this
   button and the remaining header controls, to the right edge). Shows the
   signed-in user's display_name (from `GET /api/auth/me`) as a small label
   before "Sign out" when available, so the affordance doubles as "who am I
   signed in as" without adding a whole new header element — falls back to
   just "Sign out" (span left `hidden`) if `/api/auth/me` fails or the
   backend doesn't return a display_name. See iris.js: initAuth(). */

#logout-btn {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}

#logout-username {
  color: var(--text-muted);
  font-size: var(--text-sm);
  font-weight: 500;
}

#logout-username[hidden] {
  display: none;
}

/* Header "New session" button — icon + label, same treatment as other
   .btn--secondary.btn--icon header controls (Tools/Library). Destructive
   action (wipes the visible chat + local session state), so it's confirmed
   before acting (see iris.js) rather than styled as --danger — it's a
   normal, expected action, not an error state. */
.app-header #new-session-btn {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}

/* Model badge — director model, header, next to the wordmark. Hidden by
   default (JS un-hides it once GET /api/models resolves — this is an
   optional/not-yet-live backend route, so the badge degrades to fully
   absent rather than showing an empty pill). Doubles as the usage-popover
   trigger (click), same target the popover's Escape/outside-click logic
   treats as "inside". See docs/webui-templates-index.md "Director +
   sub-agent worker usage UI". */

.model-badge {
  display: inline-flex;
  align-items: center;
  background: var(--bg-elevated-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-full);
  padding: var(--space-1) var(--space-3);
  font-size: var(--text-xs);
  color: var(--text-muted);
  cursor: pointer;
  transition: border-color var(--transition-fast), color var(--transition-fast);
}

.model-badge:hover,
.model-badge[aria-expanded="true"] {
  color: var(--accent);
  border-color: var(--accent);
}

.model-badge:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

@media (max-width: 639px) {
  .model-badge {
    display: none; /* credits/usage control remains reachable; badge is a
                       secondary affordance not worth the header space at
                       390px */
  }
}

/* Credits widget — SaaS prepaid-credits balance display. The chat itself
   never talks about money; this widget owns all credit visibility. Shows
   remaining balance prominently; lifetime spent lives in a title tooltip
   only (see brandbook.md "Credits widget"). */

.credits-widget {
  margin-left: auto;
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--space-3);
  background: var(--bg-elevated-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-full);
  padding: var(--space-1) var(--space-2) var(--space-1) var(--space-4);
  font-size: var(--text-sm);
}

.credits-widget--low {
  color: var(--warning);
}

.credits-widget__balance {
  white-space: nowrap;
  font-weight: 500;
  cursor: default;
}

.credits-widget__topup {
  position: relative;
}

@media (max-width: 639px) {
  .credits-widget__balance {
    white-space: normal;
  }
}

/* Top-up popover — dev-only preset amounts, explicitly tagged "dev" since
   real payment integration isn't wired up yet. */

.credits-topup-menu {
  position: absolute;
  top: calc(100% + var(--space-2));
  right: 0;
  z-index: 30;
  display: flex;
  align-items: center;
  gap: var(--space-1);
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: var(--space-2);
  box-shadow: var(--shadow-md);
}

.credits-topup-menu[hidden] {
  display: none;
}

.credits-topup-menu__tag {
  font-size: var(--text-xs);
  color: var(--text-faint);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 0.1em 0.4em;
  margin-right: var(--space-1);
}

/* Session usage popover — total cost/tokens + a by-role (director/image
   check/batch) breakdown. Opens from #usage-toggle (next to credits) or the
   model badge; refreshed from GET /api/sessions/{id}/usage on open, kept
   live in between via accumulated `usage` SSE events. See
   docs/webui-templates-index.md "Director + sub-agent worker usage UI". */

.usage-popover {
  position: absolute;
  top: calc(100% + var(--space-2));
  right: 0;
  z-index: 30;
  width: min(300px, calc(100vw - 2 * var(--space-4)));
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: var(--space-4);
  box-shadow: var(--shadow-md);
}

.usage-popover[hidden] {
  display: none;
}

.usage-popover__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: var(--space-3);
}

.usage-popover__title {
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--text);
  margin: 0;
}

.usage-popover__close {
  background: transparent;
  border: none;
  color: var(--text-muted);
  cursor: pointer;
  font-size: var(--text-sm);
  line-height: 1;
  padding: var(--space-1);
  border-radius: var(--radius-sm);
  transition: color var(--transition-fast), background var(--transition-fast);
}

.usage-popover__close:hover {
  color: var(--text);
  background: var(--bg-elevated-2);
}

.usage-popover__close:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.usage-popover__totals {
  padding-bottom: var(--space-3);
  margin-bottom: var(--space-3);
  border-bottom: 1px solid var(--border);
}

.usage-popover__total-cost {
  font-family: var(--font-mono);
  font-size: var(--text-lg);
  font-weight: 500;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}

.usage-popover__total-tokens {
  font-size: var(--text-xs);
  color: var(--text-muted);
  margin-top: var(--space-1);
  font-variant-numeric: tabular-nums;
}

.usage-popover__roles {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.usage-popover__role-row {
  display: grid;
  grid-template-columns: 1fr auto auto auto;
  gap: var(--space-2);
  align-items: baseline;
  font-size: var(--text-xs);
}

.usage-popover__role-name {
  color: var(--text);
  font-weight: 500;
}

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

.usage-popover__role-calls {
  color: var(--text-faint);
  font-variant-numeric: tabular-nums;
}

.usage-popover__role-cost {
  color: var(--text);
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  text-align: right;
}

.usage-popover__empty {
  font-size: var(--text-xs);
  color: var(--text-faint);
  margin: 0;
}

@media (max-width: 639px) {
  .usage-popover {
    right: auto;
    left: 50%;
    transform: translateX(-50%);
    width: min(320px, calc(100vw - var(--space-4)));
  }
}

/* Drawer */

.drawer {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  width: min(320px, 100vw);
  background: var(--bg-elevated);
  border-left: 1px solid var(--border);
  transform: translateX(100%);
  transition: transform var(--transition-base);
  display: flex;
  flex-direction: column;
  z-index: 20;
  box-shadow: var(--shadow-md);
}

.drawer.is-open {
  transform: translateX(0);
}

@media (min-width: 768px) {
  .drawer {
    position: relative;
    width: 280px;
    transform: none;
    display: none;
  }

  .drawer.is-open {
    display: flex;
  }
}

/* Library status segments — All / Approved / Final. A subtle pill/segmented
   control (not tabs, not a select) matching the drawer's compact chrome.
   The sliding `.segmented__indicator` is positioned via inline
   left/width (iris.js: updateSegmentIndicator) rather than nth-child CSS,
   since the number/width of segments could change without a fixed count.
   See docs/webui-templates-index.md "Asset status / approval workflow". */

.segmented {
  position: relative;
  display: flex;
  gap: 2px;
  margin: 0 var(--space-4);
  margin-top: var(--space-3);
  padding: 3px;
  background: var(--bg-elevated-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-full);
}

.segmented__indicator {
  position: absolute;
  top: 3px;
  bottom: 3px;
  left: 3px;
  width: 0;
  background: var(--accent-soft);
  border: 1px solid var(--accent);
  border-radius: var(--radius-full);
  transition: transform var(--transition-base), width var(--transition-base);
  pointer-events: none;
}

.segmented__btn {
  position: relative;
  z-index: 1;
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-1);
  background: transparent;
  border: none;
  color: var(--text-muted);
  font-size: var(--text-xs);
  font-weight: 500;
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-full);
  cursor: pointer;
  transition: color var(--transition-fast);
  white-space: nowrap;
}

/* On touch devices the segmented control is the only way to switch
   status filters (no keyboard-shortcut equivalent for this control), so
   its buttons need a real 44px tap target — the 23px-tall desktop pill
   is fine for a mouse pointer but too fussy to hit reliably with a
   finger. */
@media (hover: none) {
  .segmented__btn {
    min-height: 44px;
  }
}

.segmented__btn:hover {
  color: var(--text);
}

.segmented__btn.is-active {
  color: var(--accent);
}

.segmented__btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

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

.segmented__count:empty {
  display: none;
}

.segmented__btn.is-active .segmented__count {
  color: var(--accent);
}

@media (prefers-reduced-motion: reduce) {
  .segmented__indicator {
    transition: none;
  }
}

/* Per-card approval-status chip + change-status affordance. Draft assets
   show nothing (default/unmarked state, not worth a badge) — only
   approved/final get a small chip in the corner. The action control (opens
   a small popover offering the two non-current statuses — see
   .drawer__status-popover below) is hover-revealed on pointer devices,
   always visible on touch (no hover state to reveal it). */

.drawer__thumb-wrap {
  position: relative;
}

.drawer__status-chip {
  position: absolute;
  top: 4px;
  left: 4px;
  z-index: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  border-radius: var(--radius-full);
  font-size: 10px;
  line-height: 1;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  pointer-events: none;
}

.drawer__status-chip--approved {
  color: var(--success);
  border-color: var(--success);
}

.drawer__status-chip--final {
  color: var(--accent);
  border-color: var(--accent);
}

.drawer__status-chip.is-popping {
  animation: status-pop 0.35s ease;
}

@keyframes status-pop {
  0% { transform: scale(0.6); }
  60% { transform: scale(1.25); }
  100% { transform: scale(1); }
}

.drawer__status-action {
  position: absolute;
  top: 4px;
  right: 4px;
  z-index: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  border-radius: var(--radius-full);
  background: rgba(10, 10, 15, 0.65);
  border: 1px solid var(--border);
  color: var(--text);
  font-size: var(--text-xs);
  line-height: 1;
  cursor: pointer;
  opacity: 0;
  transition: opacity var(--transition-fast), border-color var(--transition-fast),
    color var(--transition-fast);
}

/* The visible chip stays small (22px) to fit the card corner without
   dominating the thumbnail, but its actual hit target is expanded to the
   WCAG-recommended 44x44px minimum via an invisible ::after — matters most
   on touch, where this control is always-visible (no hover to compensate
   for a fussy tap target). */
.drawer__status-action::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 44px;
  height: 44px;
  transform: translate(-50%, -50%);
}

.drawer__thumb-wrap:hover .drawer__status-action,
.drawer__thumb-wrap:focus-within .drawer__status-action,
.drawer__status-action.is-touch-visible {
  opacity: 1;
}

.drawer__status-action:hover {
  border-color: var(--accent);
  color: var(--accent);
}

.drawer__status-action:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  opacity: 1;
}

@media (hover: none) {
  .drawer__status-action {
    opacity: 1;
  }
}

/* Status-change popover — replaces the old draft->approved->final->draft
   round-robin. Opens anchored under the "⋯" button, listing the two
   statuses other than the card's current one so the user can jump straight
   to any target in one click instead of cycling through. */

.drawer__status-popover {
  position: absolute;
  top: 30px;
  right: 4px;
  z-index: 2;
  display: flex;
  flex-direction: column;
  min-width: 128px;
  padding: 4px;
  border-radius: var(--radius-sm);
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  box-shadow: var(--shadow-md);
}

.drawer__status-popover-option {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 8px;
  border: none;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--text);
  font-size: var(--text-xs);
  text-align: left;
  cursor: pointer;
  transition: background var(--transition-fast);
}

.drawer__status-popover-option:hover,
.drawer__status-popover-option:focus-visible {
  background: rgba(255, 255, 255, 0.08);
}

.drawer__status-popover-option:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

.drawer__status-popover-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  flex-shrink: 0;
  font-size: 10px;
  line-height: 1;
}

.drawer__status-popover-option--approved .drawer__status-popover-icon {
  color: var(--success);
}

.drawer__status-popover-option--final .drawer__status-popover-icon {
  color: var(--accent);
}

.drawer__thumb-wrap:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* Empty state copy for the Approved/Final segments — friendlier and more
   specific than the base "No assets yet." string used for All/no assets. */

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

.drawer__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--space-4);
  border-bottom: 1px solid var(--border);
}

.drawer__title {
  font-family: var(--font-display);
  font-size: var(--text-lg);
  font-weight: 600;
  letter-spacing: var(--tracking-tight);
  margin: 0;
}

.drawer__grid {
  flex: 1;
  overflow-y: auto;
  padding: var(--space-4);
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
  gap: var(--space-2);
  align-content: start;
}

.drawer__empty {
  grid-column: 1 / -1;
  color: var(--text-faint);
  font-size: var(--text-sm);
}

.drawer__thumb {
  width: 100%;
  aspect-ratio: 1;
  object-fit: cover;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  cursor: zoom-in;
  background: var(--bg-elevated-2);
  transition: border-color var(--transition-fast), transform var(--transition-fast);
}

.drawer__thumb:hover {
  border-color: var(--accent);
  transform: scale(1.02);
}

/* Video thumbnail tile — v0 treatment: a muted, controls-less <video
   preload="metadata"> shows the first frame as a poster without downloading
   the full file, with a play-icon overlay so it reads as "video" at a
   glance in the grid. Click opens the lightbox's video player (full native
   controls). Unlike img.drawer__thumb this is a <div> wrapper (button role)
   since it holds two children (video + icon). */

.drawer__thumb--video {
  position: relative;
  padding: 0;
  overflow: hidden;
  display: block;
  /* BUG FIX 2026-08-27: .drawer__thumb sets aspect-ratio: 1, but this rule (the
     video wrapper <div>) never inherited it — a <div> has no intrinsic size, so
     without an explicit aspect-ratio its height was driven purely by the child
     <video>'s loaded size. A portrait 9:16 clip rendered far taller than the
     square image tiles beside it, crowding/cramming the grid exactly like the
     inline chat-artifact bug fixed earlier — same root cause (unconstrained video
     box in a grid of otherwise-uniform square tiles), different component. Video
     tiles are a grid THUMBNAIL, not the full player (that's the lightbox), so
     forcing square + object-fit: cover (crop, not letterbox) is correct here,
     unlike the inline chat artifact which preserves real aspect ratio for full
     playback. */
  aspect-ratio: 1;
}

.drawer__thumb--video video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  pointer-events: none;
}

.drawer__thumb-play {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--text-lg);
  color: var(--text);
  background: rgba(10, 10, 15, 0.4);
  pointer-events: none;
}

.drawer__thumb--video:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* Lightbox */

.lightbox {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.85);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 50;
  padding: var(--space-6);
}

.lightbox[hidden] {
  display: none;
}

.lightbox__stage {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-3);
  max-width: 100%;
  max-height: 100%;
}

.lightbox__img {
  max-width: 100%;
  max-height: 70vh;
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
}

.lightbox__video {
  max-width: 100%;
  max-height: 70vh;
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
}

.lightbox__video[hidden] {
  display: none;
}

/* Lightbox status + history panel — fetched on open (GET
   /api/assets/{id}/history), shown below the media. Kept compact: a status
   row (current status + inline change control, reusing the same cycle
   affordance as the card) and a short "who/when/note" list, newest first. */

.lightbox__meta {
  width: min(480px, 100%);
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: var(--space-3) var(--space-4);
  box-shadow: var(--shadow-sm);
}

.lightbox__meta[hidden] {
  display: none;
}

.lightbox__status-row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-sm);
  color: var(--text-muted);
}

.lightbox__status-row .status-pill {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: var(--space-1) var(--space-3);
  border-radius: var(--radius-full);
  border: 1px solid var(--border);
  font-size: var(--text-xs);
  color: var(--text-muted);
}

.lightbox__status-row .status-pill--approved {
  color: var(--success);
  border-color: var(--success);
}

.lightbox__status-row .status-pill--final {
  color: var(--accent);
  border-color: var(--accent);
}

.lightbox__history {
  list-style: none;
  margin: var(--space-3) 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  max-height: 160px;
  overflow-y: auto;
}

.lightbox__history-item {
  font-size: var(--text-xs);
  color: var(--text-muted);
  border-left: 2px solid var(--border);
  padding-left: var(--space-2);
}

.lightbox__history-item time {
  color: var(--text-faint);
  font-family: var(--font-mono);
}

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

.lightbox__close {
  position: absolute;
  top: var(--space-4);
  right: var(--space-4);
  background: var(--bg-elevated-2);
  border: 1px solid var(--border);
  color: var(--text);
  border-radius: var(--radius-full);
  width: 36px;
  height: 36px;
  cursor: pointer;
  transition: border-color var(--transition-fast), color var(--transition-fast);
}

.lightbox__close:hover {
  border-color: var(--accent);
  color: var(--accent);
}

.lightbox__close:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
