/* =========================================================
   Troupe v2 default theme — desktop layout (style.css).
   Tokens: tokens.css. Wisps: wisps.js. Theme: theme.js.
   Spec: §2 of v2 default redesign.
   ========================================================= */

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

html, body {
  height: 100%;
  background: var(--void-deep);
  color: var(--text-bright);
  font-family: var(--font-system);
  font-size: var(--text-base);
  line-height: var(--leading-base);
  overflow: hidden;
}

body { display: grid; grid-template-rows: auto 1fr; position: relative; }

#wisps-bg {
  position: fixed; inset: 0;
  width: 100%; height: 100%;
  z-index: 0;
  pointer-events: none;
}

troupe-app, .modal, .lightbox, .toast-stack {
  position: relative;
  z-index: 1;
}

/* === HEADER ===
 * .topbar gets z-index: 2 (NOT 1) so the kebab dropdown menu — which
 * lives inside .topbar's stacking context — paints above troupe-app.
 * Without this, troupe-app (later in the DOM at z-index 1) would
 * paint over the kebab dropdown, making it invisible. Mobile got
 * this right from the start; desktop had the same bug until
 * Gemini PR #625 round-7 HIGH caught the asymmetry.
 */
.topbar {
  display: flex; align-items: center; justify-content: space-between;
  padding: var(--space-sm) var(--space-md);
  background: var(--glass-bg);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  border-bottom: 1px solid var(--glass-border);
  position: relative;
  z-index: 2;
}

.wordmark {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: var(--text-xl);
  line-height: var(--leading-tight);
  letter-spacing: -0.01em;
  color: var(--text-bright);
}

.topbar-right {
  display: flex; align-items: center; gap: var(--space-sm);
  position: relative;
}

.streak-chip {
  font-family: var(--font-display);
  font-size: var(--text-xs);
  letter-spacing: 0.04em;
  color: var(--text-mid);
  background: var(--wisp-teal-glow);
  border: 1px solid var(--border-dim);
  border-radius: var(--radius-pill);
  padding: 2px 10px;
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-settle);
}
.streak-chip:hover { background: var(--wisp-teal-bright); color: var(--text-bright); }
.streak-chip[disabled] { cursor: default; opacity: 0.6; }

.icon-button {
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px;
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  color: var(--text-mid);
  cursor: pointer;
  transition: background var(--duration-fast), color var(--duration-fast), border-color var(--duration-fast);
}
.icon-button:hover { color: var(--text-bright); border-color: var(--border-dim); background: var(--void-raised); }
.icon-button:focus-visible { outline: 2px solid var(--wisp-teal); outline-offset: 1px; }

/* Help button keeps a visible dim border at rest so the "?" is
   discoverable as an affordance, not just lost text in the topbar. */
.help-button {
  border-color: var(--border-dim);
  font-family: var(--font-display);
  font-weight: 600;
  font-size: var(--text-base);
}

.kebab-menu {
  position: absolute; top: calc(100% + 4px); right: 0;
  list-style: none;
  background: var(--void-bright);
  border: 1px solid var(--border-glow);
  border-radius: var(--radius-md);
  padding: var(--space-xs);
  min-width: 180px;
  box-shadow: 0 8px 24px rgba(0,0,0,0.4);
  z-index: 5;
}
.kebab-menu[hidden] { display: none; }
.kebab-menu button[role="menuitem"] {
  display: block; width: 100%;
  text-align: left;
  padding: 6px 10px;
  background: transparent;
  border: 0;
  border-radius: var(--radius-sm);
  font-size: var(--text-base);
  color: var(--text-bright);
  cursor: pointer;
}
.kebab-menu button[role="menuitem"]:hover,
.kebab-menu button[role="menuitem"]:focus-visible {
  background: var(--void-raised);
  outline: none;
}

/* === LAYOUT === */
troupe-app {
  display: grid;
  grid-template-columns: 280px 1fr;
  height: 100%;
  overflow: hidden;
}

.sidebar {
  border-right: 1px solid var(--border-dim);
  overflow-y: auto;
  padding: var(--space-md);
  display: flex; flex-direction: column;
  gap: var(--space-lg);
}

.sidebar-section { display: flex; flex-direction: column; gap: var(--space-xs); }

.sidebar-section-header {
  display: flex; align-items: center; justify-content: space-between;
  padding: 0 var(--space-xs);
}

.section-label {
  font-family: var(--font-display);
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-mid);
}

