/* Cobalt Desk — the one ratified stylesheet (issue #173).
   Owner choice on #172, 2026-08-15: "this for me is the final version for
   now." Every screen this WP touches inherits this file unchanged — no
   screen may re-declare a token or a semantic class with different values.

   AC1 (extraction identity) is what this file answers: it must reproduce
   `design-a.html`'s shipped values exactly, byte-for-byte in computed
   style. It does NOT meet the WCAG AA projection floor at these values
   (15px body; several greys below 4.5:1/3:1) — that gap is real, known,
   and recorded rather than implied away. Deciding whether to accept the
   gap or raise the floor (and take the resulting visual delta) is issue
   #173's WP4, not this WP. See app/evidence-reports/173-cobalt-desk/
   projection-floor-measurements.json for the measured gap.

   The header keeps design-a's diagonal gradient — a solid header value
   was tried during this WP's first pass specifically to clear the floor,
   but that is exactly the redesign-under-cover-of-refactor AC1 forbids,
   so it reverts here alongside everything else. */

:root {
  --cd-color-bg: #f5f7fa;
  --cd-color-surface: #ffffff;

  --cd-color-text: #14171c;
  --cd-color-text-soft: #262b33;
  --cd-color-text-dim: #5c6473;
  --cd-color-text-faint: #9aa2b1;
  --cd-color-text-on-accent: #ffffff;

  --cd-color-accent: #1652f0;
  --cd-color-accent-hover: #0f45d1;
  --cd-color-accent-soft: #eef2fd;
  --cd-color-accent-softer: #eaedf2;
  --cd-color-accent-tint: #d3daf8;
  --cd-color-tab-hover-border: #b7c5f5;

  --cd-color-empty-text: #b7bfcc;
  --cd-color-empty-title: #7b869c;

  --cd-color-header-bg: linear-gradient(115deg, #0c1740 0%, #17297a 32%, #1652f0 68%, #3f7cff 100%);
  --cd-color-header-highlight: rgba(120, 180, 255, 0.35);
  --cd-color-header-highlight-soft: rgba(255, 255, 255, 0.16);
  --cd-color-header-chip-bg: rgba(255, 255, 255, 0.16);
  --cd-color-header-text: #ffffff;
  --cd-color-header-text-dim: rgba(255, 255, 255, 0.75);

  --cd-color-border: #e2e6ec;
  --cd-color-border-soft: #c7d0e6;
  --cd-color-border-input: #dbe0e8;
  --cd-color-error: #c23b3b;
  /* Issue #174: a translucent tint of --cd-color-error above (#c23b3b =
     rgb(194, 59, 59)) — not a new colour, the same derive-from-an-existing-
     token technique this file already uses for --cd-shadow-viewer/-picker. */
  --cd-color-error-soft: rgba(194, 59, 59, 0.08);
  /* Issue #182: a translucent tint of --cd-color-text (#14171c =
     rgb(20, 23, 28)) — same derive-from-an-existing-token technique as
     --cd-color-error-soft above, for the walkthrough's modal backdrop. */
  --cd-color-scrim: rgba(20, 23, 28, 0.55);

  --cd-shadow-viewer: 0 8px 28px rgba(20, 60, 140, 0.08);
  --cd-shadow-picker: 0 8px 24px rgba(20, 60, 140, 0.14);

  --cd-radius-pill: 999px;
  --cd-radius-lg: 14px;
  --cd-radius-md: 10px;
  --cd-radius-sm: 8px;
  --cd-radius-xs: 6px;

  --cd-font-family: "Segoe UI", Helvetica, Arial, sans-serif;
  --cd-font-size-base: 15px;

  /* Issue #160's consult-exception, ruled by Pim (PM) on #160: the
     ratified language shipped exactly one type token (the base above)
     and no scale, so no surface built after it could add a type
     hierarchy without either inventing a page-scoped token (barred —
     "inherited, not invented") or a raw literal (barred by AC3-style
     clauses across every surface). Added here, minimally, values only —
     --cd-font-size-base above is UNCHANGED and untouched by this. This
     is additive only: every existing Cobalt Desk screen's own literal
     font-size declarations are unaffected (out of scope here — #174's,
     not this item's), so nothing already shipped moves a pixel.
     --cd-font-size-md is the one size with a real constraint bound to
     it: any surface whose AA/legibility bar requires body-copy text to
     clear 16px (e.g. #160 AC9's "read from a couple of metres") uses
     this step for running prose, never a smaller one. */
  --cd-font-size-2xs: 12px;
  --cd-font-size-xs: 13px;
  --cd-font-size-sm: 14px;
  --cd-font-size-md: 16px;
  --cd-font-size-lg: 18px;
  --cd-font-size-2xl: 34px;
  --cd-font-size-display-h2: clamp(1.6rem, 2vw + 1rem, 2.2rem);
  --cd-font-size-display-hero: clamp(2.4rem, 4vw + 1rem, 4rem);

  --cd-ease: cubic-bezier(0.16, 1, 0.3, 1);
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  height: 100%;
  background: var(--cd-color-bg);
  color: var(--cd-color-text);
  font-family: var(--cd-font-family);
  font-size: var(--cd-font-size-base);
}

/* ---- app shell ---- */

.app {
  display: grid;
  grid-template-columns: 1fr 360px;
  height: 100vh;
}

.main {
  display: flex;
  flex-direction: column;
  min-height: 0;
  border-right: 1px solid var(--cd-color-border);
}

.main-top {
  position: relative;
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: 1.4rem 2rem;
  overflow: hidden;
  background: var(--cd-color-header-bg);
  color: var(--cd-color-header-text);
  z-index: 0;
}
.main-top::before {
  content: "";
  position: absolute;
  inset: 0;
  background:
    radial-gradient(38% 60% at 15% 20%, var(--cd-color-header-highlight-soft) 0%, rgba(255, 255, 255, 0) 70%),
    radial-gradient(30% 50% at 85% 100%, var(--cd-color-header-highlight) 0%, rgba(120, 180, 255, 0) 70%);
  pointer-events: none;
}
.main-top .brand, .main-top .idea, .main-top #budget-signal { position: relative; z-index: 1; }
.main-top .brand { font-weight: 700; font-size: 1.05rem; }
.main-top .idea { color: var(--cd-color-header-text-dim); font-size: 0.82rem; }
/* Scoped to .main-top (issue #174): this is the chip treatment for
   cobalt-desk.html's own diagonal-gradient header specifically — on a
   white .app-bar (every other screen), the standalone .budget-signal pill
   below is what applies instead. An unscoped #budget-signal would win
   that fight on specificity alone and paint every other screen's pill
   the same near-transparent chip meant for a dark background. */
.main-top #budget-signal {
  margin-left: auto;
  position: relative;
  z-index: 1;
  background: var(--cd-color-header-chip-bg);
  color: var(--cd-color-header-text);
  padding: 0.3rem 0.8rem;
  border-radius: var(--cd-radius-xs);
  font-size: 0.75rem;
  font-weight: 600;
  backdrop-filter: blur(6px);
}

