/* ============================================================
   CULTURILY — COMPONENT LIBRARY
   Reusable UI components shared across every page.
   Requires global.css to be loaded first.
   ============================================================ */

/* ─────────────────────────────────────────────────────────── */
/* BUTTONS                                                      */
/* ─────────────────────────────────────────────────────────── */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  padding: var(--sp-3) var(--sp-5);
  min-height: 44px; /* a11y medium-pass (2026-07-11): was 40px (16px text * line-height:1 + 12px*2 padding) */
  border-radius: var(--r-md);
  font-family: var(--font-body);
  font-size: var(--text-base);
  font-weight: 600;
  line-height: 1;
  white-space: nowrap;
  cursor: pointer;
  border: none;
  transition:
    background-color var(--t-fast) var(--ease),
    color            var(--t-fast) var(--ease),
    border-color     var(--t-fast) var(--ease),
    transform        var(--t-fast) var(--ease),
    box-shadow       var(--t-fast) var(--ease),
    opacity          var(--t-fast) var(--ease);
  user-select: none;
  position: relative;
  overflow: hidden;
  -webkit-tap-highlight-color: transparent;
}

.btn:active:not(:disabled) {
  transform: scale(0.97);
}

.btn:disabled,
.btn[aria-disabled="true"] {
  opacity: 0.38;
  cursor: not-allowed;
  pointer-events: none;
}

/* Primary — lime fill, dark text */
.btn-primary {
  background: var(--accent);
  color: var(--accent-text);
  box-shadow: var(--shadow-sm);
}
.btn-primary:hover:not(:disabled) {
  background: var(--accent-hover);
  box-shadow: var(--shadow-accent);
}

/* VISUAL-IDENTITY-RULING §5 K2 — the Duolingo-style 3D "lip" is retired
   (kill-list, reversed the old DESIGN_SYSTEM "signature premium" call).
   Flat lime CTA: fill, near-black text, radius ≤8, no lip, no glow. */
.btn-primary-3d {
  position: relative;
  background: var(--accent);
  color: var(--accent-text);
  border: none;
  border-radius: 8px;
  padding: 14px 24px;
  font-weight: 600;
  font-size: 16px;
  transition: opacity 60ms ease;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
  cursor: pointer;
  width: 100%;
}
.btn-primary-3d:active:not(:disabled) {
  opacity: 0.85;
}
@media (prefers-reduced-motion: reduce) {
  .btn-primary-3d,
  .btn-primary-3d:active:not(:disabled) { transition: none; }
}

/* ─────────────────────────────────────────────────────────── */
/* SKELETON LOADING PRIMITIVE                                   */
/* GPU-friendly sweep (transform-only). Match these to the real */
/* layout so content doesn't jump when it loads.               */
/* ─────────────────────────────────────────────────────────── */
.skeleton {
  position: relative;
  overflow: hidden;
  background: var(--surface-2);
  border-radius: 10px;
}
.skeleton::after {
  content: "";
  position: absolute;
  inset: 0;
  transform: translateX(-100%);
  background: linear-gradient(
    90deg,
    transparent 0%,
    color-mix(in srgb, var(--text-primary) 6%, transparent) 50%,
    transparent 100%
  );
  animation: skeleton-sweep 1.3s ease-in-out infinite;
}
@keyframes skeleton-sweep { to { transform: translateX(100%); } }
@media (prefers-reduced-motion: reduce) {
  .skeleton::after { animation: none; }
  .skeleton { background: var(--surface-3); }
}
.skeleton-text    { height: 16px; border-radius: 4px;  margin-bottom: 8px; }
.skeleton-text-sm { height: 12px; border-radius: 4px;  margin-bottom: 6px; }
.skeleton-title   { height: 24px; border-radius: 6px;  margin-bottom: 12px; }
.skeleton-card    { height: 120px; border-radius: 16px; }
.skeleton-avatar  { width: 48px; height: 48px; border-radius: 50%; }

/* Culture primary — uses the current culture's accent */
.btn-culture {
  background: var(--culture-accent);
  color: var(--culture-accent-text);
  box-shadow: var(--shadow-sm);
}
.btn-culture:hover:not(:disabled) {
  filter: brightness(1.08);
  box-shadow: var(--shadow-culture);
}

/* Secondary — outline */
.btn-secondary {
  background: transparent;
  color: var(--text-primary);
  border: 1.5px solid var(--border-strong);
}
.btn-secondary:hover:not(:disabled) {
  background: var(--surface-3);
  border-color: var(--border-strong);
}

/* Ghost — text only, no border */
.btn-ghost {
  background: transparent;
  color: var(--text-muted);
}
.btn-ghost:hover:not(:disabled) {
  background: var(--surface-2);
  color: var(--text-primary);
}

/* Danger */
.btn-danger {
  background: var(--error-dim);
  color: var(--error);
  border: 1.5px solid transparent;
}
.btn-danger:hover:not(:disabled) {
  background: color-mix(in srgb, var(--error) 20%, transparent);
  border-color: color-mix(in srgb, var(--error) 35%, transparent);
}

/* Surface — raised card-like button */
.btn-surface {
  background: var(--surface-2);
  color: var(--text-primary);
  border: 1px solid var(--border);
}
.btn-surface:hover:not(:disabled) {
  background: var(--surface-3);
  border-color: var(--border-strong);
}

/* Size modifiers */
.btn-sm {
  padding: var(--sp-2) var(--sp-3);
  font-size: var(--text-sm);
  border-radius: var(--r-sm);
}

.btn-lg {
  padding: var(--sp-4) var(--sp-8);
  font-size: var(--text-lg);
  border-radius: var(--r-lg);
}

/* Full width */
.btn-full {
  width: 100%;
}

/* Loading spinner inside button */
.btn .btn-spinner {
  width: 16px;
  height: 16px;
  border: 2px solid currentColor;
  border-top-color: transparent;
  border-radius: 50%;
  animation: spin 700ms linear infinite;
  flex-shrink: 0;
}

/* ─────────────────────────────────────────────────────────── */
/* CARDS                                                        */
/* ─────────────────────────────────────────────────────────── */

.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  padding: var(--sp-4);
  position: relative;
  /* Premium 3D lip — lime object-shadow + soft ambient shadow. */
  box-shadow: 0 4px 0 0 color-mix(in srgb, var(--accent) 25%, transparent), 0 8px 24px rgba(0, 0, 0, 0.4);
  transition: box-shadow 80ms ease, transform 80ms ease;
}
.card:active {
  box-shadow: 0 2px 0 0 color-mix(in srgb, var(--accent) 15%, transparent), 0 4px 12px rgba(0, 0, 0, 0.3);
  transform: translateY(2px);
}

.card-md {
  padding: var(--sp-5);
  border-radius: var(--r-xl);
}

.card-lg {
  padding: var(--sp-6);
  border-radius: var(--r-xl);
}

/* Interactive card — hover lift */
.card-hover {
  transition:
    border-color var(--t-base) var(--ease),
    box-shadow   var(--t-base) var(--ease),
    transform    var(--t-base) var(--ease);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.card-hover:hover {
  border-color: var(--border-strong);
  box-shadow: var(--shadow-md);
  transform: translateY(-2px);
}
.card-hover:active {
  transform: translateY(0) scale(0.99);
  box-shadow: var(--shadow-sm);
}

/* Culture-accented card — thin coloured top border */
.card-culture {
  border-top: 2px solid var(--culture-accent);
}

/* Accent card — lime for global UI elements */
.card-accent {
  border-top: 2px solid var(--accent);
}

/* Selected state */
.card-selected {
  border-color: var(--accent);
  box-shadow: 0 0 0 1px var(--accent), var(--shadow-accent);
}

.card-selected-culture {
  border-color: var(--culture-accent);
  box-shadow: 0 0 0 1px var(--culture-accent), var(--shadow-culture);
}

/* ─────────────────────────────────────────────────────────── */
/* FORM FIELDS                                                  */
/* ─────────────────────────────────────────────────────────── */

.form-group {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}

.form-label {
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--text-muted);
}