.entity-list, troupe-room-list {
  list-style: none;
  display: flex; flex-direction: column;
  gap: 1px;
}

.entity-row, .troupe-room-list__item {
  display: flex; align-items: center; gap: var(--space-sm);
  padding: 6px 10px;
  border-radius: var(--radius-sm);
  font-size: var(--text-base);
  color: var(--text-bright);
  cursor: pointer;
  transition: background var(--duration-fast);
}
.entity-row:hover, .troupe-room-list__item:hover { background: var(--void-raised); }
.entity-row.active, .troupe-room-list__item--active { background: var(--wisp-teal-glow); border-left: 2px solid var(--wisp-teal); padding-left: 8px; }

.presence-dot {
  width: 8px; height: 8px;
  border-radius: 50%;
  background: var(--text-ghost);
  flex-shrink: 0;
}

.channel-glyph {
  width: 14px;
  font-family: var(--font-mono);
  color: var(--text-mid);
  flex-shrink: 0;
}

.unread-badge {
  margin-left: auto;
  font-size: var(--text-xs);
  background: var(--wisp-teal);
  color: var(--void-deep);
  padding: 2px 6px;
  border-radius: var(--radius-pill);
  min-width: 18px;
  text-align: center;
}

/* === MAIN === */
.main {
  display: grid;
  grid-template-rows: auto 1fr auto;
  overflow: hidden;
}

.active-room-header {
  display: flex; align-items: center; justify-content: space-between;
  padding: var(--space-sm) var(--space-md);
  background: var(--glass-bg);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  border-bottom: 1px solid var(--glass-border);
  min-height: 44px;
}

.active-room-name {
  font-family: var(--font-display);
  font-size: var(--text-lg);
  font-weight: 600;
  color: var(--text-bright);
}

troupe-message-list {
  display: block;
  overflow-y: auto;
  padding: var(--space-md);
}

/* === BUBBLES === */
.bubble {
  margin: 8px 0;
  padding: 10px 14px;
  border-radius: var(--radius-md);
  background: var(--bubble-them);
  border: 1px solid var(--bubble-them-border);
  max-width: 70%;
  /* --age direction (Phase 1d §3.1): platform sets --age = 0 for the
     OLDEST bubble in the loaded window, --age = 1 for the NEWEST. With
     this formula:
       --age = 0  → saturate(0.6)  → muted, ~60% saturation (history)
       --age = 1  → saturate(1.0)  → full saturation             (now)
     Matches spec §1.2 / §2.3: "oldest at 60% saturation, newest at
     100%". Fallback `var(--age, 1)` keeps non-Phase-1d hosts looking
     normal. Verified against the spec direction; Gemini PR #625
     MEDIUM sanity-check resolved. */
  filter: saturate(calc(0.6 + 0.4 * var(--age, 1)));
  animation: bubble-mount var(--duration-normal) var(--ease-breathe);
}
.bubble[data-self="true"] {
  margin-left: auto;
  background: var(--bubble-me);
  border-color: var(--bubble-me-border);
}
@keyframes bubble-mount {
  from { transform: scale(0.96); opacity: 0; }
  to   { transform: scale(1);    opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .bubble { animation: none; filter: saturate(1); }
  .toast { animation: none; }
  body.shame-1, body.shame-2, body.shame-3 { transition: none; }
}

.bubble-sender { font-size: var(--text-xs); color: var(--text-mid); margin-bottom: 2px; }
.bubble[data-self="true"] .bubble-sender { display: none; }
.bubble-body { font-size: var(--text-base); color: var(--text-bright); white-space: pre-wrap; word-wrap: break-word; }
.bubble-meta { font-size: var(--text-xs); color: var(--text-dim); margin-top: 4px; }
.bubble-reactions { margin-top: 6px; }
.empty-room { text-align: center; color: var(--text-dim); padding: var(--space-2xl) var(--space-md); font-style: italic; }

/* === COMPOSER === */
troupe-composer {
  display: block;
  padding: var(--space-sm) var(--space-md);
  background: var(--glass-bg);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  border-top: 1px solid var(--glass-border);
}
.troupe-composer__input {
  background: var(--void-mid);
  border: 1px solid var(--border-dim);
  border-radius: var(--radius-md);
  color: var(--text-bright);
  font-size: var(--text-base);
  padding: 8px 14px;
}
.troupe-composer__input:focus { outline: 2px solid var(--wisp-teal); outline-offset: -1px; border-color: transparent; }
.composer-attach { color: var(--text-mid); }
troupe-composer button[slot="send"], .troupe-composer__send {
  background: var(--wisp-teal-bright);
  border: 1px solid var(--bubble-me-border);
  border-radius: var(--radius-pill);
  color: var(--text-bright);
  font-family: var(--font-display);
  font-weight: 600;
  padding: 6px 16px;
  cursor: pointer;
  transition: background var(--duration-fast);
}
troupe-composer button[slot="send"]:hover, .troupe-composer__send:hover { background: var(--wisp-teal); }

/* === FLOATING COMPONENTS (action-bar, emoji-picker) ===
   Platform mounts both once and toggles visibility via class/attribute.
   troupe-vars.css sets `display: block`, so without these gate rules the
   bar floats permanently over the message list and the picker spills its
   16 emoji buttons into the layout on mount. The :not() selectors raise
   specificity above the bare `troupe-action-bar { display: flex }` rule
   below, so hiding wins until the platform toggles the state. */
troupe-action-bar:not(.troupe-action-bar--visible) { display: none; }
troupe-action-bar {
  display: flex; gap: 2px; padding: 4px;
  background: var(--glass-bg);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  border: 1px solid var(--border-glow);
  border-radius: var(--radius-md);
  box-shadow: 0 4px 12px rgba(0,0,0,0.4);
}
.troupe-action-bar__btn {
  background: transparent; border: 0;
  color: var(--text-bright); font-size: var(--text-base);
  padding: 6px 10px; border-radius: var(--radius-sm);
  cursor: pointer;
  transition: background var(--duration-fast);
}
.troupe-action-bar__btn:hover,
.troupe-action-bar__btn:focus-visible { background: var(--wisp-teal-glow); outline: none; }

troupe-emoji-picker:not([open]) { display: none; }
troupe-emoji-picker {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(36px, 1fr)); gap: 4px;
  padding: var(--space-sm);
  background: var(--void-bright);
  border: 1px solid var(--border-glow);
  border-radius: var(--radius-md);
  box-shadow: 0 8px 24px rgba(0,0,0,0.5);
  max-width: 320px;
}
.troupe-emoji-picker__emoji {
  background: transparent; border: 0;
  font-size: 20px; line-height: 1;
  padding: 6px; cursor: pointer;
  border-radius: var(--radius-sm);
  transition: background var(--duration-fast);
}
.troupe-emoji-picker__emoji:hover,
.troupe-emoji-picker__emoji:focus-visible { background: var(--wisp-teal-glow); outline: none; }