.canvas {
  position: relative;
  flex: 1;
  overflow-y: auto;
  padding: 0 2rem 2rem;
  margin-top: -0.75rem;
  display: flex;
  flex-direction: column;
  z-index: 3;
}
.canvas::before,
.canvas::after {
  content: "";
  position: fixed;
  pointer-events: none;
  z-index: 0;
  border-radius: 50%;
  filter: blur(2px);
}
.canvas::before {
  top: -12vh;
  right: 2vw;
  width: 50vw;
  height: 50vw;
  max-width: 760px;
  max-height: 760px;
  background: radial-gradient(circle, rgba(22, 82, 240, 0.16) 0%, rgba(22, 82, 240, 0) 70%);
  animation: cd-drift-a 24s ease-in-out infinite;
}
.canvas::after {
  top: 10vh;
  left: -8vw;
  width: 34vw;
  height: 34vw;
  max-width: 520px;
  max-height: 520px;
  background: radial-gradient(circle, rgba(124, 58, 237, 0.12) 0%, rgba(124, 58, 237, 0) 70%);
  animation: cd-drift-b 30s ease-in-out infinite;
}

/* ---- artifact tabs + viewer ---- */

#slot-panels {
  position: relative;
  z-index: 1;
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
  padding-top: 1.4rem;
}
.tabs { display: flex; gap: 0.5rem; margin-bottom: 1rem; flex-wrap: wrap; }
.tab {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  font-family: inherit;
  background: var(--cd-color-surface);
  border: 1px solid var(--cd-color-border);
  color: var(--cd-color-text-dim);
  padding: 0.5rem 0.95rem;
  border-radius: var(--cd-radius-pill);
  font-size: 0.82rem;
  font-weight: 600;
  cursor: pointer;
  transition: transform 0.15s var(--cd-ease), border-color 0.2s, color 0.2s, background 0.2s;
}
.tab svg { width: 15px; height: 15px; flex: none; }
.tab:hover { transform: translateY(-1px); border-color: var(--cd-color-tab-hover-border); }
.tab[aria-selected="true"] { background: var(--cd-color-accent); color: var(--cd-color-text-on-accent); border-color: var(--cd-color-accent); }

.viewer {
  flex: 1;
  display: flex;
  flex-direction: column;
  background: var(--cd-color-surface);
  border: 1px solid var(--cd-color-border);
  border-radius: var(--cd-radius-lg);
  padding: 2rem;
  box-shadow: var(--cd-shadow-viewer);
  min-height: 0;
  animation: cd-fade-up 0.5s var(--cd-ease) both;
}
.viewer-label {
  font-size: 0.78rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--cd-color-accent);
  margin-bottom: 1rem;
}
.viewer-body { flex: 1; display: flex; flex-direction: column; min-height: 0; }
.viewer-body.is-out { animation: cd-viewer-out 0.15s cubic-bezier(0.4, 0, 1, 1) both; }
.viewer-body.is-in { animation: cd-viewer-in 0.2s var(--cd-ease) both; }
.viewer-label.is-out { animation: cd-viewer-out 0.15s cubic-bezier(0.4, 0, 1, 1) both; }
.viewer-label.is-in { animation: cd-viewer-in 0.2s var(--cd-ease) both; }
.viewer-text { font-size: 1rem; line-height: 1.6; white-space: pre-wrap; color: var(--cd-color-text-soft); overflow-y: auto; }
.viewer-meta { font-size: 0.78rem; color: var(--cd-color-text-faint); margin-top: 1rem; }

.empty-state {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1rem;
  color: var(--cd-color-empty-text);
  text-align: center;
}
.empty-state svg { width: 96px; height: 96px; color: var(--cd-color-accent-tint); }
.empty-state .empty-title { font-size: 1rem; font-weight: 600; color: var(--cd-color-empty-title); font-style: normal; }
.empty-state .empty-sub { font-size: 0.85rem; color: var(--cd-color-empty-text); max-width: 32ch; }

.viewer-image {
  max-width: 100%;
  max-height: 100%;
  border-radius: var(--cd-radius-md);
  display: block;
  margin: 0 auto;
  opacity: 0;
  animation: cd-fade-up 0.5s var(--cd-ease) forwards;
}

/* ---- crew chat ---- */

.chat-panel {
  display: flex;
  flex-direction: column;
  min-height: 0;
  background: var(--cd-color-surface);
}
.chat-head {
  padding: 1rem 1.4rem;
  font-weight: 700;
  font-size: 0.95rem;
  border-bottom: 1px solid var(--cd-color-border);
}
#thread { flex: 1; overflow-y: auto; padding: 1rem 1.4rem; }
.chat-hint {
  background: var(--cd-color-bg);
  border: 1px dashed var(--cd-color-border-soft);
  border-radius: var(--cd-radius-md);
  padding: 1rem 1.1rem;
  margin-bottom: 1rem;
  font-size: 0.84rem;
  color: var(--cd-color-text-dim);
  line-height: 1.5;
  animation: cd-fade-up 0.5s var(--cd-ease) both;
}
.msg { margin-bottom: 0.9rem; animation: cd-slide-in 0.3s var(--cd-ease) both; }
.msg .who { font-size: 0.72rem; color: var(--cd-color-text-dim); margin-bottom: 0.2rem; font-weight: 600; }
.msg .bubble {
  font-size: 0.88rem;
  background: var(--cd-color-bg);
  border-radius: var(--cd-radius-sm);
  padding: 0.55rem 0.8rem;
  display: inline-block;
}
.msg.user .bubble { background: var(--cd-color-accent); color: var(--cd-color-text-on-accent); }
.msg.system .bubble { background: transparent; font-style: italic; color: var(--cd-color-text-dim); padding: 0; }
/* Demo feedback item 4 — a teammate's own message in the now-shared
   thread: same "user" column/shape as "You", the lighter accent-soft
   tint (same language slot-badge.filled already uses) is what tells the
   two apart at a glance. */