.form-input,
.form-select,
.form-textarea {
  width: 100%;
  background: var(--surface-2);
  border: 1.5px solid var(--border);
  border-radius: var(--r-md);
  padding: var(--sp-3) var(--sp-4);
  color: var(--text-primary);
  font-family: var(--font-body);
  font-size: var(--text-base);
  line-height: var(--leading-normal);
  transition:
    border-color var(--t-fast) var(--ease),
    background   var(--t-fast) var(--ease),
    box-shadow   var(--t-fast) var(--ease);
  appearance: none;
  -webkit-appearance: none;
}

/* a11y low-pass (2026-07-11): --text-disabled means "disabled control," not
   "hint text before typing" — using it here measured 3.10:1 dark / 2.12:1
   light on --surface-2 (both fail 4.5:1), the same class of --text-disabled
   misuse the critical/high pass already flagged elsewhere. Swapped to
   --text-subtle (7.32:1 / 6.26:1) — fixes every form field using this shared
   component class across the app in one place (login, register, settings,
   feedback, etc.), not just the two the audit sampled. */
.form-input::placeholder,
.form-textarea::placeholder {
  color: var(--text-subtle);
}

.form-input:focus,
.form-select:focus,
.form-textarea:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-dim);
  background: var(--surface-3);
}

.form-input.input-error,
.form-select.input-error,
.form-textarea.input-error {
  border-color: var(--error);
  box-shadow: 0 0 0 3px var(--error-dim);
}

.form-input.input-success,
.form-select.input-success {
  border-color: var(--success);
  box-shadow: 0 0 0 3px var(--success-dim);
}

.form-hint {
  font-size: var(--text-xs);
  color: var(--text-subtle);
}

.form-error {
  font-size: var(--text-xs);
  color: var(--error);
  display: flex;
  align-items: center;
  gap: var(--sp-1);
}

.form-textarea {
  resize: vertical;
  min-height: 96px;
}

/* Custom select arrow */
.form-select {
  /* NOTE: raw data-URI SVG stroke color can't reference a CSS var() — flagged
     WO-VIS Wave B. Dark value kept literal; light-theme override below
     supplies a dark-stroke twin so the chevron stays visible on light. */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='rgba(248,248,248,0.45)' stroke-width='2'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right var(--sp-3) center;
  padding-right: var(--sp-8);
  cursor: pointer;
}
html.theme-light .form-select {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='rgba(22,22,22,0.45)' stroke-width='2'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
}

.form-select option {
  background: var(--surface-3);
  color: var(--text-primary);
}

/* ── Password input with show/hide toggle ─────────────────── */
.input-password-wrap {
  position: relative;
}
.input-password-wrap .form-input {
  padding-right: var(--sp-10);
}
.input-password-toggle {
  position: absolute;
  right: var(--sp-3);
  top: 50%;
  transform: translateY(-50%);
  color: var(--text-subtle);
  background: none;
  border: none;
  cursor: pointer;
  padding: var(--sp-1);
  border-radius: var(--r-sm);
  transition: color var(--t-fast) var(--ease);
}
.input-password-toggle:hover {
  color: var(--text-primary);
}

/* ─────────────────────────────────────────────────────────── */
/* MODAL                                                        */
/* ─────────────────────────────────────────────────────────── */

/* Overlay */
.modal-overlay {
  position: fixed;
  inset: 0;
  background: var(--scrim);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  z-index: var(--z-overlay);
  display: flex;
  align-items: flex-end;
  justify-content: center;
  padding: var(--sp-4);
  animation: fadeIn var(--t-base) var(--ease-out) both;
}

/* On larger screens, centre the modal */
@media (min-width: 480px) {
  .modal-overlay {
    align-items: center;
  }
}

/* Sheet */
.modal {
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-2xl) var(--r-2xl) var(--r-md) var(--r-md);
  width: 100%;
  max-width: var(--app-width);
  max-height: 88dvh;
  overflow-y: auto;
  box-shadow: var(--shadow-xl);
  animation: modalIn var(--t-slow) var(--ease-spring) both;
}

@media (min-width: 480px) {
  .modal {
    border-radius: var(--r-2xl);
    max-height: 80dvh;
  }
}

.modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--sp-5) var(--sp-5) 0;
}

.modal-title {
  font-family: var(--font-display);
  font-size: var(--text-xl);
  font-weight: 700;
  color: var(--text-primary);
}

.modal-close {
  width: 32px;
  height: 32px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--surface-3);
  border-radius: var(--r-full);
  color: var(--text-subtle);
  cursor: pointer;
  transition: background var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
  flex-shrink: 0;
}
.modal-close:hover {
  background: var(--surface-4);
  color: var(--text-primary);
}
.modal-close svg {
  width: 16px;
  height: 16px;
}

.modal-body {
  padding: var(--sp-4) var(--sp-5) var(--sp-5);
  color: var(--text-muted);
  font-size: var(--text-base);
  line-height: var(--leading-relaxed);
}

.modal-footer {
  padding: 0 var(--sp-5) var(--sp-5);
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}

/* Close modal by adding .is-closing class before removing from DOM */
.modal.is-closing {
  animation: fadeOut var(--t-base) var(--ease-in) both;
}

/* ─────────────────────────────────────────────────────────── */
/* TOAST NOTIFICATIONS                                          */
/* ─────────────────────────────────────────────────────────── */

.toast-container {
  position: fixed;
  top: calc(var(--top-bar-h) + var(--sp-3));
  right: var(--sp-4);
  left: var(--sp-4);
  z-index: var(--z-toast);
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  pointer-events: none;
  align-items: flex-end;
}

/* Toast component — reward-kit styling: icon chip · title + message ·
   3px accent rail · trailing slot (✕ or reward amount). Per-variant accent
   via --tac. Token-driven surface so dark + light flow through. */
.toast {
  --tac: var(--accent);
  --tac-bg: color-mix(in srgb, var(--accent) 12%, transparent);
  --tac-bd: color-mix(in srgb, var(--accent) 22%, transparent);
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 13px 14px;
  background: var(--surface-2);
  border: 1px solid var(--border-strong);
  border-left: 3px solid var(--tac);
  border-radius: 16px;
  box-shadow: var(--shadow-lg);
  pointer-events: auto;
  max-width: min(380px, 100%);
  animation: toastIn 520ms cubic-bezier(.16,.9,.3,1) both;
  color: var(--text-primary);
}

.toast.is-dismissing {
  animation: toastOut 200ms var(--ease-in) both;
}

/* icon chip — tinted square, glyph in the variant accent */
.toast-icon {
  width: 38px;
  height: 38px;
  flex: 0 0 38px;
  border-radius: 11px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--tac-bg);
  border: 1px solid var(--tac-bd);
  color: var(--tac);
  font-size: 18px;
  line-height: 1;
}