/* === MODAL === */
.modal::backdrop {
  background: var(--glass-bg);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
}
.modal {
  background: var(--void-bright);
  border: 1px solid var(--border-glow);
  border-radius: var(--radius-lg);
  padding: 0;
  color: var(--text-bright);
  width: min(440px, calc(100vw - 32px));
  box-shadow: 0 16px 48px rgba(0,0,0,0.6);
}
.modal-header { padding: var(--space-md) var(--space-lg); border-bottom: 1px solid var(--border-dim); }
.modal-header h3 { font-family: var(--font-display); font-weight: 600; font-size: var(--text-lg); }
.modal-body { padding: var(--space-lg); }
.modal-body input[type="text"] {
  width: 100%;
  background: var(--void-mid);
  border: 1px solid var(--border-dim);
  border-radius: var(--radius-sm);
  color: var(--text-bright);
  font-size: var(--text-base);
  padding: 8px 12px;
}
.modal-body input[type="text"]:focus { outline: 2px solid var(--wisp-teal); outline-offset: 0; border-color: transparent; }
.modal-footer { display: flex; justify-content: flex-end; gap: var(--space-sm); padding: var(--space-md) var(--space-lg); border-top: 1px solid var(--border-dim); }
.btn-ghost, .btn-primary {
  font-family: var(--font-display); font-weight: 600;
  border-radius: var(--radius-sm);
  padding: 6px 14px;
  cursor: pointer;
  transition: background var(--duration-fast);
}
.btn-ghost { background: transparent; border: 1px solid var(--border-dim); color: var(--text-mid); }
.btn-ghost:hover { color: var(--text-bright); border-color: var(--border-glow); }
.btn-primary { background: var(--wisp-teal-bright); border: 1px solid var(--bubble-me-border); color: var(--text-bright); }
.btn-primary:hover { background: var(--wisp-teal); }

/* === LIGHTBOX ===
   Selector must be [open]-qualified — bare .lightbox would override
   <dialog>'s default display:none when closed, putting a 100vh grid
   item in body's grid flow and collapsing troupe-app to 0px. */