.msg.teammate .bubble { background: var(--cd-color-accent-soft); color: var(--cd-color-accent); }
.msg.teammate .who { color: var(--cd-color-accent); }
/* Demo feedback (@ a teammate): mention-highlight marks a message
   @teammate for everyone in the thread; mention-me is the stronger accent
   on the one message actually addressed to ME — a left border, no
   ping/sound, visual only. */
.msg.mention-highlight .bubble { border: 1px solid var(--cd-color-accent); }
.msg.mention-me .bubble { border-left: 3px solid var(--cd-color-accent); padding-left: calc(0.8rem - 2px); }
.proposal-actions { display: flex; gap: 0.5rem; margin-top: 0.5rem; }
.proposal-actions button {
  font-family: inherit;
  border: none;
  border-radius: var(--cd-radius-xs);
  padding: 0.35rem 0.9rem;
  cursor: pointer;
  font-size: 0.8rem;
  font-weight: 600;
  transition: transform 0.15s var(--cd-ease);
}
.proposal-actions button:active { transform: scale(0.96); }
.btn-confirm { background: var(--cd-color-accent); color: var(--cd-color-text-on-accent); }
.btn-decline { background: var(--cd-color-accent-softer); color: var(--cd-color-text); }

.composer-row {
  position: relative;
  border-top: 1px solid var(--cd-color-border);
  padding: 1rem 1.4rem;
}
#mention-picker {
  position: absolute;
  bottom: 100%;
  left: 1.4rem;
  right: 1.4rem;
  background: var(--cd-color-surface);
  border: 1px solid var(--cd-color-border);
  border-radius: var(--cd-radius-sm);
  box-shadow: var(--cd-shadow-picker);
  max-height: 200px;
  overflow-y: auto;
}
.mention-picker-item { padding: 0.5rem 0.8rem; display: flex; justify-content: space-between; cursor: pointer; font-size: 0.86rem; }
.mention-picker-item.highlighted, .mention-picker-item:hover { background: var(--cd-color-accent-soft); }
.mention-picker-item .slug { color: var(--cd-color-text-dim); font-size: 0.78rem; }
/* Demo feedback (@ a teammate): a divider ahead of the picker's teammate
   rows — same list, roles first, so an addressable agent is never buried
   below a long team roster. */
.mention-picker-divider {
  padding: 0.35rem 0.8rem;
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--cd-color-text-faint);
  border-top: 1px solid var(--cd-color-border);
}
.mention-picker-item.teammate .slug { color: var(--cd-color-accent); }
/* Issue #184: the roster is unreachable or came back empty — same register
   as the budget pill's "unavailable" state, in the picker itself since
   that's where the participant is looking. */
.mention-picker-status {
  padding: 0.6rem 0.7rem;
  font-size: 0.8rem;
  font-style: italic;
  color: var(--cd-color-text-dim);
}
#composer-input {
  width: 100%;
  font-family: inherit;
  font-size: 0.88rem;
  padding: 0.6rem 0.85rem;
  border: 1px solid var(--cd-color-border-input);
  border-radius: var(--cd-radius-sm);
  margin-bottom: 0.6rem;
  transition: box-shadow 0.2s var(--cd-ease), border-color 0.2s;
  /* Issue #174: the new shared `input[type="text"]` rule below sets a
     colour this element never had (design-a.html's own #composer-input
     never declared one either, so it painted browser-default black) —
     `revert` is the one value that un-does an author rule and falls back
     to the UA default, which is exactly what AC1's extraction-identity
     check (cobalt-desk-ui.js) pins byte-for-byte against design-a.html. */
  color: revert;
}
#composer-input:focus {
  outline: none;
  border-color: var(--cd-color-accent);
  box-shadow: 0 0 0 3px rgba(22, 82, 240, 0.14);
}
.composer-controls { display: flex; align-items: center; gap: 0.5rem; }
/* Issue #174 (QA FAIL, round 1): live.html/substrate-vanilla.html also
   carry class="model-select" on this control, but only the ID was ever
   styled here — the class was inert. Both selectors, same declaration
   block, so "semantic class in the one stylesheet" (AC1) is genuinely
   true, not just true-by-ID-coincidence. */
#model-select, .model-select {
  flex: 1;
  font-family: inherit;
  font-size: 0.78rem;
  padding: 0.45rem 0.6rem;
  border: 1px solid var(--cd-color-border-input);
  border-radius: var(--cd-radius-xs);
  background: var(--cd-color-surface);
}
#composer-button {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-family: inherit;
  font-size: 0.82rem;
  font-weight: 600;
  padding: 0.45rem 1rem;
  border: none;
  border-radius: var(--cd-radius-xs);
  background: var(--cd-color-accent);
  color: var(--cd-color-text-on-accent);
  cursor: pointer;
  transition: transform 0.15s var(--cd-ease), background 0.15s;
}
#composer-button:hover { background: var(--cd-color-accent-hover); }
#composer-button:active { transform: scale(0.96); }
#composer-button svg { width: 14px; height: 14px; transition: transform 0.2s var(--cd-ease); }
#composer-button:hover svg { transform: translateX(2px); }
#status { font-size: 0.75rem; color: var(--cd-color-text-faint); margin-top: 0.5rem; }
#status[data-kind="error"] { color: var(--cd-color-error); }

/* ---- join screen (app/public/index.html) ---- */