.toast-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.toast-title {
  font-family: var(--font-display);
  font-size: 14.5px;
  font-weight: 600;
  color: var(--text-primary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.toast-msg { font-size: 12.5px; line-height: 1.3; color: var(--text-subtle); }
/* single-line toasts (no title/msg split) still read well */
.toast > span:not([class]) { font-size: var(--text-sm); font-weight: 500; }

/* trailing slot: reward amount (lime) or a dismiss ✕ */
.toast-amount {
  flex: 0 0 auto;
  font-family: var(--font-display);
  font-size: 16px;
  font-weight: 700;
  color: var(--tac);
}
.toast-close { flex: 0 0 auto; color: var(--text-disabled); cursor: pointer; display: flex; }
.toast-close svg { width: 15px; height: 15px; }

/* variants — accent + tint per kind */
.toast-success, .toast-accent, .toast-reward { --tac: var(--accent); --tac-bg: color-mix(in srgb, var(--accent) 12%, transparent); --tac-bd: color-mix(in srgb, var(--accent) 22%, transparent); }
.toast-reward  { --tac-bg: color-mix(in srgb, var(--accent) 14%, transparent); border-color: color-mix(in srgb, var(--accent) 30%, transparent); }
.toast-error   { --tac: var(--error); --tac-bg: color-mix(in srgb, var(--error) 12%, transparent); --tac-bd: color-mix(in srgb, var(--error) 22%, transparent); }
.toast-info    { --tac: var(--info); --tac-bg: color-mix(in srgb, var(--info) 12%, transparent); --tac-bd: color-mix(in srgb, var(--info) 30%, transparent); }
.toast-warning { --tac: var(--warning); --tac-bg: color-mix(in srgb, var(--warning) 12%, transparent); --tac-bd: color-mix(in srgb, var(--warning) 28%, transparent); }

/* light theme — lime/blue text needs a darker shade on the beige surface */
html.theme-light .toast-success .toast-icon,
html.theme-light .toast-accent .toast-icon,
html.theme-light .toast-reward .toast-icon,
html.theme-light .toast-reward .toast-amount,
html.theme-light .toast-success .toast-amount { color: var(--accent-ink); }
html.theme-light .toast-success, html.theme-light .toast-accent, html.theme-light .toast-reward { border-left-color: var(--accent-ink); }
/* toast-error/-info retired their light overrides: base --tac now uses
   var(--error)/var(--info), which already resolve to the correct AA light
   values — the old #C2342A/#2f6fd0 one-off shades are gone (harmonized). */

/* ─────────────────────────────────────────────────────────── */
/* PROGRESS BAR                                                 */
/* ─────────────────────────────────────────────────────────── */

.progress-track {
  width: 100%;
  height: 8px;
  background: var(--surface-3);
  border-radius: var(--r-full);
  overflow: hidden;
}

.progress-fill {
  height: 100%;
  background: var(--accent);
  border-radius: var(--r-full);
  transition: width 400ms var(--ease-out);
  min-width: 4px;
}

/* Culture-coloured progress */
.progress-fill-culture {
  background: var(--culture-accent);
}

/* XP bar variant — thinner */
.progress-track-sm {
  height: 5px;
}

/* Lesson progress bar — shown at top of quiz */
.lesson-progress-track {
  height: 14px;
  background: var(--surface-well);
  border-radius: 8px;
  overflow: hidden;
  box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.5);
}

.lesson-progress-fill {
  position: relative;
  height: 100%;
  background: var(--accent-grad);
  border-radius: 8px;
  transition: width 0.6s cubic-bezier(0.4, 0, 0.2, 1);
  overflow: hidden;
}
.lesson-progress-fill::after {
  content: '';
  position: absolute;
  inset: 0 0 50% 0;
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.25), rgba(255, 255, 255, 0));
  border-radius: 8px 8px 0 0;
}
html.theme-light .lesson-progress-track {
  box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.12);
}

/* ─────────────────────────────────────────────────────────── */
/* AVATAR                                                       */
/* ─────────────────────────────────────────────────────────── */
/*
 * Usage:
 *   <div class="avatar avatar--md"
 *        data-initials="JD"
 *        style="--avatar-color: var(--accent);">
 *   </div>
 *
 * Drop-in illustrated avatar: add <img src="..." alt=""> inside the div.
 * The image covers the initials automatically — no JS or CSS change needed.
 */

.avatar {
  position: relative;
  border-radius: 50%;
  overflow: hidden;
  background-color: var(--avatar-color, var(--surface-3));
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  user-select: none;
  border: 2px solid var(--border);
}

/* Show initials from data attribute */
.avatar::after {
  content: attr(data-initials);
  font-family: var(--font-body);
  font-weight: 700;
  font-size: calc(var(--avatar-size, 40px) * 0.36);
  color: var(--accent-text);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  line-height: 1;
  z-index: 1;
}

/* When an <img> is present it covers the initials */
.avatar img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  z-index: 2;
}

/* Size variants */
.avatar--2xs { --avatar-size: 20px; width: 20px; height: 20px; border-width: 1px; }
.avatar--xs  { --avatar-size: 28px; width: 28px; height: 28px; border-width: 1px; }
.avatar--sm  { --avatar-size: 36px; width: 36px; height: 36px; }
.avatar--md  { --avatar-size: 44px; width: 44px; height: 44px; }
.avatar--lg  { --avatar-size: 60px; width: 60px; height: 60px; border-width: 3px; }
.avatar--xl  { --avatar-size: 84px; width: 84px; height: 84px; border-width: 3px; }
.avatar--2xl { --avatar-size: 112px; width: 112px; height: 112px; border-width: 4px; }

/* Culture border on avatar */
.avatar-culture { border-color: var(--culture-accent); }
.avatar-accent  { border-color: var(--accent); }

/* Online dot indicator */
.avatar-wrap {
  position: relative;
  display: inline-flex;
  flex-shrink: 0;
}
.avatar-status {
  position: absolute;
  bottom: 0;
  right: 0;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  border: 2px solid var(--bg);
  background: var(--success);
}

/* ─────────────────────────────────────────────────────────── */
/* GAME UI CHIPS                                               */
/* Hearts · Gems · XP · Streak                                 */
/* ─────────────────────────────────────────────────────────── */

.game-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
  font-family: var(--font-body);
  font-weight: 700;
  font-size: var(--text-sm);
  line-height: 1;
  white-space: nowrap;
}

.game-chip-icon {
  font-size: var(--text-base);
  line-height: 1;
}

/* Heart chip — red */
.hearts-chip { color: var(--hearts); }
.hearts-chip .game-chip-icon { color: var(--hearts); }

/* Gem chip — teal/cyan */
.gems-chip   { color: var(--gems); }
.gems-chip .game-chip-icon { color: var(--gems); }

/* Streak-freeze chip — cold light blue */
.freeze-chip { color: var(--freeze); }
.freeze-chip .game-chip-icon { color: var(--freeze); }

/* XP chip — lime accent */
.xp-chip     { color: var(--accent); }
.xp-chip .game-chip-icon { color: var(--accent); }

/* Streak chip — orange-red fire */
.streak-chip { color: var(--streak); }
.streak-chip.is-active .game-chip-icon {
  animation: pulse 1.4s ease-in-out infinite;
  display: inline-block;
}

/* Empty heart */
.heart-empty { opacity: 0.30; }

/* Hearts display row (5 hearts) */
.hearts-row {
  display: flex;
  align-items: center;
  gap: var(--sp-1);
}

/* ─────────────────────────────────────────────────────────── */
/* STAR RATING                                                  */
/* ─────────────────────────────────────────────────────────── */

.stars-row {
  display: flex;
  align-items: center;
  gap: 2px;
}

.star {
  font-size: var(--text-lg);
  line-height: 1;
  color: var(--surface-4);
  transition: color var(--t-base) var(--ease);
}

.star.is-earned {
  color: var(--gold);
}

/* Lesson card star indicator */
.lesson-stars {
  display: flex;
  gap: 2px;
}
.lesson-star {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--surface-4);
  transition: background var(--t-base) var(--ease);
}
.lesson-star.is-earned {
  background: var(--gold);
}

/* ─────────────────────────────────────────────────────────── */
/* BADGE / ACHIEVEMENT CHIP                                     */
/* ─────────────────────────────────────────────────────────── */

.badge {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
  padding: 3px var(--sp-2);
  border-radius: var(--r-full);
  font-size: var(--text-xs);
  font-weight: 700;
  line-height: 1;
  white-space: nowrap;
}

.badge-accent {
  background: var(--accent-dim);
  color: var(--accent-ink);
  border: 1px solid color-mix(in srgb, var(--accent) 25%, transparent);
}

.badge-culture {
  background: var(--culture-accent-dim);
  color: var(--culture-accent);
  border: 1px solid color-mix(in srgb, var(--culture-accent) 30%, transparent);
}

.badge-super {
  background: linear-gradient(135deg, color-mix(in srgb, var(--accent) 15%, transparent), color-mix(in srgb, var(--info) 15%, transparent));
  color: var(--accent-ink);
  border: 1px solid var(--border-accent);
}

.badge-muted {
  background: var(--surface-3);
  color: var(--text-subtle);
  border: 1px solid var(--border);
}

/* ─────────────────────────────────────────────────────────── */
/* LOADING SPINNER                                              */
/* ─────────────────────────────────────────────────────────── */

.spinner {
  width: 24px;
  height: 24px;
  border: 2.5px solid var(--border-strong);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: spin 700ms linear infinite;
  flex-shrink: 0;
}

.spinner-sm {
  width: 16px;
  height: 16px;
  border-width: 2px;
}

.spinner-lg {
  width: 36px;
  height: 36px;
  border-width: 3px;
}