.lightbox[open] {
  background: var(--troupe-overlay-bg);
  border: 0;
  width: 100vw; height: 100vh; max-width: none; max-height: none;
  margin: 0;
  padding: 0;
  display: grid; place-items: center;
}
.lightbox::backdrop { background: transparent; }
.lightbox-close {
  position: absolute; top: 16px; right: 16px;
  width: 36px; height: 36px;
  background: rgba(255,255,255,0.08);
  border: 0;
  border-radius: var(--radius-pill);
  color: var(--text-bright);
  font-size: 24px; line-height: 1;
  cursor: pointer;
}
#lightbox-image { max-width: 90vw; max-height: 90vh; object-fit: contain; }

/* === TOASTS === */
.toast-stack {
  position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%);
  display: flex; flex-direction: column; gap: 8px;
  z-index: 10;
}
.toast {
  background: var(--void-bright);
  border: 1px solid var(--border-glow);
  border-radius: var(--radius-md);
  padding: 8px 16px;
  color: var(--text-bright);
  font-size: var(--text-sm);
  box-shadow: 0 4px 12px rgba(0,0,0,0.4);
  animation: toast-in var(--duration-normal) var(--ease-breathe);
}
/* When the toast is appended inside an open <dialog> (top-layer
   stacking context, so it can render above the modal backdrop), it
   loses the .toast-stack's positioning. Position the toast itself in
   that case. The dialog's top-layer means this fixed positioning still
   wins z-order against everything below the dialog. */