.join-shell { max-width: 480px; margin: 4rem auto 0; padding: 1.25rem; }
.join-panel {
  background: var(--cd-color-surface);
  border: 1px solid var(--cd-color-border-soft);
  border-radius: var(--cd-radius-lg);
  box-shadow: var(--cd-shadow-viewer);
  padding: 1.75rem;
}
.join-title { margin: 0 0 0.4rem 0; font-size: 1.4rem; color: var(--cd-color-text); }
.join-lede { margin: 0 0 1.25rem 0; font-size: 0.9rem; line-height: 1.5; color: var(--cd-color-text-dim); }
.join-field {
  width: 100%;
  margin-bottom: 0.75rem;
  font-family: inherit;
  font-size: 0.9rem;
  padding: 0.6rem 0.85rem;
  border: 1px solid var(--cd-color-border);
  border-radius: var(--cd-radius-sm);
  color: var(--cd-color-text);
}
.join-field:focus {
  outline: none;
  border-color: var(--cd-color-accent);
  box-shadow: 0 0 0 3px rgba(22, 82, 240, 0.14);
}
.join-error { color: var(--cd-color-error); font-size: 0.8rem; margin-bottom: 0.75rem; display: none; }

/* ---- queue screen (app/public/team/queue.html) — demo feedback item 1 ---- */
.queue-panel { text-align: center; }
.queue-spinner {
  width: 2.5rem;
  height: 2.5rem;
  margin: 0 auto 1.25rem;
  border: 3px solid var(--cd-color-border);
  border-top-color: var(--cd-color-accent);
  border-radius: 50%;
  animation: cd-queue-spin 0.9s linear infinite;
}
@keyframes cd-queue-spin {
  to { transform: rotate(360deg); }
}
@media (max-width: 768px) {
  .join-shell { margin-top: 1.5rem; }
  /* Below 16px, iOS Safari zooms the page on focus. */
  .join-field { font-size: 16px; }
}
.join-submit {
  width: 100%;
  font-family: inherit;
  font-size: 0.92rem;
  font-weight: 600;
  padding: 0.6rem 1rem;
  border: none;
  border-radius: var(--cd-radius-xs);
  background: var(--cd-color-accent);
  color: var(--cd-color-text-on-accent);
  cursor: pointer;
  transition: background 0.15s var(--cd-ease);
}
.join-submit:hover { background: var(--cd-color-accent-hover); }
.join-header {
  display: flex;
  align-items: center;
  padding: 0.85rem 1.25rem;
  background: var(--cd-color-surface);
  border-bottom: 1px solid var(--cd-color-border-soft);
}
.join-header .brand { font-weight: 700; font-size: 0.95rem; color: var(--cd-color-text); }

/* ---- shared cross-screen components (issue #174, Epic M WP3) ----
   Every one of these was previously defined only in shared.css (issue
   #70), which the dashboard, both demo-picker/variant screens and the
   live surface loaded instead of this file. Ported here, token-for-token
   equivalent, so those screens can retire shared.css entirely and speak
   the one ratified language — no new colours or type sizes, every value
   below resolves to a --cd-* token already declared at the top of this
   file. */

header.app-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.85rem 1.25rem;
  background: var(--cd-color-surface);
  border-bottom: 1px solid var(--cd-color-border);
}
header.app-bar .brand { font-weight: 700; font-size: 0.95rem; color: var(--cd-color-text); }
header.app-bar .brand .variant-tag { color: var(--cd-color-text-dim); font-weight: 500; margin-left: 0.5rem; }
header.app-bar nav { display: flex; align-items: center; gap: 1rem; }
header.app-bar nav a {
  color: var(--cd-color-text-dim);
  text-decoration: none;
  font-size: 0.82rem;
  border-bottom: 1px solid transparent;
}
header.app-bar nav a:hover { color: var(--cd-color-accent); border-bottom-color: var(--cd-color-accent); }
header.app-bar nav a.current { color: var(--cd-color-text); font-weight: 600; }

/* The ambient budget pill (issue #70's non-negotiable: exactly one, this
   footprint) for every screen that is NOT cobalt-desk.html's own dark
   gradient header — see the .main-top-scoped #budget-signal chip above. */
.budget-signal {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  font-size: 0.76rem;
  font-weight: 600;
  padding: 0.3rem 0.65rem;
  border-radius: var(--cd-radius-pill);
  background: var(--cd-color-accent-softer);
  border: 1px solid var(--cd-color-border);
  color: var(--cd-color-text-dim);
}
.budget-signal[data-state="revealed"] { background: var(--cd-color-accent-soft); border-color: var(--cd-color-accent); color: var(--cd-color-accent); }
.budget-signal .dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
.budget-signal.just-revealed {
  transform: scale(1.18);
  box-shadow: 0 0 0 4px var(--cd-color-accent-soft);
  transition: transform 2.2s ease-out, box-shadow 2.2s ease-out;
}
.budget-signal[data-state="revealed"]:not(.just-revealed) {
  transform: scale(1);
  box-shadow: 0 0 0 0 transparent;
  transition: transform 2.2s ease-out, box-shadow 2.2s ease-out;
}
@media (prefers-reduced-motion: reduce) {
  .budget-signal.just-revealed,
  .budget-signal[data-state="revealed"]:not(.just-revealed) {
    transition: none;
    transform: none;
    box-shadow: none;
  }
}

/* The thin activity line (issue #74) under the product card. */
.activity-line { margin-top: 0.6rem; font-size: 0.76rem; color: var(--cd-color-text-dim); }

/* The hero product card (issue #76) — the largest object on screen. */
.product-card {
  background: var(--cd-color-surface);
  border: 1px solid var(--cd-color-border);
  border-radius: var(--cd-radius-lg);
  box-shadow: var(--cd-shadow-viewer);
  padding: 1.75rem;
}
.product-card .hero-image {
  width: 100%;
  aspect-ratio: 16 / 9;
  border-radius: var(--cd-radius-md);
  background: repeating-linear-gradient(45deg, var(--cd-color-bg), var(--cd-color-bg) 10px, var(--cd-color-border) 10px, var(--cd-color-border) 20px);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--cd-color-text-dim);
  font-size: 0.85rem;
  margin-bottom: 1.25rem;
}
.product-card h1 { margin: 0 0 0.4rem 0; font-size: 1.9rem; letter-spacing: -0.01em; color: var(--cd-color-text); }
.product-card .card-title { margin-bottom: 1rem; }
.product-card .tagline { margin: 0; color: var(--cd-color-text-dim); font-size: 1.05rem; }