.spinner-culture {
  border-top-color: var(--culture-accent);
}

/* Full-page loading state */
.page-loading {
  min-height: 100dvh;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-direction: column;
  gap: var(--sp-4);
  color: var(--text-subtle);
  font-size: var(--text-sm);
}

/* ─────────────────────────────────────────────────────────── */
/* APP TOP BAR                                                  */
/* ─────────────────────────────────────────────────────────── */

.top-bar {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: var(--top-bar-h);
  background: var(--bg);
  border-bottom: 1px solid var(--border);
  z-index: var(--z-sticky);
  display: flex;
  align-items: center;
  padding-inline: var(--sp-4);
  gap: var(--sp-3);
}

/* Constrain inner content to app width */
.top-bar-inner {
  width: 100%;
  max-width: var(--app-width);
  margin-inline: auto;
  display: flex;
  align-items: center;
  gap: var(--sp-3);
}

.top-bar-culture {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  cursor: pointer;
  flex-shrink: 0;
  padding: var(--sp-1) var(--sp-2);
  border-radius: var(--r-sm);
  transition: background var(--t-fast) var(--ease);
}
.top-bar-culture:hover {
  background: var(--surface-2);
}

.top-bar-flag {
  font-size: var(--text-xl);
  line-height: 1;
}

.top-bar-culture-name {
  font-weight: 700;
  font-size: var(--text-sm);
  color: var(--text-primary);
}

/* Game HUD row — hearts · streak · gems · XP */
.top-bar-hud {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  flex: 1;
  justify-content: flex-end;
}

/* XP bar below hud (optional alternate layout) */
.top-bar-xp {
  width: 100%;
  max-width: var(--app-width);
  margin-inline: auto;
  padding: 0 var(--sp-4) var(--sp-2);
}

/* ─────────────────────────────────────────────────────────── */
/* BOTTOM NAVIGATION                                            */
/* ─────────────────────────────────────────────────────────── */

.bottom-nav {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  /* Inset carrier — height + padding-bottom keep the safe-area contract intact.
     DO NOT change height/padding-bottom: they, --nav-bottom-h, and the content
     padding-bottom reservations are one linked unit. Only paint moved inside. */
  height: var(--nav-bottom-h);
  background: transparent;        /* was var(--surface) — pill provides the chrome */
  border-top: none;               /* was 1px solid var(--border) */
  z-index: var(--z-sticky);
  display: flex;
  align-items: center;            /* center the shorter floating pill in the row */
  justify-content: center;        /* centre the capped pill on wide screens */
  padding-bottom: var(--safe-bottom, 0px);
  padding-left: 16px;             /* gutter from screen edge (not flush) — mobile */
  padding-right: 16px;
  pointer-events: none;           /* transparent side-margins never eat taps */
  overflow: visible;              /* let the active circle pop above the pill */
}

/* Floating pill — the visible nav chrome now lives on the inner row. */
.bottom-nav-inner {
  display: flex;
  width: 100%;
  max-width: var(--app-width);
  margin-inline: auto;            /* centred; caps at --app-width on desktop */
  height: 58px;                   /* shorter than the 64px row → floats */
  padding: 0 8px;
  border-radius: var(--r-full);
  /* §ED441 §7b / DELETED §ED451: `background: rgba(18,18,20,.92)` lived here.
     §ED441 rendered it in all four surfaces (web light, web dark, shell light,
     shell dark) and the literal LOST every time — global.css's
     --c-glass-chrome wins on the web, app-nav.css's --surface in the shell. It
     painted nowhere. Removed rather than left as clutter because the failure
     mode is invisible: it is one specificity bump away from repainting the bar
     near-black, and a colour that did not change looks exactly like a colour
     that was not meant to change (§ED440 T1).

     THE `border` AND `box-shadow` BELOW ARE LEFT IN PLACE DELIBERATELY. Both
     are also overridden (global.css's specular-edge block on the web,
     app-nav.css §5 in the shell), so both are very probably dead too — but
     §ED441 §7b MEASURED the background only, and this pass deletes what was
     measured, not what is inferred. Filed as a follow-up rather than taken on
     reasoning. */
  border: 1px solid rgba(255, 255, 255, 0.08);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.28);
  pointer-events: auto;           /* re-enable taps on the pill itself */
  overflow: visible;              /* active circle pops above the pill top edge */
}

.bottom-nav-item {
  flex: 1;
  min-width: 0;                /* allow the tabs to share a 390px row */
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  cursor: pointer;
  color: rgba(255, 255, 255, 0.68);   /* light icons/labels on the dark pill */
  transition: color var(--t-fast) var(--ease);
  border: none;
  background: none;
  padding: var(--sp-1) 2px;
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;  /* no tap delay / double-tap zoom on the tabs */
  position: relative;
  overflow: visible;           /* active circle pops above the pill */
}

.bottom-nav-item:hover {
  color: rgba(255, 255, 255, 0.9);
}

.bottom-nav-item.is-active {
  /* (No `color` here: the glyph is a fixed lime gradient and the label is
     hidden, so an item colour would be a no-op.) */
  outline: none;               /* replaces the old outline-box indicator */
}
.bottom-nav-item.is-active .bottom-nav-label {
  visibility: hidden;          /* active shows the pop circle, not the label */
}

/* Tabs are COMPLETELY static on tap — the ONLY feedback is the lime colour
   (+ the pop circle). Every transform is force-killed from every state and
   descendant, and icon transitions are removed so nothing can animate a move.

   NAV-JUMP HISTORY (do not re-litigate without reading this):
   Round 1 — naming the nav (view-transition-name:bottom-nav) made it a
   persistent view-transition GROUP: across a page navigation the active
   circle morphed from the old tab to the new tab's (popped) position — it
   appeared to JUMP UP. An `animation: none` opt-out on the named group did
   NOT stop this on WKWebView.
   Round 2 — unnaming it (.bottom-nav, still current) folded the nav into
   the ROOT snapshot instead. This did NOT fix it either: root-snapshot
   cross-document View Transitions crossfade a raster image of the OLD page
   into a raster image of the NEW page. Both images contained the nav with
   the active circle in a DIFFERENT x-position (different pages have
   different active tabs) — dissolving between two images that differ in
   content reads as a jump regardless of naming/grouping, because nothing
   about grouping changes what gets rasterized.
   Round 3 — found the actual bug: cross-document View Transitions were
   never disabled at all; rounds 1-2 only touched this element's
   participation in it. Removed the @view-transition opt-in in global.css
   entirely; navigation now always uses the JS #page-veil (js/transitions.js)
   — a solid-bg fade-through where content is never visible mid-transition.
   Round 4 (this pass) — went further: STRUCTURALLY eliminated the bug
   class rather than relying on round 3 staying correct forever. Previously
   the pop circle was CREATED by moving .bottom-nav-icon itself (top:0 → -18px)
   only on .is-active — a real position CHANGE that existed on every page,
   just never animated (transition:none) and never cross-page-visible once
   round 3 landed. Now EVERY tab always renders a .bottom-nav-pop layer at
   the exact same top:-18px position, on every page, active or not — only
   ITS OWN opacity toggles (0 → 1). There is no longer a position CHANGE to
   protect against, on this page or across any future transition mechanism
   (CSS, native, or unknown) that might wrap navigation again. Do NOT
   reintroduce a top/left/transform change gated on .is-active — add new
   "popped" states as opacity-only toggles on an always-present element,
   the same pattern as .bottom-nav-pop below. */