dialog[open] > .toast {
  position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%);
  z-index: 10;
}
@keyframes toast-in {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* === SHAME (PR3 — ported from v1 default style.css:1130-1132 with .bubble.my-bubble → .bubble[data-self="true"]) === */
body.shame-1, body.shame-2, body.shame-3 {
  transition: filter 1.6s var(--ease-breathe);
}
body.shame-1 { filter: saturate(0.5); }
body.shame-2 .bubble[data-self="true"] { background: #1a2a1a; }
body.shame-3 { font-family: 'Comic Sans MS', cursive; }

/* === SIDEBAR SECTIONS — REQUESTS & INVITES (Phase B §5.7) === */
.sidebar-section--contact-requests[hidden],
.sidebar-section--room-invites[hidden] { display: none; }
.sidebar-section--contact-requests .entity-list li,
.sidebar-section--room-invites .entity-list li {
  display: flex; align-items: center; gap: var(--space-sm);
  padding: 6px 10px;
  border-radius: var(--radius-sm);
  background: var(--void-raised);
}
.sidebar-section--contact-requests .entity-list li > span,
.sidebar-section--room-invites .entity-list li > span {
  flex: 1;
  color: var(--text-bright);
  font-size: var(--text-base);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.sidebar-section--contact-requests button[data-action],
.sidebar-section--room-invites button[data-action] {
  font-family: var(--font-display);
  font-size: var(--text-xs);
  font-weight: 600;
  padding: 4px 8px;
  border-radius: var(--radius-sm);
  cursor: pointer;
  border: 1px solid var(--border-dim);
  background: transparent;
  color: var(--text-mid);
  transition: background var(--duration-fast), color var(--duration-fast);
}
.sidebar-section--contact-requests button[data-action='accept'],
.sidebar-section--room-invites button[data-action='accept'] {
  background: var(--wisp-teal-bright);
  color: var(--text-bright);
  border-color: var(--bubble-me-border);
}
.sidebar-section--contact-requests button[data-action]:disabled,
.sidebar-section--room-invites button[data-action]:disabled {
  opacity: 0.6; cursor: default;
}

/* Form helpers used by add-contact + profile modals */
.modal-body .form-error {
  color: var(--bubble-me-border);
  font-size: var(--text-xs);
  margin-top: 6px;
  min-height: 1em;
}
.modal-body .form-readonly {
  color: var(--text-mid);
  font-size: var(--text-base);
  padding: 6px 0;
}

/* Streak chip data-shame styling — chip-only for now; full body shame
 * visual lands in PR3. */
.streak-chip[data-shame-level="1"] { color: var(--text-mid); }
.streak-chip[data-shame-level="2"] { color: var(--text-dim); }
.streak-chip[data-shame-level="3"] { color: var(--text-ghost); }

/* Presence dot states */
.entity-row[data-presence="online"] .presence-dot { background: var(--wisp-teal-bright); }
.entity-row[data-presence="away"]   .presence-dot { background: var(--text-mid); }
.entity-row[data-presence="offline"] .presence-dot { background: var(--text-ghost); opacity: 0.5; }
.entity-row[data-presence="unknown"] .presence-dot,
.entity-row:not([data-presence]) .presence-dot { background: var(--text-ghost); opacity: 0.3; }

/* Inline room-rename input takes over the room-list item */
.troupe-room-list__item input[type="text"] {
  width: 100%;
  background: var(--void-mid);
  border: 1px solid var(--wisp-teal);
  border-radius: var(--radius-sm);
  color: var(--text-bright);
  font-size: var(--text-base);
  padding: 4px 8px;
}

/* Streak explainer modal list spacing */
.streak-explainer p { margin-bottom: var(--space-sm); }
.streak-explainer ul { padding-left: var(--space-md); }
.streak-explainer li { margin-bottom: var(--space-xs); }

/* === FOCUS-VISIBLE COVERAGE (Phase B PR4 §4.4 item 1) ===
 * Browser-default focus ring is invisible on the dark theme. Cover
 * every interactive element that's missing it. Outline-offset 1px
 * works for buttons and rows; modal/composer inputs already have
 * a focus rule with offset 0/-1 (kept verbatim).
 */
.btn-ghost:focus-visible,
.btn-primary:focus-visible,
.entity-row:focus-visible,
.troupe-room-list__item:focus-visible,
.lightbox-close:focus-visible,
.streak-chip:focus-visible,
troupe-composer button[slot="send"]:focus-visible,
.troupe-composer__send:focus-visible {
  outline: 2px solid var(--wisp-teal);
  outline-offset: 1px;
}
.sidebar-section--contact-requests button[data-action]:focus-visible,
.sidebar-section--room-invites button[data-action]:focus-visible {
  outline: 2px solid var(--wisp-teal);
  outline-offset: 1px;
}
.troupe-room-list__item input[type="text"]:focus-visible {
  outline: 2px solid var(--wisp-teal);
  outline-offset: 0;
}

/* ========================================================= */
/* Devices modal (Tier 3)                                    */
/* ========================================================= */

.devices-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.devices-error {
  color: var(--color-danger, #b00020);
  font-size: 13px;
  min-height: 0;
}

.devices-error:empty {
  display: none;
}

.devices-empty {
  color: var(--color-muted, #888);
  text-align: center;
  padding: 16px;
}

.devices-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 8px 12px;
  border: 1px solid var(--color-border, #e0e0e0);
  border-radius: 6px;
  transition: opacity 200ms;
}

.devices-row__meta {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.devices-row__primary {
  font-weight: 500;
}

.devices-row__id {
  font-family: monospace;
  font-size: 12px;
  color: var(--color-muted, #888);
}

.devices-row__current {
  background: var(--color-accent-soft, #e8f0fe);
  color: var(--color-accent, #1a73e8);
  font-size: 12px;
  padding: 2px 8px;
  border-radius: 10px;
}

.devices-row__remove {
  background: transparent;
  border: 1px solid var(--color-border, #e0e0e0);
  border-radius: 4px;
  padding: 4px 12px;
  cursor: pointer;
  font: inherit;
}

.devices-row__remove:hover:not(:disabled) {
  background: var(--color-bg-hover, #f5f5f5);
}

.devices-row__remove:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

.devices-row__remove[data-confirming="true"] {
  background: var(--color-danger, #b00020);
  color: #fff;
  border-color: var(--color-danger, #b00020);
}

.devices-pair-trigger {
  margin-top: 8px;
  background: transparent;
  border: 1px solid var(--color-accent, #1a73e8);
  color: var(--color-accent, #1a73e8);
  border-radius: 6px;
  padding: 8px 12px;
  cursor: pointer;
  font: inherit;
}

.devices-pair-trigger:hover {
  background: var(--color-accent-soft, #e8f0fe);
}

.devices-pair-status {
  font-weight: 500;
  margin-bottom: 8px;
}

.devices-pair-error {
  color: var(--color-danger, #b00020);
  font-size: 13px;
  min-height: 0;
}

.devices-pair-error:empty {
  display: none;
}

.devices-pair-qr {
  display: flex;
  justify-content: center;
  margin: 12px 0;
}

.devices-pair-url {
  font-family: monospace;
  font-size: 12px;
  word-break: break-all;
  background: var(--color-bg-subtle, #f5f5f5);
  padding: 8px;
  border-radius: 4px;
  user-select: all;
}

.devices-pair-hint {
  color: var(--color-muted, #888);
  font-size: 13px;
  margin-top: 8px;
}

.devices-pair-cancel {
  margin-top: 12px;
  background: transparent;
  border: 1px solid var(--color-border, #e0e0e0);
  border-radius: 4px;
  padding: 6px 12px;
  cursor: pointer;
  font: inherit;
}

/* ========================================================= */
/* First-run tutorial (PR #4 of ship-to-friends)             */
/* ========================================================= */
/* Full-screen overlay rather than <dialog> so the tutorial   */
/* can coexist with the regular modal layer (push prompt was  */
/* a <dialog>; tutorial absorbs it but the chat shell still   */
/* uses <dialog> for everything else). Top-level z-index keeps */
/* it above wisps + toasts. role=dialog + aria-modal on the   */
/* container plus focus-trap in theme_logic.js cover SR/keyboard. */

.tutorial[hidden] { display: none; }
.tutorial {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: grid;
  place-items: center;
  background: var(--glass-bg);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
}
.tutorial-card {
  position: relative;
  width: min(480px, calc(100vw - 32px));
  background: var(--void-bright);
  border: 1px solid var(--border-glow);
  border-radius: var(--radius-lg);
  padding: var(--space-lg);
  color: var(--text-bright);
  box-shadow: 0 16px 48px rgba(0,0,0,0.6);
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  box-sizing: border-box;
}
.tutorial-skip {
  position: absolute;
  top: 12px;
  right: 12px;
  min-width: 32px;
  min-height: 32px;
  background: transparent;
  border: 0;
  color: var(--text-mid);
  font-family: var(--font-display);
  font-size: var(--text-sm);
  cursor: pointer;
  padding: 4px 8px;
  border-radius: var(--radius-sm);
}
.tutorial-skip:hover { color: var(--text-bright); background: var(--void-raised); }
.tutorial-title {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: var(--text-lg);
  margin: 0;
  /* margin-right (not padding-right) so the title's box actually stops
     short of the Skip button — padding-right kept the content visually
     clear but left the title's pointer-event box overlapping. */
  margin-right: 56px;
}
.tutorial-body {
  font-family: var(--font-system);
  font-size: var(--text-base);
  color: var(--text-bright);
  min-height: 96px;
  /* Slide-in animation per card change. Keyed so each transition restarts. */
  animation: tutorial-slide-in var(--duration-normal) var(--ease-breathe);
}
.tutorial-body p { margin: 0 0 var(--space-sm); }
.tutorial-body p:last-child { margin-bottom: 0; }
.tutorial-body strong { color: var(--text-bright); font-weight: 600; }
.tutorial-body code {
  font-family: monospace;
  background: var(--void-mid);
  padding: 2px 6px;
  border-radius: var(--radius-sm);
  font-size: 0.95em;
}
.tutorial-link {
  color: var(--wisp-teal-bright);
  text-decoration: underline;
}
.tutorial-link:hover { color: var(--wisp-teal); }
.tutorial-actions {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-sm);
  margin-top: var(--space-sm);
}
.tutorial-dots {
  display: flex;
  justify-content: center;
  gap: 8px;
  margin-top: var(--space-xs, 4px);
}
/* Visible dot is 8x8 but the hit-target is inflated to 24x24 so mouse
   clicks land reliably (WCAG 2.5.8 Target Size Minimum). The same
   ::after technique is used on mobile, just sized to 44x44 for thumbs. */
.tutorial-dot {
  width: 24px;
  height: 24px;
  background: transparent;
  border: 0;
  padding: 0;
  cursor: pointer;
  position: relative;
}
.tutorial-dot::after {
  content: "";
  position: absolute;
  inset: 0;
  margin: auto;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--border-dim);
  transition: background var(--duration-fast);
}
.tutorial-dot:hover::after { background: var(--border-glow); }
.tutorial-dot[aria-current="step"]::after { background: var(--wisp-teal-bright); }
.tutorial-dot:focus-visible { outline: 2px solid var(--wisp-teal); outline-offset: -2px; border-radius: 50%; }

@keyframes tutorial-slide-in {
  from { opacity: 0; transform: translateX(16px); }
  to   { opacity: 1; transform: translateX(0); }
}
/* Hoisted out of the @media block — older WebKit had bugs with nested
   @keyframes; toplevel declarations are universally safe. */
@keyframes tutorial-fade-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .tutorial-body {
    animation: tutorial-fade-in var(--duration-normal) linear;
  }
}