/* The generic panel shell (crew rail, credential card, dashboard card…). */
.panel {
  background: var(--cd-color-surface);
  border: 1px solid var(--cd-color-border);
  border-radius: var(--cd-radius-lg);
  box-shadow: var(--cd-shadow-viewer);
  display: flex;
  flex-direction: column;
}
.panel-header {
  padding: 0.7rem 0.9rem;
  border-bottom: 1px solid var(--cd-color-border);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
}
.panel-header h2, .panel-header h3 { margin: 0; font-size: 0.88rem; font-weight: 600; color: var(--cd-color-text); }

/* Generic form controls, everywhere they appear without one of the more
   specific #composer-input / #model-select / #composer-button rules
   above already covering them (those win on specificity, unchanged). */
input[type="text"] {
  font-family: inherit;
  font-size: 0.88rem;
  padding: 0.6rem 0.85rem;
  border: 1px solid var(--cd-color-border-input);
  border-radius: var(--cd-radius-sm);
  background: var(--cd-color-surface);
  color: var(--cd-color-text);
}
input[type="text"]:focus {
  outline: none;
  border-color: var(--cd-color-accent);
  box-shadow: 0 0 0 3px rgba(22, 82, 240, 0.14);
}
button {
  font-family: inherit;
  font-size: 0.86rem;
  font-weight: 600;
  padding: 0.5rem 1rem;
  border-radius: var(--cd-radius-xs);
  border: 1px solid var(--cd-color-accent);
  background: var(--cd-color-accent);
  color: var(--cd-color-text-on-accent);
  cursor: pointer;
  transition: background 0.15s var(--cd-ease);
}
button:hover { background: var(--cd-color-accent-hover); }
button:disabled { opacity: 0.5; cursor: not-allowed; }

.status-line { font-size: 0.78rem; color: var(--cd-color-text-dim); padding: 0.5rem 0.9rem; border-top: 1px solid var(--cd-color-border); }
.alert-status-text { font-size: 0.8rem; color: var(--cd-color-text-dim); }

/* The ONE composer row — live.html and its variants use this class name
   (cobalt-desk.html's own single-page composer uses .composer-row above;
   both need position:relative so #mention-picker/.mention-picker anchors
   to the row it sits above, not to some further-up ancestor). */
.composer { display: flex; gap: 0.5rem; padding: 0.75rem 0.9rem; border-top: 1px solid var(--cd-color-border); position: relative; }
.composer input[type="text"] { flex: 1; }
.thread { flex: 1; overflow-y: auto; padding: 1rem 1.4rem; }

/* A confirmed generation's inline result image (issue #158) — the ONE
   place a hero image can render outside the artifact viewer/slot panel,
   since generateArtifact appends it straight into the chat bubble on
   every screen, cobalt-desk.html included. */
.proposal-result-image { display: block; width: 100%; max-width: 260px; margin-top: 0.5rem; border-radius: var(--cd-radius-sm); }

/* The five-slot product-card tabs (issue #76, extended by #196) — the
   MULTI-panel pattern the live surface and its variants use (each panel
   stays mounted, toggled by .active), distinct from cobalt-desk.html's
   own single-viewer swap (.tabs/.viewer above) — issue #197's sandboxed
   preview needs a real persistent panel per slot to mount/unmount an
   iframe against, which a swap-in-place viewer can't offer. */
.slot-tabs { display: flex; gap: 0.35rem; margin-bottom: 1rem; border-bottom: 1px solid var(--cd-color-border); flex-wrap: wrap; }
.slot-tab {
  background: none;
  border: none;
  border-bottom: 2px solid transparent;
  border-radius: 0;
  color: var(--cd-color-text-dim);
  font-weight: 600;
  font-size: 0.82rem;
  padding: 0.55rem 0.2rem;
  cursor: pointer;
  font-family: inherit;
}
.slot-tab:hover { color: var(--cd-color-text); }
.slot-tab.active { color: var(--cd-color-accent); border-bottom-color: var(--cd-color-accent); }
.slot-panel { display: none; }
.slot-panel.active { display: block; }
.slot-panel .slot-empty { color: var(--cd-color-empty-text); font-style: italic; font-size: 0.9rem; }
.slot-panel .slot-content { font-size: 0.95rem; white-space: pre-wrap; color: var(--cd-color-text-soft); }
.slot-panel .slot-meta { margin-top: 0.75rem; font-size: 0.76rem; color: var(--cd-color-text-faint); }
.slot-panel .slot-image { width: 100%; border-radius: var(--cd-radius-md); }

/* Structured artifact rendering (2026-08-18) — market_snapshot/
   product_concept/campaign hold named JSON fields now (slots.js's
   StructuredArtifactError enforces the shape server-side), rendered here as
   real fields/chips/bars instead of one text blob. Same component shapes as
   the standalone vision mockup (artifact-vision-mockup.html), reskinned
   onto this surface's own --cd-* tokens rather than that file's inline
   ones, so it reads as part of THIS app, not a foreign page. */
.structured-field { margin-bottom: 1.1rem; }
.structured-field:last-child { margin-bottom: 0; }
.structured-label {
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--cd-color-text-faint);
  margin-bottom: 0.4rem;
}
.structured-value { font-size: 0.92rem; line-height: 1.5; color: var(--cd-color-text-soft); }

.chips { display: flex; flex-wrap: wrap; gap: 0.4rem; }
.chip {
  background: var(--cd-color-accent-soft);
  color: var(--cd-color-text);
  font-size: 0.78rem;
  font-weight: 600;
  padding: 0.3rem 0.65rem;
  border-radius: var(--cd-radius-pill);
}

.bars { display: flex; align-items: flex-end; gap: 0.85rem; height: 110px; padding-top: 0.5rem; border-bottom: 1px solid var(--cd-color-border-soft); }
.bar-col { display: flex; flex-direction: column; align-items: center; gap: 0.35rem; flex: 1; height: 100%; justify-content: flex-end; }
.bar-value { font-size: 0.72rem; font-weight: 700; color: var(--cd-color-text-dim); font-variant-numeric: tabular-nums; }
.bar { width: 100%; max-width: 36px; background: var(--cd-color-accent); border-radius: var(--cd-radius-xs) var(--cd-radius-xs) 0 0; }
.bar-ticks { display: flex; gap: 0.85rem; margin-top: 0.4rem; }
.bar-tick { flex: 1; text-align: center; font-size: 0.7rem; color: var(--cd-color-text-faint); }
.chart-caption { font-size: 0.72rem; color: var(--cd-color-text-faint); margin-top: 0.6rem; font-style: italic; }