.bottom-nav { transform: none !important; }
.bottom-nav-item {
  transform: none !important;
  touch-action: manipulation;
}
.bottom-nav-item:active { transform: none !important; }
/* :not(.is-active) so focusing/tapping the active tab never strips its box. */
.bottom-nav-item:focus:not(.is-active) { transform: none !important; outline: none; }
.bottom-nav-item:active:not(.is-active) { outline: none; }
.bottom-nav-icon {
  font-size: 20px;
  line-height: 1;
  position: relative;          /* anchor for the count badge */
  transform: none !important;
  transition: none !important;
  /* Center inline-SVG icons (e.g. the Super crown) like the font glyphs. */
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
/* Rest icon — the inline icon shown when this tab is NOT active. Its position
   never changes on any state; only its own opacity toggles (soft cross-fade
   against the pop layer below). */
.bottom-nav-icon-rest { transition: opacity 100ms ease !important; }
.bottom-nav-item.is-active .bottom-nav-icon-rest { opacity: 0; }

/* Pop layer — ALWAYS present, ALWAYS at this exact position (top:-6px,
   centered), for every tab, on every page load, active or not. Only opacity
   toggles. See the round-4 note above: this is the entire fix — there is no
   position for any transition mechanism to ever mis-render.
   -6px (structural rearchitecture pass, was -18px): .bottom-nav-item stretches
   to the pill's full 58px height (flex default align-items:stretch), so its
   top edge IS the pill's top edge — top:-6px puts the circle's top edge 6px
   above the pill, contained rather than floating mostly above it. The 52px
   circle's bottom edge then lands 46px down, still 12px clear of the pill's
   own bottom edge. Centered via left:50%+margin-left (NOT transform, which the
   blanket .bottom-nav-item * rule below force-kills anyway). */
/* §ED441 §7c / DELETED §ED451 — THE DISC CHROME, NOT THE LAYER.
   The 52px circle (top:-6px, left:50%, margin-left:-26px, width/height 52px,
   border-radius:50%, --surface-4 fill, drop shadow) painted nothing from the
   moment css/global.css §ED441 (3) pinned this layer flush over the rest
   glyph at 22x22 — and app-nav.css had hidden it outright in the shell. Since
   §ED451 the shell uses the same flush layer, so the disc is dead on all four
   surfaces.

   WHAT REMAINS BELOW IS LOAD-BEARING AND MUST NOT FOLLOW IT.
   `position: absolute` + the opacity pair + the 100ms transition ARE the
   active state: rest renders Phosphor's regular weight, this layer renders
   the fill weight, and the cross-fade is the whole mechanism. The layer is
   also the §ED167 round-4 architecture — read the NAV-JUMP HISTORY note above
   before touching it. Deleting the LAYER would take the active state with it. */
.bottom-nav-pop {
  position: absolute;
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  pointer-events: none;           /* decorative — taps go to the whole <a> */
  transition: opacity 100ms ease !important;
}
.bottom-nav-item.is-active .bottom-nav-pop { opacity: 1; }
.bottom-nav-item.is-active .bottom-nav-icon { transform: none !important; }
.bottom-nav-item * { transform: none !important; }
.bottom-nav-svg-icon, .bottom-nav-super-icon { display: block; }
/* Icons render currentColor (scripts/inject-bottom-nav.js) — was a fixed
   lime gradient baked at bake-time (§3a bans gradients). Uses var(--accent),
   not --accent-ink: the pill is a fixed near-black floating dock in BOTH
   themes (not theme-following), so the icon always needs the bright tier —
   --accent-ink is tuned for light backgrounds, which this pill never has. */
.bottom-nav-svg-icon { color: var(--accent); }

.bottom-nav-label {
  /* §ED441 §7b / DELETED §ED451: `font-size: 9px` was here and resolved
     NOWHERE — the web renders 11px (css/global.css §ED441) and the shell 10px
     (css/app-nav.css). Both are inside the 10-12px band DESIGN-INVARIANTS
     licenses as the ONE named exception to the §ED306 type floor; 9px was
     below even that, and would have been a live violation the day it won. */
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  line-height: 1;
}

/* (Active indicator is now the outline box on .bottom-nav-item.is-active —
   the old ::before line was removed because its appear/disappear read as
   movement on tap.) */

/* Daily challenge badge — notification dot */
.bottom-nav-badge {
  position: absolute;
  top: 4px;
  right: calc(50% - 16px);
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--error);
  border: 1.5px solid var(--surface);
}

/* Numbered count badge (e.g. Review cards due) — top-right of the icon */
.bottom-nav-count {
  position: absolute;
  top: -6px;
  right: -10px;
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  border-radius: var(--r-full);
  background: var(--accent);
  color: var(--accent-text);
  border: 1.5px solid var(--surface);
  font-size: 10px;
  font-weight: 800;
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* Desktop replaces the pill with a fixed top nav instead (css/global.css
   .app-topnav) — same ≥900px breakpoint briefing.css's own "DESKTOP LAYER
   (WO-WEB-2)" already hides its self-contained .bottom-nav copy at. This is
   the missing half for pages that load THIS stylesheet's .bottom-nav
   definition instead (profile.html, settings.html) — components.css never had
   the override, so the pill floated on desktop everywhere those two pages
   were viewed (world-sweep components pass, 2026-07-21). */
@media (min-width: 900px) {
  .bottom-nav { display: none; }
}

/* ─────────────────────────────────────────────────────────── */
/* EMPTY STATE                                                  */
/* ─────────────────────────────────────────────────────────── */

.empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: var(--sp-12) var(--sp-6);
  gap: var(--sp-3);
}

.empty-state-icon {
  font-size: 48px;
  line-height: 1;
  animation: float 3s ease-in-out infinite;
}

.empty-state-title {
  font-family: var(--font-display);
  font-size: var(--text-xl);
  font-weight: 700;
  color: var(--text-primary);
}

.empty-state-body {
  font-size: var(--text-sm);
  color: var(--text-subtle);
  max-width: 260px;
  line-height: var(--leading-relaxed);
}

/* ─────────────────────────────────────────────────────────── */
/* TABS                                                         */
/* ─────────────────────────────────────────────────────────── */

.tabs {
  display: flex;
  background: var(--surface-2);
  border-radius: var(--r-full);
  padding: 3px;
  gap: 2px;
}

.tab-item {
  flex: 1;
  padding: var(--sp-2) var(--sp-3);
  border-radius: var(--r-full);
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--text-subtle);
  cursor: pointer;
  text-align: center;
  transition:
    background  var(--t-fast) var(--ease),
    color       var(--t-fast) var(--ease),
    box-shadow  var(--t-fast) var(--ease);
  white-space: nowrap;
  -webkit-tap-highlight-color: transparent;
}

.tab-item:hover:not(.is-active) {
  color: var(--text-muted);
  background: var(--surface-3);
}

.tab-item.is-active {
  background: var(--surface-4);
  color: var(--text-primary);
  box-shadow: var(--shadow-sm);
}

/* Scrollable tabs (for more than 3 items) */
.tabs-scroll {
  display: flex;
  gap: var(--sp-1);
  overflow-x: auto;
  scrollbar-width: none;
  padding-bottom: var(--sp-1);
}
.tabs-scroll::-webkit-scrollbar { display: none; }

/* ─────────────────────────────────────────────────────────── */
/* STAT TILE                                                    */
/* ─────────────────────────────────────────────────────────── */

.stat-tile {
  background: var(--surface-2);
  border-radius: var(--r-lg);
  border: 1px solid var(--border);
  padding: var(--sp-4);
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
}

.stat-value {
  font-family: var(--font-display);
  font-size: var(--text-2xl);
  font-weight: 700;
  color: var(--text-primary);
  line-height: var(--leading-tight);
}

.stat-value-sm {
  font-size: var(--text-xl);
}

.stat-label {
  font-size: var(--text-xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--text-subtle);
}

/* ─────────────────────────────────────────────────────────── */
/* LEVEL BADGE                                                  */
/* ─────────────────────────────────────────────────────────── */

.level-badge {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
  padding: var(--sp-1) var(--sp-3);
  background: var(--surface-3);
  border-radius: var(--r-full);
  border: 1px solid var(--border-strong);
  font-size: var(--text-xs);
  font-weight: 700;
  color: var(--text-muted);
}

.level-badge .level-number {
  color: var(--accent);
}

/* ─────────────────────────────────────────────────────────── */
/* TOOLTIP                                                      */
/* ─────────────────────────────────────────────────────────── */

[data-tooltip] {
  position: relative;
}

[data-tooltip]::after {
  content: attr(data-tooltip);
  position: absolute;
  bottom: calc(100% + 8px);
  left: 50%;
  transform: translateX(-50%);
  background: var(--surface-4);
  color: var(--text-primary);
  font-size: var(--text-xs);
  font-weight: 500;
  padding: var(--sp-1) var(--sp-2);
  border-radius: var(--r-sm);
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  transition: opacity var(--t-fast) var(--ease);
  z-index: var(--z-dropdown);
  border: 1px solid var(--border-strong);
  box-shadow: var(--shadow-md);
}