.roadmap { display: flex; align-items: flex-start; gap: 0; }
.stop { flex: 1; text-align: center; position: relative; }
.stop .dot { width: 10px; height: 10px; border-radius: 50%; background: var(--cd-color-accent); margin: 0 auto 0.5rem; position: relative; z-index: 1; }
.stop::before { content: ""; position: absolute; top: 4px; left: -50%; width: 100%; height: 2px; background: var(--cd-color-border); }
.stop:first-child::before { display: none; }
.stop .stop-label { font-size: 0.76rem; font-weight: 700; color: var(--cd-color-text); margin-bottom: 0.1rem; }
.stop .stop-sub { font-size: 0.7rem; color: var(--cd-color-text-faint); }

.rollout-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.6rem; }
.rollout-item { display: flex; gap: 0.6rem; align-items: flex-start; }
.rollout-phase {
  flex-shrink: 0;
  width: 22px; height: 22px; border-radius: 50%;
  background: var(--cd-color-accent-soft);
  color: var(--cd-color-accent);
  font-size: 0.7rem; font-weight: 700;
  display: flex; align-items: center; justify-content: center;
}
.rollout-text .region { font-size: 0.88rem; font-weight: 600; color: var(--cd-color-text); }
.rollout-text .timing { font-size: 0.74rem; color: var(--cd-color-text-faint); }

/* The sandboxed web-page preview (issue #197) — preview|source toggle
   plus the iframe/source pair webPagePreview.js mounts. */
.web-page-preview-toggle { display: flex; gap: 0.35rem; margin-bottom: 0.6rem; }
.web-page-preview-toggle-btn {
  background: none;
  border: 1px solid var(--cd-color-border);
  border-radius: var(--cd-radius-xs);
  color: var(--cd-color-text-dim);
  font-size: 0.76rem;
  font-weight: 600;
  padding: 0.3rem 0.6rem;
  cursor: pointer;
  font-family: inherit;
}
.web-page-preview-toggle-btn.active { color: var(--cd-color-accent); border-color: var(--cd-color-accent); }
.web-page-preview-frame { width: 100%; height: 420px; border: 1px solid var(--cd-color-border); border-radius: var(--cd-radius-md); background: var(--cd-color-surface); }
.web-page-preview-source { font-size: 0.8rem; white-space: pre-wrap; word-break: break-word; max-height: 420px; overflow-y: auto; background: var(--cd-color-surface); border: 1px solid var(--cd-color-border); border-radius: var(--cd-radius-md); padding: 0.75rem; }

/* ---- facilitator dashboard (issues #87 et al, ported here by #174) ----
   Genuinely single-screen furniture (a table, badges, an alert row, a
   modal), so it stays its own section rather than pretending to be
   cross-screen — but centralised in the one ratified stylesheet like
   everything else, never a local <style> block (#173's WP2 grep). */

table.dashboard { width: 100%; border-collapse: collapse; margin-top: 1rem; }
table.dashboard th, table.dashboard td { text-align: left; padding: 0.5rem 0.75rem; border-bottom: 1px solid var(--cd-color-border); font-size: 0.85rem; }
table.dashboard th { color: var(--cd-color-text-dim); font-weight: 600; font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.02em; }
.spend-unknown { color: var(--cd-color-text-dim); font-style: italic; }
.spend-partial .unpriced-marker { color: var(--cd-color-error); font-weight: 600; }
.slots-cell { display: flex; gap: 0.35rem; flex-wrap: wrap; }
.slot-badge { font-size: 0.72rem; padding: 0.15rem 0.5rem; border-radius: var(--cd-radius-pill); border: 1px solid var(--cd-color-border); }
/* Cobalt Desk's own stated idea (cobalt-desk.html) is "one confident
   blue" — no separate green/red traffic-light semantic exists in this
   palette, so a "filled"/positive state reuses the one accent rather
   than introducing a colour the ratified language doesn't have. */
.slot-badge.filled { background: var(--cd-color-accent-soft); border-color: var(--cd-color-accent); color: var(--cd-color-accent); }
.slot-badge.empty { color: var(--cd-color-text-dim); }
/* Dashboard-scoped: distinct from the artifact viewer's own .empty-state
   (the flex/icon empty slot treatment above) — this is a plain text row. */
.dashboard-empty-state { color: var(--cd-color-text-dim); padding: 1.5rem 0; }
.stale-banner { display: none; background: var(--cd-color-error-soft); color: var(--cd-color-error); border-radius: var(--cd-radius-md); padding: 0.6rem 0.85rem; font-size: 0.8rem; margin-bottom: 0.75rem; }
.room-controls { display: flex; gap: 1.5rem; align-items: center; flex-wrap: wrap; margin-bottom: 1.25rem; padding-bottom: 1.25rem; border-bottom: 1px solid var(--cd-color-border); }
.room-control { display: flex; align-items: center; gap: 0.6rem; }
.room-control .status-label { font-size: 0.8rem; color: var(--cd-color-text-dim); min-width: 9.5rem; }
.alerts { display: flex; flex-direction: column; gap: 0.5rem; margin-bottom: 1.25rem; }
.alert-row { display: flex; align-items: center; justify-content: space-between; gap: 1rem; background: var(--cd-color-error-soft); color: var(--cd-color-error); border-radius: var(--cd-radius-md); padding: 0.55rem 0.85rem; font-size: 0.85rem; font-weight: 600; }
.alert-row button { flex-shrink: 0; }
.session-creation-backdrop { display: none; position: fixed; inset: 0; background: rgba(0, 0, 0, 0.4); align-items: center; justify-content: center; z-index: 10; }
.session-creation-panel { background: var(--cd-color-surface); border-radius: var(--cd-radius-md); padding: 1.5rem; width: 100%; max-width: 360px; }
.session-creation-panel h3 { margin: 0 0 1rem 0; font-size: 1.05rem; color: var(--cd-color-text); }
.session-creation-panel label { display: block; font-size: 0.8rem; color: var(--cd-color-text-dim); margin: 0.75rem 0 0.25rem 0; }
.session-creation-panel input { width: 100%; }
.session-creation-actions { display: flex; gap: 0.6rem; margin-top: 1.25rem; }
.session-creation-error { color: var(--cd-color-error); font-size: 0.8rem; margin-top: 0.75rem; display: none; }
.session-info { display: none; align-items: center; gap: 1.5rem; flex-wrap: wrap; font-size: 0.85rem; color: var(--cd-color-text-dim); margin-bottom: 1.25rem; }
.session-info strong { color: inherit; font-weight: 600; }
.session-info .team-count-mismatch { color: var(--cd-color-error); font-weight: 600; }

/* The credential gate + its own field/error, and the "full width" form
   controls a modal/gate needs that the composer's fixed-width rules
   (above) don't cover. */
.credential-panel { padding: 1.75rem; max-width: 480px; }
.credential-panel input[type="password"] { width: 100%; margin-bottom: 0.75rem; }
.credential-panel .field-error { color: var(--cd-color-error); font-size: 0.8rem; margin-bottom: 0.75rem; display: none; }
.credential-panel button[type="submit"] { width: 100%; }
.dashboard-panel { padding: 1.5rem; display: none; }

/* Demo feedback item 3 — facilitator health check card. Reuses the same
   "one confident blue"/error-soft language slot-badge and stale-banner
   already established above rather than a new red/green pair. */
.health-check-card { border: 1px solid var(--cd-color-border); border-radius: var(--cd-radius-md); padding: 1rem 1.25rem; margin-bottom: 1.25rem; }
.health-check-header { display: flex; align-items: center; gap: 1rem; flex-wrap: wrap; }
.health-check-title { font-weight: 600; font-size: 0.9rem; margin-right: auto; }
.health-check-deep-label { display: flex; align-items: center; gap: 0.4rem; font-size: 0.78rem; color: var(--cd-color-text-dim); }
.health-check-results { display: flex; flex-direction: column; gap: 0.4rem; margin-top: 0.9rem; }
.health-check-row { display: grid; grid-template-columns: 8rem 4rem 4.5rem 1fr; align-items: center; gap: 0.6rem; font-size: 0.82rem; }
.health-check-name { font-weight: 600; }
.health-check-pill { font-size: 0.72rem; padding: 0.15rem 0.5rem; border-radius: var(--cd-radius-pill); border: 1px solid var(--cd-color-border); text-align: center; width: fit-content; }
.health-check-pill.health-ok { background: var(--cd-color-accent-soft); border-color: var(--cd-color-accent); color: var(--cd-color-accent); }
.health-check-pill.health-fail { background: var(--cd-color-error-soft); border-color: var(--cd-color-error); color: var(--cd-color-error); }
.health-check-latency { color: var(--cd-color-text-dim); }
.health-check-detail { color: var(--cd-color-text-dim); overflow-wrap: anywhere; }

/* ---- per-screen layout wrappers (issue #174) ----
   Geometry only (widths, gaps, columns) — never a colour or a type size,
   so none of this is what AC3-style "zero literals" clauses police. Kept
   here rather than duplicated per file so the one-stylesheet rule
   (#173's own header comment: "no screen may re-declare a semantic class
   with different values") holds for layout too, not just colour/type. */

.landing h1 { font-size: 1.4rem; margin: 0 0 0.4rem 0; color: var(--cd-color-text); }
.landing p.lede { color: var(--cd-color-text-dim); max-width: 62ch; }
.variant-grid-links { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 1rem; margin-top: 1.5rem; }
.variant-card { padding: 1rem 1.1rem; }
.variant-card h3 { margin: 0 0 0.3rem 0; color: var(--cd-color-text); }
.variant-card p { margin: 0 0 0.8rem 0; color: var(--cd-color-text-dim); font-size: 0.86rem; }
.variant-card a.enter { font-weight: 600; text-decoration: none; color: var(--cd-color-accent); }

/* Variant A — card-dominant, collapsed rail */
.layout-a { max-width: 1180px; margin: 0 auto; padding: 1.25rem; display: flex; gap: 1.25rem; align-items: flex-start; }
.layout-a .product-card { flex: 1 1 auto; }
.layout-a .rail {
  width: 340px;
  flex: 0 0 auto;
  height: calc(100vh - 8rem);
  display: flex;
  flex-direction: column;
}
.layout-a .rail .thread { max-height: none; }

/* Variant B — card-and-column side by side, equally weighted */
.layout-b { max-width: 1280px; margin: 0 auto; padding: 1.25rem; display: grid; grid-template-columns: 1.1fr 1fr; gap: 1.25rem; align-items: start; }
.layout-b .column { height: calc(100vh - 8rem); display: flex; flex-direction: column; }

/* Variant C — card on top, stream beneath, single column */
.layout-c { max-width: 760px; margin: 0 auto; padding: 1.25rem; display: flex; flex-direction: column; gap: 1.25rem; }
.layout-c .panel { height: 480px; }

/* The hardened live surface (issue #72) — chosen-variant-A ratio, widened. */
.layout-live { max-width: 1440px; margin: 0 auto; padding: 0.85rem; display: flex; gap: 1rem; align-items: flex-start; }
.layout-live .product-card {
  flex: 1 1 auto;
  height: calc(100vh - 6rem);
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.layout-live .rail {
  width: 460px;
  flex: 0 0 auto;
  height: calc(100vh - 6rem);
  display: flex;
  flex-direction: column;
}
.layout-live .rail .thread { max-height: none; }
/* Scoped to .layout-live (issue #174): cobalt-desk.html's own #slot-panels
   rule above (its single-viewer swap layout) is untouched by this — the
   live surface's multi-panel #slot-panels just needs its own scroll. */
.layout-live #slot-panels { flex: 1; overflow-y: auto; }

/* Phone chat-only view (demo Q&A, 2026-08-19): a team with fewer laptops
   than people can still have phone participants contribute — below this
   width the artifact card is hidden (DOM stays intact, so the slot-building
   loop in live.html's inline script and the desktop-viewport evidence
   scripts that assert on #slot-tabs/#slot-panels are unaffected) and the
   chat rail becomes the whole screen. live.html's own script pairs this
   with a matching matchMedia check that skips the 4s artifact poll while
   the panel is hidden. */
@media (max-width: 768px) {
  .layout-live .product-card { display: none; }
  .layout-live {
    display: block;
    padding: 0;
    max-width: none;
  }
  .layout-live .rail {
    width: auto;
    flex: none;
    /* dvh, not vh: tracks the visual viewport, so the composer stays above
       an open mobile keyboard instead of being pushed off-screen. */
    height: 100dvh;
  }
  header.app-bar {
    flex-wrap: wrap;
    padding: 0.6rem 0.85rem;
  }
  /* The two nav links lead to desktop-only surfaces (the demo-variant
     picker, a new-tab mockup) — dropped here rather than wrapped, to keep
     the bar to one compact row on a phone. */
  header.app-bar nav a { display: none; }
  .composer { flex-wrap: wrap; }
  #model-select, .model-select {
    flex: 0 1 auto;
    max-width: 45%;
  }
  #composer-input {
    flex: 1 1 auto;
    /* Below 16px, iOS Safari zooms the page on focus. */
    font-size: 16px;
  }
  #mention-picker { max-width: calc(100vw - 2rem); }
  /* A narrow phone rail makes an unbroken long string (a pasted URL, a
     token) in a chat bubble much more likely to force horizontal scroll
     than the 460px desktop rail ever did — .msg .bubble has no
     overflow-wrap of its own at any width. */
  .msg .bubble { max-width: 100%; overflow-wrap: anywhere; }
}