[data-tooltip]:hover::after,
[data-tooltip]:focus-visible::after {
  opacity: 1;
}

/* ─────────────────────────────────────────────────────────── */
/* CONFIRM DIALOG (simple yes/no modal)                         */
/* ─────────────────────────────────────────────────────────── */

.confirm-dialog .modal-body {
  font-size: var(--text-sm);
  color: var(--text-muted);
}

.confirm-dialog .modal-footer {
  flex-direction: row;
  gap: var(--sp-2);
}

.confirm-dialog .modal-footer .btn {
  flex: 1;
}

/* ─────────────────────────────────────────────────────────── */
/* CULTURE CARD (culture select screen)                         */
/* ─────────────────────────────────────────────────────────── */

.culture-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-xl);
  padding: var(--sp-5);
  cursor: pointer;
  position: relative;
  overflow: hidden;
  transition:
    border-color var(--t-base) var(--ease),
    box-shadow   var(--t-base) var(--ease),
    transform    var(--t-base) var(--ease);
  -webkit-tap-highlight-color: transparent;
}

.culture-card:hover:not(.is-locked) {
  border-color: var(--border-strong);
  box-shadow: var(--shadow-md);
  transform: translateY(-2px);
}

.culture-card.is-active {
  border-color: var(--culture-accent);
  box-shadow: 0 0 0 1px var(--culture-accent), var(--shadow-culture);
}

.culture-card.is-locked {
  opacity: 0.55;
  cursor: default;
}

.culture-card-flag {
  font-size: 40px;
  line-height: 1;
  margin-bottom: var(--sp-3);
}

.culture-card-name {
  font-family: var(--font-display);
  font-size: var(--text-xl);
  font-weight: 700;
  color: var(--text-primary);
  margin-bottom: var(--sp-1);
}

.culture-card-desc {
  font-size: var(--text-sm);
  color: var(--text-subtle);
  line-height: var(--leading-snug);
}

.culture-card-status {
  margin-top: var(--sp-4);
}

.culture-card-action {
  margin-top: var(--sp-3);
  font-size: var(--text-sm);
  font-weight: 700;
  color: var(--culture-accent, var(--accent));
  transition: letter-spacing var(--t-fast) var(--ease);
}
.culture-card:hover:not(.is-locked) .culture-card-action {
  letter-spacing: 0.02em;
}

/* Accent bar at bottom of culture card */
.culture-card::after {
  content: '';
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  height: 3px;
  background: var(--culture-accent, var(--border));
  opacity: 0;
  transition: opacity var(--t-base) var(--ease);
}
.culture-card.is-active::after,
.culture-card:hover:not(.is-locked)::after {
  opacity: 1;
}

/* ── Notification permission bottom sheet ─────────────────── */
/* Shared so it works on app.html (deferred from the first lesson) and lesson.html. */
.notif-sheet-backdrop {
  position: fixed;
  inset: 0;
  background: var(--scrim);
  z-index: 9200;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  padding: 0 0 var(--safe-bottom, 0px);
  opacity: 0;
  transition: opacity 0.3s ease;
}
.notif-sheet-backdrop.is-visible { opacity: 1; }
.notif-sheet {
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: 24px 24px 0 0;
  padding: 28px 24px 36px;
  width: 100%;
  max-width: 480px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  text-align: center;
  transform: translateY(100%);
  transition: transform 0.35s cubic-bezier(0.32, 0.72, 0, 1);
}
.notif-sheet-backdrop.is-visible .notif-sheet { transform: translateY(0); }
.notif-sheet-icon { font-size: 2.5rem; line-height: 1; }
.notif-sheet-title {
  font-family: var(--font-display);
  font-size: 1.25rem;
  font-weight: 700;
  color: var(--text-primary);
  line-height: 1.25;
  margin: 0;
  max-width: 300px;
}
.notif-sheet-body {
  font-size: 0.9rem;
  color: var(--text-subtle);
  line-height: 1.55;
  margin: 0;
  max-width: 320px;
}
.notif-sheet-actions {
  display: flex;
  flex-direction: column;
  gap: 10px;
  width: 100%;
  margin-top: 8px;
}

/* ─────────────────────────────────────────────────────────── */
/* COACH-MARK TOUR (Home + per-tab first-run tours)            */
/* Shared so every page can run window.Tour / app.js's tour.   */
/* ─────────────────────────────────────────────────────────── */

.home-tour {
  position: fixed;
  inset: 0;
  z-index: 9000;                 /* above top bar / bottom nav / modals */
  -webkit-tap-highlight-color: transparent;
}

/* The spotlight is a transparent rounded box; its huge spread shadow dims the
   whole screen EXCEPT the hole, highlighting the targeted element. */
.home-tour-spotlight {
  position: fixed;
  border-radius: 14px;
  box-shadow: 0 0 0 9999px var(--scrim);
  outline: 2px solid var(--accent-ink);
  pointer-events: none;
  transition: top 0.25s ease, left 0.25s ease, width 0.25s ease, height 0.25s ease;
}

.home-tour-pop {
  position: fixed;
  transform: translateX(-50%);
  width: min(340px, calc(100vw - 32px));
  background: var(--surface-2);
  border: 1px solid var(--border-strong);
  border-radius: 16px;
  box-shadow: var(--shadow-lg);
  padding: 16px 16px 14px;
  z-index: 9001;
}

.home-tour-text {
  margin: 0 0 12px;
  font-size: 15px;
  line-height: 1.45;
  font-weight: 600;
  color: var(--text-primary);
}

.home-tour-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}

.home-tour-skip {
  background: none;
  border: none;
  color: var(--text-subtle);
  font-family: var(--font-body);
  font-size: 13px;
  text-decoration: underline;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.home-tour-skip:hover { color: var(--text-primary); }

.home-tour-dots { display: inline-flex; gap: 5px; }
.home-tour-dots span {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--surface-4);
}
.home-tour-dots span.is-on { background: var(--accent); }

.home-tour-next {
  background: var(--accent);
  color: var(--accent-text);
  border: none;
  border-radius: 10px;
  padding: 9px 18px;
  font-family: var(--font-body);
  font-size: 14px;
  font-weight: 800;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: background var(--t-fast) var(--ease);
}
.home-tour-next:hover { background: var(--accent-hover); }

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

/* ── Shareable fact image (window.ShareCard) — branded, lime-on-dark ──────
   Rendered off-screen by html2canvas, then handed to the native share sheet.
   Used by Explore (fact deck) and Heritage (discovered facts). No emoji. */