/* A page-width shell for a single centred panel/copy block (dashboard,
   the team-surface demo index) — geometry only, same reasoning as above. */
.page-shell { max-width: 960px; margin: 0 auto; padding: 1.25rem; }
.page-shell.narrow { max-width: 480px; }

/* The team-surface demo index (issue #70) — one-off page furniture, not
   reusable elsewhere, but still centralised here rather than in a local
   <style> block: #173's own WP2 grep (re-run whole-tree by this WP)
   forbids an inline <style> tag on any screen, not just literals in one. */
.demo-index-shell { max-width: 1180px; margin: 0 auto; padding: 1.25rem; }
.demo-index-note { margin-top: 2rem; }
.demo-index-hint { margin-top: 0.5rem; color: var(--cd-color-text-dim); font-size: 0.8rem; }

/* ---- first-entry walkthrough (issue #182) ----
   A self-contained overlay — never the real page's own #thread/#slot-panels,
   its own isolated DOM so it structurally cannot touch real state. Reuses
   the real .msg/.bubble/.proposal-actions/.slot-empty/.slot-content/
   .slot-meta/.slot-tab classes above for the mini stage, so it speaks
   whatever language THIS stylesheet ships without restating anything —
   only the walkthrough's own new chrome (backdrop, panel, header) is here.
   Ported from shared.css (QA round 2) now that #174 has landed and
   live.html loads this file instead — every literal from that version is
   now a token declared above, and the 16px chrome sizes below resolve to
   --cd-font-size-md rather than a bare literal. */
.walkthrough-backdrop {
  position: fixed;
  inset: 0;
  z-index: 50;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--cd-color-scrim);
  padding: 1.5rem;
}
.walkthrough-panel {
  background: var(--cd-color-surface);
  border-radius: var(--cd-radius-md);
  box-shadow: var(--cd-shadow-picker);
  width: 100%;
  max-width: 720px;
  max-height: 90vh;
  overflow-y: auto;
  padding: 1.75rem;
}
.walkthrough-panel:focus { outline: none; }
.walkthrough-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: 1.25rem;
}
.walkthrough-title { font-size: var(--cd-font-size-md); font-weight: 700; color: var(--cd-color-text); }
.walkthrough-btn {
  font-family: inherit;
  font-size: var(--cd-font-size-md);
  font-weight: 600;
  padding: 0.4rem 0.9rem;
  border: 1px solid var(--cd-color-border);
  border-radius: var(--cd-radius-pill);
  background: var(--cd-color-surface);
  color: var(--cd-color-text-dim);
  cursor: pointer;
}
.walkthrough-btn:hover { color: var(--cd-color-text); border-color: var(--cd-color-text-dim); }
.walkthrough-stage {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1.25rem;
}
.walkthrough-mini-card {
  background: var(--cd-color-bg);
  border: 1px solid var(--cd-color-border);
  border-radius: var(--cd-radius-sm);
  padding: 1rem;
}
.walkthrough-mini-tabs { display: flex; gap: 0.35rem; margin-bottom: 0.75rem; }
.walkthrough-mini-slot { min-height: 5rem; }
.walkthrough-mini-chat {
  background: var(--cd-color-bg);
  border: 1px solid var(--cd-color-border);
  border-radius: var(--cd-radius-sm);
  padding: 1rem;
  display: flex;
  flex-direction: column;
}
.walkthrough-mini-thread { min-height: 12rem; max-height: 18rem; overflow-y: auto; }
.walkthrough-generating { color: var(--cd-color-accent); font-style: normal; animation: walkthrough-pulse 1.2s ease-in-out infinite; }
/* The closing narrator note — its own class rather than reusing
   .msg.system directly, so it stays correct regardless of what register
   that class carries on any given stylesheet. */
.walkthrough-note .bubble { background: transparent; font-style: italic; color: var(--cd-color-text-dim); padding: 0; }
@keyframes walkthrough-pulse {
  0%, 100% { opacity: 0.55; }
  50% { opacity: 1; }
}
@media (max-width: 640px) {
  .walkthrough-stage { grid-template-columns: 1fr; }
}

/* ---- motion ---- */

@keyframes cd-fade-up {
  from { opacity: 0; transform: translateY(14px); }
  to { opacity: 1; transform: translateY(0); }
}
@keyframes cd-drift-a {
  0%, 100% { transform: translate(0, 0) scale(1); }
  50% { transform: translate(-4vw, 3vh) scale(1.08); }
}
@keyframes cd-drift-b {
  0%, 100% { transform: translate(0, 0) scale(1); }
  50% { transform: translate(3vw, -2vh) scale(0.94); }
}
@keyframes cd-slide-in {
  from { opacity: 0; transform: translateY(8px); }
  to { opacity: 1; transform: translateY(0); }
}
@keyframes cd-viewer-out {
  from { opacity: 1; transform: translateY(0); }
  to { opacity: 0; transform: translateY(6px); }
}
@keyframes cd-viewer-in {
  from { opacity: 0; transform: translateY(-6px); }
  to { opacity: 1; transform: translateY(0); }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}