.sharecard {
  width: 360px;
  box-sizing: border-box;
  background: #0e0e0e;            /* solid for a crisp html2canvas export */
  border: 2px solid var(--accent);
  border-radius: 28px;
  padding: 30px 28px 24px;
  display: flex;
  flex-direction: column;
  gap: 22px;
  min-height: 540px;
  font-family: var(--font-body);
}
.sc-top { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.sc-brand {
  font-family: 'Fraunces';
  font-size: 1.15rem; font-weight: 800; letter-spacing: 0.01em; color: var(--accent);
}
.sc-brand span { font-size: 0.6em; vertical-align: super; }
.sc-chip {
  font-size: 0.62rem; font-weight: 800; letter-spacing: 0.07em;
  color: var(--accent-text); background: var(--accent); border-radius: 999px;
  padding: 6px 12px; white-space: nowrap;
}
.sc-mid { flex: 1; display: flex; flex-direction: column; justify-content: center; gap: 14px; }
.sc-eyebrow {
  font-size: 0.72rem; font-weight: 800; letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--accent); margin: 0;
}
.sc-title {
  font-family: var(--font-display);
  font-size: 1.85rem; font-weight: 800; line-height: 1.12; letter-spacing: -0.01em;
  color: #ffffff; margin: 0;
}
.sc-body { font-size: 1.0625rem; line-height: 1.5; color: #d7d7d7; margin: 0; }
.sc-foot {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  border-top: 1px solid rgba(255, 255, 255, 0.1); padding-top: 16px;
}
.sc-src { font-size: 0.72rem; font-weight: 600; color: #7a7a7a; }
.sc-url { font-size: 0.84rem; font-weight: 800; letter-spacing: 0.03em; color: var(--accent); }

/* Rank / standing variant — big lime rank number, same branded shell. */
.sc-mid--rank { align-items: flex-start; }
.sc-rank-num {
  font-family: var(--font-numeric);
  font-size: 5.5rem; font-weight: 800; line-height: 0.95; letter-spacing: -0.03em;
  color: var(--accent); margin: 4px 0 0;
}
.sc-rank-sub { font-size: 1.0625rem; font-weight: 700; color: #d7d7d7; margin: 6px 0 0; }

/* ── Username helper (window.Username) — field + mandatory picker ───────── */
.uname-status { font-size: 0.8125rem; font-weight: 600; margin: 6px 0 0; min-height: 1.1em; line-height: 1.2; }
.uname-status.is-checking { color: var(--text-subtle); }
.uname-status.is-good { color: var(--text-primary); } /* dark-theme small text is never lime — Wave D follow-up; light stays --accent-ink below */
html.theme-light .uname-status.is-good { color: var(--accent-ink); }
.uname-status.is-bad  { color: #ff9a9a; }

.uname-input-wrap { position: relative; display: flex; align-items: center; }
.uname-input-wrap .uname-at {
  position: absolute; left: 14px; font-weight: 700; color: var(--text-subtle);
  font-family: var(--font-body); pointer-events: none;
}
.uname-input-wrap .uname-input { padding-left: 30px; }

/* Mandatory full-screen picker (OAuth signup + app-entry safety net) */
body.uname-locked { overflow: hidden; }
.uname-picker {
  position: fixed; inset: 0; z-index: 9800;
  display: flex; align-items: center; justify-content: center;
  padding: var(--sp-5);
  background: var(--bg);
}
.uname-card {
  width: 100%; max-width: 420px;
  display: flex; flex-direction: column;
}
.uname-title {
  font-family: var(--font-display);
  font-size: clamp(var(--text-xl), 6vw, var(--text-3xl));
  font-weight: 800; line-height: 1.1; color: var(--text-primary); margin: 0 0 var(--sp-2);
}
.uname-sub { font-size: var(--text-sm); color: var(--text-subtle); line-height: 1.5; margin: 0 0 var(--sp-5); }
.uname-save { margin-top: var(--sp-4); }

/* ── Nationality flag chip (vendored SVG; matches culture .scope-flag) ── */
.flag-chip {
  display: inline-flex; width: 22px; height: 15px; border-radius: 3px; overflow: hidden;
  box-shadow: 0 0 0 1px var(--border-strong); flex-shrink: 0; vertical-align: middle;
}
.flag-chip img, .flag-chip svg { display: block; width: 100%; height: 100%; object-fit: cover; }
.flag-chip-unknown { background: var(--surface-3); color: var(--text-subtle); }
.flag-chip-unknown svg { padding: 1px; object-fit: contain; }

/* ============================================================
   GAME SCREENS — shared top bar + option cards
   Reusable across the Quiz (lesson.html), Puzzle, and Rush.
   From the Claude Design "Culturily Quiz" (+ Light) export.
   Token-driven so dark + light both flow through; correct = lime,
   incorrect = red are brand-locked fixed values.
   ============================================================ */

/* ── Game top bar (X close · progress · hearts chip) ───────── */
.game-topbar {
  display: flex;
  align-items: center;
  gap: 14px;
  /* + status-bar inset on top (lesson.html + puzzle.html pin .game-topbar to top:0
     via .lesson-top-bar / .pz-topbar, both viewport-fit=cover). Shared → one place. */
  padding: calc(18px + var(--safe-top)) 20px 14px;
}

.game-close {
  flex: 0 0 36px;
  width: 36px;
  height: 36px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 10px;
  background: var(--surface-2);
  border: 1px solid var(--border-strong);
  color: var(--text-subtle);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: border-color var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
}
.game-close:hover,
.game-close:active { border-color: var(--text-disabled); color: var(--text-primary); }
.game-close svg { width: 15px; height: 15px; stroke: currentColor; fill: none; stroke-width: 2.4; stroke-linecap: round; }

.game-progress {
  flex: 1;
  height: 12px;
  background: var(--surface-2);
  border-radius: 9999px;
  overflow: hidden;
  position: relative;
}

.game-progress-fill {
  position: absolute;
  inset: 0;
  width: 0;
  background: var(--accent-grad-v);
  border-radius: 9999px;
  transition: width 0.4s var(--ease-out);
}
/* glossy highlight line on the fill */
.game-progress-fill::after {
  content: '';
  position: absolute;
  top: 2px;
  left: 6px;
  right: 6px;
  height: 3px;
  border-radius: 9999px;
  background: rgba(255,255,255,0.4);
}

.game-hearts {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 5px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 9999px;
  padding: 5px 10px 5px 8px;
}
.game-hearts-icon { font-size: 15px; line-height: 1; color: var(--hearts); }
.game-hearts.is-low .game-hearts-icon { color: var(--error); }
.game-hearts-count {
  font-family: var(--font-display);
  font-size: 13px;
  font-weight: 600;
  color: var(--text-primary);
  min-width: 10px;
  text-align: center;
}

/* ── Option card (A–D letter badge + label) ────────────────── */
/* Layout applies to BOTH the legacy `.option-grid` and the shared `.options-grid`
   container so every quiz surface (guest/lesson/daily/review/practice) gets the
   same column layout + gap. */
.option-grid,
.options-grid {
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.option-grid.is-true-false,
.options-grid.is-true-false {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
}

.option-card {
  display: flex;
  align-items: center;
  gap: 13px;
  width: 100%;
  text-align: left;
  padding: 15px 16px;
  border-radius: 16px;
  /* Elevation by luminance (raised surface-2) + a soft drop shadow & top sheen
     for depth — NOT a candy lip, lime stays exclusive to the primary CTA. */
  background: var(--surface-2);
  border: 1.5px solid var(--border);
  color: var(--text-primary);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  box-shadow: 0 2px 8px rgba(0,0,0,0.22), inset 0 1px 0 rgba(255,255,255,0.05);
  /* Spring-back on release for the satisfying "pop". */
  transition: border-color var(--t-fast) var(--ease), background var(--t-fast) var(--ease),
              transform 170ms cubic-bezier(.34,1.56,.64,1), box-shadow 120ms var(--ease);
}
html.theme-light .option-card { box-shadow: 0 2px 8px rgba(14,14,14,0.10), inset 0 1px 0 rgba(255,255,255,0.5); }
.option-card:hover:not(:disabled) { border-color: var(--border-accent); }
@media (hover: none) {
  .option-card:hover:not(:disabled) { border-color: var(--border); background: var(--surface-2); }
}
/* Press DOWN: sink into the page + flatten the shadow (the original lesson feel). */
.option-card:active:not(:disabled) {
  transform: translateY(2px) scale(0.99);
  box-shadow: 0 1px 3px rgba(0,0,0,0.28), inset 0 1px 0 rgba(255,255,255,0.04);
  transition: transform 80ms ease, box-shadow 80ms ease;
}
html.theme-light .option-card:active:not(:disabled) { box-shadow: 0 1px 3px rgba(14,14,14,0.12), inset 0 1px 0 rgba(255,255,255,0.4); }
.option-card:disabled { cursor: default; }

.option-card-key {
  flex: 0 0 30px;
  width: 30px;
  height: 30px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 9px;
  background: var(--surface-3);
  border: 1px solid var(--border);
  font-family: var(--font-display);
  font-size: 13px;
  font-weight: 600;
  color: var(--text-subtle);
  text-transform: uppercase;
}

.option-card-label {
  font-family: var(--font-body);
  font-size: 16px;
  font-weight: 600;
  line-height: 1.3;
  color: inherit;
}

/* Selected (pre-check, where used) */
.option-card.is-selected {
  border-color: color-mix(in srgb, var(--accent) 60%, transparent);
  background: color-mix(in srgb, var(--accent) 7%, transparent);
}
.option-card.is-selected .option-card-key {
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  color: var(--accent-ink);
  border-color: color-mix(in srgb, var(--accent) 40%, transparent);
}

/* Correct (the option the user CHOSE and got right) — OUTLINE highlight, NOT a
   solid fill: lime border + inset ring + a very subtle lime tint so it reads as a
   highlighted bubble on the dark surface, with readable (uninverted) text. Mirrors
   the .is-reveal outline language. Brand-locked values (token-immune). */
.option-card.is-correct {
  border-color: var(--accent);
  background: var(--accent-dim);
  color: var(--text-primary);
  box-shadow: 0 0 0 1px var(--accent) inset, 0 2px 10px color-mix(in srgb, var(--accent) 12%, transparent);
}
.option-card.is-correct .option-card-label { color: var(--text-primary); }
.option-card.is-correct .option-card-key {
  background: color-mix(in srgb, var(--accent) 16%, transparent);
  color: var(--accent-ink);
  border-color: color-mix(in srgb, var(--accent) 45%, transparent);
}
.option-card.is-correct::after {
  content: '';
  margin-left: auto;
  flex: 0 0 20px;
  width: 20px;
  height: 20px;
  background: var(--accent-ink);   /* lime check (outlined state — no solid fill behind it) */
  -webkit-mask: var(--ico-check) center / contain no-repeat;
  mask: var(--ico-check) center / contain no-repeat;
}

/* Reveal (the right answer shown after a WRONG pick) — OUTLINED, not solid, so it
   reads as "here's the correct one" rather than a celebration of the user's pick. */
.option-card.is-reveal {
  border-color: var(--accent);
  background: var(--accent-dim);
}
.option-card.is-reveal .option-card-key {
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  color: var(--accent-ink);
  border-color: color-mix(in srgb, var(--accent) 40%, transparent);
}
.option-card.is-reveal::after {
  content: '';
  margin-left: auto;
  flex: 0 0 20px;
  width: 20px;
  height: 20px;
  background: var(--accent-ink);
  -webkit-mask: var(--ico-check) center / contain no-repeat;
  mask: var(--ico-check) center / contain no-repeat;
}

/* Incorrect (the wrong option the user chose) — OUTLINE highlight, NOT a solid
   fill: red border + inset ring + a very subtle red tint, with readable text.
   Paired with the shake + haptic.error(). */
.option-card.is-wrong {
  border-color: var(--error);
  background: var(--error-dim);
  color: var(--text-primary);
  box-shadow: 0 0 0 1px var(--error) inset, 0 2px 10px color-mix(in srgb, var(--error) 12%, transparent);
}
.option-card.is-wrong .option-card-label { color: var(--text-primary); }
.option-card.is-wrong .option-card-key {
  background: color-mix(in srgb, var(--error) 16%, transparent);
  color: var(--error-ink);
  border-color: color-mix(in srgb, var(--error) 45%, transparent);
}
.option-card.is-wrong::after {
  content: '';
  margin-left: auto;
  flex: 0 0 19px;
  width: 19px;
  height: 19px;
  background: var(--error-ink);   /* red X (outlined state — no solid fill behind it) */
  -webkit-mask: var(--ico-x) center / contain no-repeat;
  mask: var(--ico-x) center / contain no-repeat;
}

/* Continue button now lives INSIDE the feedback panel, ABOVE the explanation, so
   it's tappable without scrolling past the "why it was right/wrong" text. Applies
   to every quiz surface's continue button (all carry .btn). */
.feedback-panel > .btn { width: 100%; margin: 10px 0 2px; }

/* Source citation under the explanation — credibility line, hidden when the fact
   has no source. Token-driven → both themes. */
.feedback-source {
  display: flex;
  align-items: center;
  gap: 6px;
  margin: 6px 0 0;
  font-size: 12px;
  line-height: 1.4;
  color: var(--text-subtle);
}
.feedback-source i { font-size: 13px; flex: 0 0 auto; opacity: 0.85; }
.feedback-source a {
  color: var(--text-muted);
  text-decoration: underline;
  text-underline-offset: 2px;
  word-break: break-word;
}
.feedback-source a:hover { color: var(--text-primary); }

/* Dimmed unchosen options after answering */
.option-card.is-dimmed { opacity: 0.4; }

/* Icon mask data-URIs (stroked check / X) */
:root {
  --ico-check: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12.5 L10 17 L19 7'/%3E%3C/svg%3E");
  --ico-x: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.8' stroke-linecap='round'%3E%3Cpath d='M6 6 L18 18 M18 6 L6 18'/%3E%3C/svg%3E");
}

/* ── True / False — two tall icon+label cards ──────────────── */
.option-card.option-card--tf {
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 12px;
  min-height: 150px;
  padding: 20px 12px;
  border-radius: 20px;
  text-align: center;
}
.option-card--tf .tf-icon {
  width: 52px;
  height: 52px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 15px;
  background: var(--surface-3);
  border: 1px solid var(--border);
  font-size: 26px;
  font-weight: 800;
  line-height: 1;
}
.option-card--tf .tf-label {
  font-family: var(--font-display);
  font-size: 20px;
  font-weight: 600;
  line-height: 1;
}
.option-card.tf-true  .tf-icon { color: var(--accent-ink); }
.option-card.tf-false .tf-icon { color: var(--error-ink); }
/* answered states own the colours */
.option-card--tf.is-correct .tf-icon,
.option-card--tf.is-reveal .tf-icon { color: var(--accent-ink); background: color-mix(in srgb, var(--accent) 14%, transparent); border-color: color-mix(in srgb, var(--accent) 40%, transparent); }
.option-card--tf.is-correct .tf-label,
.option-card--tf.is-reveal .tf-label { color: var(--accent-ink); }
.option-card--tf.is-wrong .tf-icon { color: var(--error-ink); background: color-mix(in srgb, var(--error) 14%, transparent); border-color: var(--error); }
.option-card--tf.is-wrong .tf-label { color: var(--error-ink); }
/* TF cards don't show the trailing check/X pseudo (icon already conveys it) */
.option-card--tf.is-correct::after,
.option-card--tf.is-reveal::after,
.option-card--tf.is-wrong::after { content: none; }

/* ── Letter slots + bank tiles (spell games: Puzzle, Rush) ──
   Token-driven so dark + light flow through; lime = correct, red = wrong. */
.letter-track {
  display: flex;
  flex-wrap: wrap;
  gap: 9px;
}

.letter-slot {
  width: 52px;
  height: 60px;
  flex: 0 0 52px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 13px;
  background: var(--surface-2);
  border: 1.5px dashed var(--border-strong);
  color: transparent;
  font-family: var(--font-display);
  font-size: 26px;
  font-weight: 600;
  line-height: 1;
  cursor: default;
  -webkit-tap-highlight-color: transparent;
  transition: border-color var(--t-fast) var(--ease), background var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
}

.letter-slot.is-filled {
  background: var(--surface-3);
  border: 1.5px solid color-mix(in srgb, var(--accent) 55%, transparent);
  color: var(--text-primary);
  cursor: pointer;
}
.letter-slot.is-correct {
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  border: 1.5px solid var(--accent);
  color: var(--accent-ink);
}
.letter-slot.is-wrong {
  background: color-mix(in srgb, var(--error) 10%, transparent);
  border: 1.5px solid var(--error);
  color: var(--error-ink);
}

/* word/hyphen gap between letter groups — a spacer, not a slot */
.letter-slot--sep {
  flex: 0 0 14px;
  width: 14px;
  height: 60px;
  background: none;
  border: none;
}

.letter-tile {
  min-width: 52px;
  height: 56px;
  padding: 0 10px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 13px;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  color: var(--text-primary);
  font-family: var(--font-display);
  font-size: 23px;
  font-weight: 600;
  line-height: 1;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: opacity var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease), transform var(--t-fast) var(--ease);
}
.letter-tile:hover:not(.is-used) { border-color: var(--border-accent); }
.letter-tile:active:not(.is-used) { transform: translateY(1px); }
.letter-tile.is-used {
  opacity: 0.3;
  cursor: default;
  pointer-events: none;
}

/* Full-color reward icons (window.rewardIcon) sit inline like a glyph. */
.reward-ico { display: inline-flex; align-items: center; justify-content: center; line-height: 0; }
.reward-ico svg { display: block; }
