/* ============================================================
   COMPONENTS.CSS — Cards, Buttons, Modals, Badges, Tables, Forms
   ============================================================ */

/* --- Cards --- */
.card {
  background: linear-gradient(135deg, rgba(99,102,241,0.08) 0%, rgba(6,182,212,0.04) 50%, transparent 100%), var(--bg-surface);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-lg);
  padding: var(--space-lg);
  box-shadow: inset 0 1px 1px rgba(255, 255, 255, 0.03), var(--shadow-sm);
  transition: all var(--transition-base);
  position: relative;
  overflow: hidden;
  cursor: pointer;
}

.card:active {
  transform: scale(0.985);
}

.card::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 3px;
  background: linear-gradient(90deg, var(--color-primary), var(--color-accent));
  opacity: 0;
  transition: opacity var(--transition-base);
}

.card:hover {
  background: linear-gradient(135deg, rgba(99,102,241,0.12) 0%, rgba(6,182,212,0.08) 50%, rgba(16,185,129,0.04) 100%), var(--bg-surface-hover);
  box-shadow: inset 0 1px 1px rgba(255, 255, 255, 0.08), 0 10px 20px rgba(99,102,241,0.1);
  transform: translateY(-3px);
  border-color: rgba(99,102,241,0.3);
}

.card:hover::before {
  opacity: 1;
}

.subfolder-card {
  padding: 1rem;
  background: linear-gradient(135deg, rgba(99,102,241,0.08) 0%, rgba(6,182,212,0.04) 50%, transparent 100%), var(--bg-surface);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-md);
  box-shadow: inset 0 1px 1px rgba(255, 255, 255, 0.03), var(--shadow-sm);
  transition: all var(--transition-base);
  cursor: pointer;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.subfolder-card:hover {
  background: linear-gradient(135deg, rgba(99,102,241,0.12) 0%, rgba(6,182,212,0.08) 50%, rgba(16,185,129,0.04) 100%), var(--bg-surface-hover);
  box-shadow: inset 0 1px 1px rgba(255, 255, 255, 0.08), 0 10px 20px rgba(99,102,241,0.1);
  transform: translateY(-2px);
  border-color: rgba(99,102,241,0.3);
}

.card-glass {
  background: var(--glass-bg);
  backdrop-filter: blur(var(--glass-blur));
  -webkit-backdrop-filter: blur(var(--glass-blur));
  border: 1px solid var(--glass-border);
  border-radius: var(--radius-lg);
  padding: var(--space-lg);
  box-shadow: var(--shadow-md);
}

.card-flat {
  background: var(--bg-surface);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-lg);
  padding: var(--space-lg);
}

/* --- Buttons --- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  padding: 0.625rem 1.25rem;
  font-family: var(--font-sans);
  font-size: 0.875rem;
  font-weight: 600;
  line-height: 1.25;
  border: none;
  border-radius: var(--radius-md);
  cursor: pointer;
  transition: all var(--transition-fast);
  white-space: nowrap;
  text-decoration: none;
  position: relative;
  overflow: hidden;
}

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

/* Primary Button */
.btn-primary {
  background: linear-gradient(135deg, var(--color-primary) 0%, var(--color-primary-dark) 100%);
  color: var(--text-on-primary);
  box-shadow: 0 2px 8px var(--color-primary-glow);
}

.btn-primary:hover {
  background: linear-gradient(135deg, var(--color-primary-hover) 0%, var(--color-primary) 100%);
  box-shadow: 0 4px 16px var(--color-primary-glow);
  transform: translateY(-1px);
}

/* Secondary Button */
.btn-secondary {
  background: var(--bg-surface-hover);
  color: var(--text-primary);
  border: 1px solid var(--border-color);
}

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

/* Danger Button */
.btn-danger {
  background: linear-gradient(135deg, var(--color-danger) 0%, var(--color-danger-hover) 100%);
  color: white;
}

.btn-danger:hover {
  box-shadow: 0 4px 12px rgba(239, 68, 68, 0.3);
  transform: translateY(-1px);
}

/* Ghost Button */
.btn-ghost {
  background: transparent;
  color: var(--text-secondary);
  padding: 0.5rem;
}

.btn-ghost:hover {
  background: var(--bg-surface-hover);
  color: var(--text-primary);
}

/* Accent / Practice Button */
.btn-practice {
  background: var(--bg-surface-hover);
  color: var(--color-primary);
  border: 1px solid var(--border-color);
  width: 100%;
}

.btn-practice:hover {
  background: var(--color-primary-subtle);
  border-color: var(--color-primary);
  box-shadow: 0 2px 8px var(--color-primary-glow);
}

/* Small Button */
.btn-sm {
  padding: 0.375rem 0.75rem;
  font-size: 0.8125rem;
}

/* Icon-only button */
.btn-icon {
  padding: 0.5rem;
  border-radius: var(--radius-md);
}

/* --- Badges & Tags --- */
.badge {
  display: inline-flex;
  align-items: center;
  padding: 0.1875rem 0.625rem;
  font-size: 0.6875rem;
  font-weight: 700;
  border-radius: var(--radius-full);
  letter-spacing: 0.02em;
  white-space: nowrap;
}

.badge-primary {
  background: var(--color-primary-subtle);
  color: var(--color-primary);
  border: 1px solid rgba(99, 102, 241, 0.2);
}

.badge-success {
  background: var(--color-success-bg);
  color: var(--color-success);
  border: 1px solid rgba(16, 185, 129, 0.2);
}

.badge-warning {
  background: var(--color-warning-bg);
  color: var(--color-warning);
  border: 1px solid rgba(245, 158, 11, 0.2);
}

.badge-danger {
  background: var(--color-danger-bg);
  color: var(--color-danger);
  border: 1px solid rgba(239, 68, 68, 0.2);
}

.badge-neutral {
  background: var(--bg-surface-hover);
  color: var(--text-tertiary);
  font-weight: 700;
}

/* Tag (removable badge) */
.tag {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  padding: 0.25rem 0.5rem;
  font-size: 0.75rem;
  font-weight: 600;
  border-radius: var(--radius-sm);
  background: var(--color-primary-subtle);
  color: var(--color-primary);
  border: 1px solid rgba(99, 102, 241, 0.15);
}

.tag button {
  background: none;
  border: none;
  color: inherit;
  cursor: pointer;
  padding: 0;
  display: flex;
  opacity: 0.6;
  transition: opacity var(--transition-fast);
}

.tag button:hover {
  opacity: 1;
  color: var(--color-danger);
}

/* --- Forms & Inputs --- */
.form-label {
  display: block;
  font-size: 0.6875rem;
  font-weight: 700;
  color: var(--text-tertiary);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  margin-bottom: 0.375rem;
}

.form-label-inline {
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-size: 0.6875rem;
  font-weight: 700;
  color: var(--text-tertiary);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  margin-bottom: 0.375rem;
}

.form-input {
  width: 100%;
  background: var(--bg-surface);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-md);
  padding: 0.625rem 0.75rem;
  font-family: var(--font-sans);
  font-size: 0.875rem;
  color: var(--text-primary);
  outline: none;
  transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}

.form-input:focus {
  border-color: var(--color-primary);
  box-shadow: none;
}

.form-input::placeholder {
  color: var(--text-tertiary);
}

.form-select {
  width: 100%;
  background: var(--bg-surface);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-md);
  padding: 0.625rem 0.75rem;
  font-family: var(--font-sans);
  font-size: 0.875rem;
  color: var(--text-primary);
  outline: none;
  cursor: pointer;
  transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}

.form-select:focus {
  border-color: var(--color-primary);
  box-shadow: none;
}

.form-textarea {
  width: 100%;
  background: var(--bg-surface);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-md);
  padding: 0.625rem 0.75rem;
  font-family: var(--font-sans);
  font-size: 0.875rem;
  color: var(--text-primary);
  outline: none;
  resize: vertical;
  min-height: 60px;
  transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}

.form-textarea:focus {
  border-color: var(--color-primary);
  box-shadow: none;
}

/* --- Tables --- */
.table-container {
  background: var(--bg-surface);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-lg);
  overflow: hidden;
  box-shadow: var(--shadow-sm);
}

.table {
  width: 100%;
  text-align: left;
  border-collapse: collapse;
}

.table thead {
  background: var(--bg-surface-hover);
  border-bottom: 1px solid var(--border-color);
}

.table th {
  padding: 0.875rem 1rem;
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--text-tertiary);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

/* ======================== PREMIUM NOTEBOOK CAROUSEL ======================== */
.premium-carousel-container {
  position: relative;
  width: 100%;
  margin-top: 1rem;
  padding: 1.5rem 0.5rem 1.25rem;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.85rem;
  background: radial-gradient(120% 80% at 50% 0%, rgba(99, 102, 241, 0.08) 0%, transparent 60%);
  border-radius: var(--radius-xl);
}

.carousel-title-main {
  font-size: 0.95rem;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.22em;
  color: var(--text-tertiary);
  text-align: center;
  display: flex;
  align-items: center;
  gap: 0.6rem;
  justify-content: center;
}
.carousel-title-main i {
  opacity: 0.8;
  animation: gentlePulse 3s ease-in-out infinite;
}

.carousel-viewport {
  position: relative;
  width: 100%;
  max-width: 720px;
  height: 210px;
  perspective: 1400px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.carousel-track {
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
  width: 100%;
  height: 100%;
  transform-style: preserve-3d;
}

.carousel-slide {
  position: absolute;
  width: 76%;
  max-width: 540px;
  height: 100%;
  border-radius: var(--radius-lg);
  overflow: hidden;
  transition:
    transform 0.55s cubic-bezier(0.22, 1, 0.36, 1),
    opacity 0.45s ease,
    box-shadow 0.45s ease;
  opacity: 0;
  pointer-events: none;
  transform: translateX(0) scale(0.82);
  background: var(--bg-elevated);
  border: 1px solid var(--border-color);
  box-shadow: var(--shadow-lg);
  cursor: pointer;
  will-change: transform, opacity;
}

.carousel-slide.active {
  opacity: 1;
  pointer-events: auto;
  transform: translateX(0) scale(1);
  z-index: 10;
  border-color: hsl(var(--nb-hue, 245) 70% 60% / 0.7);
  box-shadow:
    0 18px 50px -12px rgba(0, 0, 0, 0.7),
    0 0 0 1px hsl(var(--nb-hue, 245) 70% 60% / 0.25),
    0 0 40px -8px hsl(var(--nb-hue, 245) 80% 55% / 0.45);
}
.carousel-slide.active:hover {
  transform: translateX(0) scale(1.015) translateY(-3px);
}

.carousel-slide.prev {
  opacity: 0.45;
  transform: translateX(-46%) scale(0.8) rotateY(14deg);
  z-index: 5;
}
.carousel-slide.next {
  opacity: 0.45;
  transform: translateX(46%) scale(0.8) rotateY(-14deg);
  z-index: 5;
}
.carousel-slide.prev:hover,
.carousel-slide.next:hover {
  opacity: 0.7;
}

.carousel-slide-bg {
  position: absolute;
  inset: 0;
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  transition: transform 6s ease-out;
}
.carousel-slide.active .carousel-slide-bg.has-cover {
  transform: scale(1.08);
}
.carousel-slide-bg.no-cover {
  filter: saturate(1.05);
}

/* Large faint icon watermark for cover-less notebooks */
.carousel-slide-watermark {
  position: absolute;
  right: -12px;
  bottom: -20px;
  width: 150px !important;
  height: 150px !important;
  color: #fff;
  opacity: 0.12;
  transform: rotate(-8deg);
  pointer-events: none;
  z-index: 1;
}

.carousel-slide-scrim {
  position: absolute;
  inset: 0;
  background: linear-gradient(
    to top,
    rgba(7, 11, 22, 0.92) 0%,
    rgba(7, 11, 22, 0.45) 42%,
    rgba(7, 11, 22, 0.1) 100%
  );
  z-index: 1;
}

.carousel-slide-content {
  position: absolute;
  inset: 0;
  padding: 1.25rem 1.35rem;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  gap: 0.35rem;
  z-index: 2;
}

.carousel-badge {
  position: absolute;
  top: 0.9rem;
  left: 0.9rem;
  background: rgba(0, 0, 0, 0.45);
  border: 1px solid rgba(255, 255, 255, 0.18);
  color: #fff;
  padding: 0.25rem 0.6rem;
  border-radius: var(--radius-full);
  font-size: 0.625rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  backdrop-filter: blur(6px);
  display: flex;
  align-items: center;
  gap: 0.3rem;
  z-index: 2;
}
.carousel-badge i {
  width: 11px;
  height: 11px;
}

.carousel-item-title {
  font-size: 1.35rem;
  font-weight: 800;
  color: #fff;
  letter-spacing: 0.01em;
  line-height: 1.15;
  text-shadow: 0 2px 8px rgba(0, 0, 0, 0.6);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.carousel-item-subtitle {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  font-size: 0.75rem;
  font-weight: 600;
  color: rgba(255, 255, 255, 0.82);
  text-transform: none;
  letter-spacing: 0;
}
.carousel-item-subtitle span {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
}
.carousel-item-subtitle i {
  width: 13px;
  height: 13px;
  opacity: 0.8;
}

.carousel-open-cta {
  margin-top: 0.5rem;
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  align-self: flex-start;
  font-size: 0.75rem;
  font-weight: 700;
  color: #fff;
  background: hsl(var(--nb-hue, 245) 75% 55% / 0.95);
  padding: 0.35rem 0.75rem;
  border-radius: var(--radius-full);
  opacity: 0;
  transform: translateY(6px);
  transition:
    opacity 0.4s ease 0.1s,
    transform 0.4s ease 0.1s,
    filter 0.2s ease;
}
.carousel-slide.active .carousel-open-cta {
  opacity: 1;
  transform: translateY(0);
}
.carousel-slide.active:hover .carousel-open-cta {
  filter: brightness(1.12);
}
.carousel-open-cta i {
  width: 14px;
  height: 14px;
  transition: transform 0.25s ease;
}
.carousel-slide.active:hover .carousel-open-cta i {
  transform: translateX(3px);
}

.carousel-nav-btn {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 40px;
  height: 40px;
  background: var(--glass-bg, rgba(19, 28, 49, 0.8));
  border: 1px solid var(--border-color);
  border-radius: 50%;
  color: var(--text-primary);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  z-index: 20;
  transition:
    transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1),
    background 0.2s ease,
    box-shadow 0.2s ease,
    color 0.2s ease;
  backdrop-filter: blur(10px);
}
.carousel-nav-btn:hover {
  background: var(--color-primary);
  border-color: var(--color-primary);
  color: #fff;
  box-shadow: 0 0 18px var(--color-primary-glow);
  transform: translateY(-50%) scale(1.1);
}
.carousel-nav-btn:active {
  transform: translateY(-50%) scale(0.95);
}
.carousel-nav-btn i {
  width: 18px;
  height: 18px;
}
.carousel-nav-btn.prev-btn {
  left: 4px;
}
.carousel-nav-btn.next-btn {
  right: 4px;
}

.carousel-indicators {
  display: flex;
  gap: 0.4rem;
  margin-top: 0.25rem;
}
.carousel-dot {
  width: 18px;
  height: 4px;
  background: var(--border-color);
  border-radius: 2px;
  transition: all 0.3s ease;
  cursor: pointer;
}
.carousel-dot:hover {
  background: var(--text-tertiary);
}
.carousel-dot.active {
  background: var(--color-primary);
  width: 30px;
  box-shadow: 0 0 10px var(--color-primary-glow);
}

@media (max-width: 600px) {
  .carousel-slide {
    width: 88%;
  }
  .carousel-slide.prev,
  .carousel-slide.next {
    opacity: 0;
  }
  .carousel-item-title {
    font-size: 1.15rem;
  }
}

/* ---- Notebook cover-image uploader (admin form) ---- */
.nb-cover-field {
  width: 100%;
}
.nb-cover-drop {
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.35rem;
  padding: 1.25rem;
  border: 1.5px dashed var(--border-color);
  border-radius: var(--radius-md);
  background: var(--bg-surface);
  color: var(--text-secondary);
  cursor: pointer;
  transition:
    border-color 0.2s ease,
    background 0.2s ease,
    color 0.2s ease;
}
.nb-cover-drop:hover {
  border-color: var(--color-primary);
  color: var(--text-primary);
  background: var(--bg-surface-hover);
}
.nb-cover-drop i {
  width: 26px;
  height: 26px;
  color: var(--color-primary);
}
.nb-cover-drop small {
  color: var(--text-tertiary);
  font-size: 0.7rem;
}
.nb-cover-preview {
  position: relative;
  border-radius: var(--radius-md);
  overflow: hidden;
  border: 1px solid var(--border-color);
}
.nb-cover-preview img {
  display: block;
  width: 100%;
  max-height: 180px;
  object-fit: cover;
}
.nb-cover-actions {
  position: absolute;
  top: 0.5rem;
  right: 0.5rem;
  display: flex;
  gap: 0.4rem;
}
.nb-cover-actions .btn {
  backdrop-filter: blur(6px);
  background: rgba(0, 0, 0, 0.5);
  border-color: rgba(255, 255, 255, 0.2);
  color: #fff;
}
.nb-cover-remove:hover {
  background: var(--color-danger) !important;
  border-color: var(--color-danger) !important;
}

.table td {
  padding: 0.875rem 1rem;
  font-size: 0.875rem;
  border-bottom: 1px solid var(--border-color-subtle);
  vertical-align: middle;
}

.table tbody tr {
  transition: background-color var(--transition-fast);
}

.table tbody tr:hover {
  background: var(--bg-surface-hover);
}

.table tbody tr:last-child td {
  border-bottom: none;
}

/* --- Modals --- */
/* The shared prompt sits above any window that opened it — asking for a daily
   goal from inside the streak calendar put the input behind the calendar,
   because both are .modal-overlay at the same z-index and the calendar is
   appended later. */
#dialog-modal { z-index: 1200; }

.modal-overlay {
  position: fixed;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-lg);
  z-index: 1000;
}

.modal-overlay::before {
  content: '';
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.55);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  z-index: -1;
}

.modal-content {
  background: var(--bg-elevated);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-xl);
  max-width: 420px;
  width: 100%;
  padding: var(--space-xl);
  text-align: center;
  /* scaleIn used cubic-bezier(0.34, 1.56, 0.64, 1) — the 1.56 is an overshoot,
     so every dialog in the app bounced past its size and settled back, which
     read as a clunky pop. This rises and settles instead: a short travel, a
     small scale, and an expo-out curve that decelerates hard at the end. */
  animation: modalRise 0.34s cubic-bezier(0.16, 1, 0.3, 1) both;
}

/* A DIALOG MUST NEVER BE TALLER THAN THE SCREEN IT IS ON.
   The overlay centres with align-items:center, and a too-tall child in a
   centred flex box overflows BOTH ends -- the top goes above the viewport,
   where no amount of scrolling on the overlay can reach it. Measured on a
   360x640 phone: the Read Aloud panel stood 686px tall with its title 23px
   off the top of the screen and no way to bring it back.

   Cap the card and let it scroll itself. `auto` is inert for every dialog
   that already fits, so this changes nothing on a desktop. dvh follows the
   phone browser chrome as it slides away; the vh line above it is the
   fallback for anything that does not know dvh. */
.modal-content {
  max-height: calc(100vh - var(--space-lg) * 2);
  max-height: calc(100dvh - var(--space-lg) * 2);
  overflow-y: auto;
  overscroll-behavior: contain;
}

@keyframes modalRise {
  from { opacity: 0; transform: translateY(12px) scale(0.985); }
  to   { opacity: 1; transform: none; }
}

/* The scrim arrives with it rather than snapping on. */
.modal-overlay::before { animation: modalScrim 0.28s ease-out both; }
@keyframes modalScrim {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* Re-rendering the inside of an open dialog — paging a calendar, say — must
   not replay its entrance. */
.modal-content.no-entry { animation: none; }

@media (prefers-reduced-motion: reduce) {
  .modal-content, .modal-overlay::before { animation: none; }
}

.modal-content-lg {
  max-width: 520px;
}

.modal-icon {
  margin-bottom: var(--space-md);
  display: flex;
  justify-content: center;
}

.modal-icon-svg {
  width: 48px;
  height: 48px;
}

.modal-title {
  font-size: 1.5rem;
  font-weight: 800;
  margin-bottom: 0.375rem;
}

.modal-desc {
  color: var(--text-secondary);
  font-size: 0.9rem;
  margin-bottom: var(--space-xl);
  line-height: 1.5;
}

.modal-actions {
  display: flex;
  gap: var(--space-md);
}

.modal-actions .btn {
  flex: 1;
  padding: 0.75rem 1rem;
}

/* --- Dividers --- */
.divider {
  width: 100%;
  height: 1px;
  background: var(--border-color);
  margin: var(--space-md) 0;
}

/* A divider inside a flex column is a flex item, so its own margin ADDS to the
   column gap instead of collapsing into it: 16 + 14 put 29px of blank above
   and below a one-pixel rule, 58px in total to draw a hairline. A section break
   should read as bigger than the rhythm around it, not as a hole in the form.
   Written for any af-section-body, not just the variant form, because the same
   16px margin lands the same way in every one of them. */
#admin-variant-content .divider,
.af-section-body > .divider {
  margin: 0.25rem 0;
}

/* --- pulse Highlight Effect (Feature 3) --- */
@keyframes pulseHighlight {
  0% {
    background-color: rgba(99, 102, 241, 0.08);
  }

  50% {
    background-color: rgba(99, 102, 241, 0.25);
  }

  100% {
    background-color: rgba(99, 102, 241, 0.08);
  }
}

.pulse-highlight {
  animation: pulseHighlight 2.5s infinite ease-in-out;
}

.divider-vertical {
  width: 1px;
  height: 1.5rem;
  background: var(--border-color);
  margin: 0 var(--space-sm);
}

/* --- Empty States --- */
.empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: var(--space-2xl);
  color: var(--text-tertiary);
  text-align: center;
}

.empty-state svg {
  width: 48px;
  height: 48px;
  margin-bottom: var(--space-md);
  opacity: 0.4;
}

.empty-state h2 {
  font-size: 1.125rem;
  color: var(--text-secondary);
  margin-bottom: var(--space-xs);
}

.empty-state p {
  font-size: 0.875rem;
  opacity: 0.8;
}

/* --- Score Badges --- */
.score-badge {
  display: inline-flex;
  padding: 0.25rem 0.625rem;
  border-radius: var(--radius-sm);
  font-size: 0.75rem;
  font-weight: 700;
}

.score-perfect {
  background: var(--color-success-bg);
  color: var(--color-success);
}

.score-partial {
  background: var(--color-warning-bg);
  color: var(--color-warning);
}

/* --- Version Count Pill --- */
.version-pill {
  font-size: 0.625rem;
  font-weight: 700;
  background: var(--bg-surface-hover);
  color: var(--text-tertiary);
  padding: 0.25rem 0.5rem;
  border-radius: var(--radius-sm);
}

/* --- Category Section --- */
.category-section {
  margin-bottom: var(--space-xl);
}

.category-title {
  font-size: 1.125rem;
  font-weight: 600;
  color: var(--text-secondary);
  padding-bottom: 0.625rem;
  margin-bottom: var(--space-md);
  border-bottom: 1px solid var(--border-color);
}

/* --- Card Grid --- */
.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: var(--space-md);
}

/* --- Pagination Controls --- */
.pagination-bar {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.375rem;
  margin-top: 1.25rem;
  padding: 0.5rem 0;
  user-select: none;
}

.pagination-bar .page-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 2rem;
  height: 2rem;
  padding: 0 0.5rem;
  border-radius: var(--radius-md);
  border: 1px solid var(--border-color);
  background: transparent;
  color: var(--text-secondary);
  font-size: 0.8rem;
  font-weight: 600;
  cursor: pointer;
  transition: all 0.18s ease;
}

.pagination-bar .page-btn:hover:not(.active):not(:disabled) {
  background: var(--bg-surface-hover);
  border-color: var(--color-primary);
  color: var(--color-primary);
}

.pagination-bar .page-btn.active {
  background: var(--color-primary);
  border-color: var(--color-primary);
  color: #fff;
  pointer-events: none;
}

.pagination-bar .page-btn:disabled {
  opacity: 0.35;
  cursor: not-allowed;
}

.pagination-bar .page-btn.page-arrow {
  font-size: 1rem;
  font-weight: 700;
}

.pagination-bar .page-ellipsis {
  color: var(--text-tertiary);
  font-size: 0.8rem;
  padding: 0 0.25rem;
  pointer-events: none;
}

/* --- Category List Item (Admin) --- */
.category-item {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0.625rem 0.75rem;
  background: var(--bg-surface-hover);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-md);
  color: var(--text-primary);
  font-size: 0.9rem;
}

/* --- Difficulty Tier Badges --- */
.tier-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0.125rem 0.5rem;
  font-size: 0.6rem;
  font-weight: 800;
  border-radius: var(--radius-full);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  flex-shrink: 0;
  line-height: 1.4;
  white-space: nowrap;
  transition: all var(--transition-fast);
}

.tier-s { background: linear-gradient(135deg, #FFD700, #FFA500); color: #1a1a1a; box-shadow: 0 0 8px rgba(255,215,0,0.4); }
.tier-a { background: linear-gradient(135deg, #EF4444, #DC2626); color: #FFFFFF; box-shadow: 0 0 8px rgba(239,68,68,0.3); }
.tier-b { background: linear-gradient(135deg, #F97316, #EA580C); color: #1a1a1a; box-shadow: 0 0 8px rgba(249,115,22,0.3); }
.tier-c { background: linear-gradient(135deg, #3B82F6, #2563EB); color: #FFFFFF; box-shadow: 0 0 8px rgba(59,130,246,0.3); }
.tier-d { background: linear-gradient(135deg, #8B5CF6, #7C3AED); color: #FFFFFF; box-shadow: 0 0 8px rgba(139,92,246,0.3); }
.tier-e { background: linear-gradient(135deg, #94A3B8, #64748B); color: #1a1a1a; }

.tier-select {
  background: transparent;
  border: 1px solid var(--border-color);
  border-radius: var(--radius-sm);
  color: var(--text-secondary);
  font-size: 0.6875rem;
  font-weight: 600;
  padding: 0.125rem 0.25rem;
  cursor: pointer;
  outline: none;
  transition: all var(--transition-fast);
  flex-shrink: 0;
}

.tier-select:focus {
  border-color: var(--color-primary);
  box-shadow: 0 0 0 2px var(--color-primary-glow);
}

/* --- Mindmap Overlay --- */
.mindmap-overlay {
  position: fixed;
  inset: 0;
  z-index: 900;
  display: flex;
  align-items: center;
  justify-content: center;
}

.mindmap-overlay::before {
  content: '';
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.7);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  z-index: -1;
}

.mindmap-overlay.hidden { display: none; }

.mindmap-panel {
  width: 95%;
  height: 90%;
  max-width: 1400px;
  background: var(--bg-elevated);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-xl);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  animation: scaleIn 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}

.mindmap-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 1rem 1.5rem;
  border-bottom: 1px solid var(--border-color);
  background: var(--bg-surface);
}

.mindmap-header h2 {
  font-size: 1.125rem;
  font-weight: 800;
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.mindmap-body {
  flex: 1;
  overflow: auto;
  padding: 2rem;
  position: relative;
}

.mindmap-scope-tabs {
  display: flex;
  gap: 0.5rem;
}

.mindmap-scope-tab {
  padding: 0.375rem 0.875rem;
  font-size: 0.8125rem;
  font-weight: 600;
  border: 1px solid var(--border-color);
  border-radius: var(--radius-full);
  background: transparent;
  color: var(--text-secondary);
  cursor: pointer;
  transition: all var(--transition-fast);
}

.mindmap-scope-tab:hover { background: var(--bg-surface-hover); }
.mindmap-scope-tab.active {
  background: var(--color-primary-subtle);
  color: var(--color-primary);
  border-color: var(--color-primary);
}

/* Mindmap Tree Nodes */
.mm-tree { padding-left: 0; }
.mm-tree ul { padding-left: 1.5rem; border-left: 2px solid var(--border-color); margin-left: 0.75rem; }

.mm-node {
  list-style: none;
  margin: 0.375rem 0;
}

.mm-node-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 0.75rem;
  border-radius: var(--radius-md);
  cursor: pointer;
  transition: all var(--transition-fast);
  border: 1px solid transparent;
  position: relative;
}

.mm-node-row:hover {
  background: var(--bg-surface-hover);
  border-color: var(--border-color);
}

.mm-node-row.mm-folder { font-weight: 700; }
.mm-node-row.mm-item { font-weight: 500; color: var(--text-secondary); }

.mm-node-row .mm-icon {
  width: 16px; height: 16px; flex-shrink: 0;
}

.mm-node-row.mm-folder .mm-icon { color: var(--color-accent); }
.mm-node-row.mm-item .mm-icon { color: var(--text-tertiary); }

.mm-chevron {
  width: 14px; height: 14px;
  color: var(--text-tertiary);
  transition: transform 0.2s ease;
  flex-shrink: 0;
  cursor: pointer;
}

.mm-chevron.expanded { transform: rotate(90deg); }
.mm-chevron.invisible { visibility: hidden; }

.mm-node-label {
  flex: 1;
  font-size: 0.875rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.mm-node-meta {
  font-size: 0.6875rem;
  color: var(--text-tertiary);
  flex-shrink: 0;
}

.mm-children.collapsed { display: none; }

.mm-add-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  margin: 0.25rem 0 0.25rem 2.25rem;
  padding: 0.25rem 0.5rem;
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--text-tertiary);
  cursor: pointer;
  border-radius: var(--radius-sm);
  border: 1px dashed var(--border-color);
  background: transparent;
  transition: all var(--transition-fast);
}

.mm-add-btn:hover {
  color: var(--color-primary);
  border-color: var(--color-primary);
  background: var(--color-primary-subtle);
}

/* --- Sample Box --- */
.sample-box {
  background: var(--bg-surface-hover);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-md);
  padding: var(--space-md);
}

.sample-box-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: var(--space-sm);
}

.sample-item {
  display: flex;
  gap: var(--space-sm);
  align-items: flex-start;
  background: var(--bg-surface);
  padding: var(--space-md);
  border-radius: var(--radius-md);
  border: 1px solid var(--border-color);
  box-shadow: var(--shadow-sm);
}

/* --- Variant Tabs --- */
.variant-tabs {
  display: flex;
  gap: 0.5rem;
  overflow-x: auto;
  /* The strip's own padding, its margin, and the content's top margin were
     stacking to 36px of blank between the version tab and the first field.
     The column this sits in already spaces its children, so the strip carries
     none of its own — the tab now sits the same 14px above the first field
     that every gap in Basics uses. An overflowing strip still gets its
     scrollbar; `auto` gives that its own layout space rather than overlaying
     the pills. */
  padding-bottom: 0;
  margin-bottom: 0;
}

.variant-tab {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 1rem;
  font-size: 0.875rem;
  font-weight: 600;
  white-space: nowrap;
  cursor: pointer;
  border-bottom: 2px solid transparent;
  color: var(--text-tertiary);
  transition: all var(--transition-fast);
  background: none;
  border-top: none;
  border-left: none;
  border-right: none;
  font-family: var(--font-sans);
}

.variant-tab:hover {
  color: var(--text-primary);
}

.variant-tab.active {
  border-bottom-color: var(--color-primary);
  color: var(--color-primary);
}

.variant-tab-close {
  color: var(--text-tertiary);
  cursor: pointer;
  opacity: 0.6;
  transition: all var(--transition-fast);
  background: none;
  border: none;
  padding: 0;
  display: flex;
}

.variant-tab-close:hover {
  opacity: 1;
  color: var(--color-danger);
}

/* --- Locked Skill Tree Folders --- */
.folder-locked {
  opacity: 0.6;
  pointer-events: none;
  /* Prevents opening */
}

.folder-locked .snippet-folder-summary {
  color: var(--text-tertiary);
}

.lock-icon {
  width: 14px;
  height: 14px;
  color: var(--color-warning);
  margin-left: 0.5rem;
}

/* ============================================================
   ANALYTICS STYLES — now .ac-*, in css/analytics-pro.css
   ------------------------------------------------------------
   This said the dual-pane styles had been replaced by .ax-*. Those went
   the same way: 90 rules and 14KB of .ax-* survived here after nothing
   referenced them any more -- verified by searching for the prefix as a
   literal AND as a constructed name, and by checking it was not simply
   renamed .ac-* (no name overlaps, and .ac- is used 66 times in JS while
   .ax- is used none).
   ============================================================ */

/* ============================================================
   RESULT MODAL BADGES
   ============================================================ */
.result-badges-container {
  margin-top: 1.25rem;
  padding: 1rem;
  background: var(--color-primary-subtle);
  border: 1px solid rgba(99, 102, 241, 0.2);
  border-radius: var(--radius-md);
  text-align: left;
}

.result-badges-title {
  font-weight: 700;
  font-size: 0.875rem;
  color: var(--color-primary);
  margin-bottom: 0.75rem;
}

.result-badge-item {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  padding: 0.5rem 0;
  border-top: 1px solid rgba(99, 102, 241, 0.1);
}

.result-badge-item:first-of-type {
  border-top: none;
  padding-top: 0;
}

.result-badge-icon {
  font-size: 1.5rem;
  flex-shrink: 0;
}

.result-badge-name {
  font-weight: 700;
  font-size: 0.875rem;
  color: var(--text-primary);
}

.result-badge-desc {
  font-size: 0.75rem;
  color: var(--text-secondary);
}

/* ============================================================
   HISTORY BULK ACTIONS
   ============================================================ */
.bulk-checkbox-col {
  display: none;
  width: 40px;
}

.show-bulk-actions .bulk-checkbox-col {
  display: table-cell;
}

.bulk-delete-btn {
  display: none;
}

.show-bulk-actions~.bulk-delete-btn,
.show-bulk-actions+.bulk-delete-btn {
  display: inline-flex;
}

/* ============================================================
   NOTES / ANSWER SHEET SYSTEM
   ============================================================ */
.ns-section-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0.75rem 1rem;
  background: var(--bg-surface-hover);
  border-bottom: 1px solid var(--border-color);
  gap: 0.5rem;
}

.ns-section-title {
  font-weight: 700;
  font-size: 1rem;
  color: var(--text-primary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.ns-label-input {
  height: 30px;
  padding: 0.25rem 0.5rem;
  font-size: 0.875rem;
  font-weight: 600;
  max-width: 200px;
}

.ns-section-body {
  padding: 1rem;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.ns-controls-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.5rem;
  padding-bottom: 0.5rem;
  border-bottom: 1px solid var(--border-color-subtle);
}

.ns-questions-grid {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}

.ns-question-row {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  padding: 0.375rem 0.5rem;
  border-radius: var(--radius-sm);
  transition: background var(--transition-fast);
}

.ns-question-row:hover {
  background: var(--bg-surface-hover);
}

.ns-q-num {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  font-weight: 700;
  color: var(--text-tertiary);
  width: 28px;
  text-align: right;
  flex-shrink: 0;
}

.ns-bubbles {
  display: flex;
  gap: 0.375rem;
}

.ns-bubble {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  border: 2px solid var(--border-color);
  background: var(--bg-surface);
  color: var(--text-secondary);
  font-size: 0.75rem;
  font-weight: 700;
  font-family: var(--font-sans);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: all var(--transition-fast);
  padding: 0;
}

.ns-bubble:hover {
  border-color: var(--color-primary);
  color: var(--color-primary);
  background: var(--color-primary-subtle);
  transform: scale(1.1);
}

.ns-bubble.selected {
  background: var(--color-primary);
  border-color: var(--color-primary);
  color: white;
  box-shadow: 0 2px 8px var(--color-primary-glow);
}

.ns-bubble.correct {
  background: var(--color-success) !important;
  border-color: var(--color-success) !important;
  color: white !important;
  box-shadow: 0 2px 8px rgba(16, 185, 129, 0.3);
}

.ns-bubble.wrong {
  background: var(--color-danger) !important;
  border-color: var(--color-danger) !important;
  color: white !important;
  box-shadow: 0 2px 8px rgba(239, 68, 68, 0.3);
  animation: shake 0.3s ease;
}

.ns-bubble.expected {
  border-color: var(--color-success) !important;
  color: var(--color-success) !important;
  background: var(--color-success-bg) !important;
  border-style: dashed;
}

@keyframes shake {

  0%,
  100% {
    transform: translateX(0);
  }

  25% {
    transform: translateX(-3px);
  }

  75% {
    transform: translateX(3px);
  }
}

.ns-key-row {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  padding-top: 0.5rem;
  border-top: 1px solid var(--border-color-subtle);
}

.ns-key-textarea {
  font-family: var(--font-mono);
  font-size: 0.8125rem;
  min-height: 80px;
  line-height: 1.6;
}

.ns-score-display {
  text-align: center;
  font-size: 1.125rem;
  font-weight: 800;
  padding: 0.75rem;
  border-radius: var(--radius-md);
  background: var(--color-warning-bg);
  color: var(--color-warning);
  border: 1px solid rgba(245, 158, 11, 0.2);
}

.ns-score-display.ns-score-perfect {
  background: var(--color-success-bg);
  color: var(--color-success);
  border-color: rgba(16, 185, 129, 0.2);
}

/* ============================================================
   NOTEBOOK PRACTICE SYSTEM
   ============================================================ */

.np-sidebar-tab {
  display: block;
  width: 100%;
  padding: 0.75rem 1rem;
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  color: var(--text-secondary);
  cursor: pointer;
  transition: all var(--transition-fast);
  text-align: left;
}

.np-sidebar-tab:hover {
  background: var(--bg-surface-hover);
  color: var(--text-primary);
}

.np-sidebar-tab.active {
  background: var(--color-primary-subtle);
  color: var(--color-primary);
  border-color: var(--color-primary);
}

.np-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(40px, 1fr));
  gap: 0.5rem;
}

.np-grid-box {
  aspect-ratio: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-mono);
  font-weight: 700;
  font-size: 0.875rem;
  border-radius: var(--radius-sm);
  border: 2px solid var(--border-color);
  background: var(--bg-surface);
  color: var(--text-tertiary);
  cursor: pointer;
  transition: all var(--transition-fast);
}

.np-grid-box:hover {
  border-color: var(--color-primary);
  color: var(--color-primary);
}

/* Unopened is default style */
/* Opened but unanswered (red) */
.np-grid-box.opened {
  border-color: var(--color-danger);
  color: var(--color-danger);
  background: var(--color-danger-bg);
}

/* Answered (green) */
.np-grid-box.answered {
  border-color: var(--color-success);
  color: var(--color-success);
  background: var(--color-success-bg);
}

/* Active current question */
.np-grid-box.active {
  box-shadow: 0 0 0 3px var(--bg-body), 0 0 0 5px var(--color-primary);
  border-color: var(--color-primary) !important;
  color: var(--color-primary) !important;
  background: var(--bg-surface) !important;
}

.np-grid-box.answered.active {
  background: var(--color-success-bg) !important;
  border-color: var(--color-success) !important;
  color: var(--color-success) !important;
  box-shadow: 0 0 0 3px var(--bg-body), 0 0 0 5px var(--color-success);
}

.np-big-bubble {
  width: 64px;
  height: 64px;
  font-size: 1.5rem;
  border-width: 3px;
}

/* ============================================================
   NOTES SIDEBAR ITEMS (Training Grounds — Notes Tab)
   ============================================================ */
.sidebar-category-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0.5rem 0.75rem;
  font-size: 0.6875rem;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-tertiary);
  cursor: pointer;
  user-select: none;
  transition: all var(--transition-fast);
  border-radius: var(--radius-sm);
}

.sidebar-category-header:hover {
  color: var(--text-primary);
  background: var(--bg-surface-hover);
}

.sidebar-category-list {
  display: flex;
  flex-direction: column;
  gap: 0.125rem;
  padding-left: 0.5rem;
  margin-top: 0.25rem;
  margin-bottom: 0.75rem;
  border-left: 1px solid var(--border-color);
  margin-left: 0.75rem;
}

.sidebar-item {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  padding: 0.625rem 0.75rem;
  border-radius: var(--radius-md);
  color: var(--text-secondary);
  cursor: pointer;
  transition: all var(--transition-fast);
  font-size: 0.875rem;
  font-weight: 500;
  border: 1px solid transparent;
  position: relative;
  overflow: hidden;
}

.sidebar-item:hover {
  background: rgba(148, 163, 184, 0.15);
  color: var(--text-primary);
}

.sidebar-item.active {
  background: radial-gradient(circle at left, rgba(34, 211, 238, 0.15) 0%, transparent 80%), var(--bg-nav);
  color: #ffffff;
  border-color: transparent;
}

.sidebar-item.active::before {
  content: '';
  position: absolute;
  left: 0;
  top: 15%;
  bottom: 15%;
  width: 3px;
  background: #22d3ee;
  box-shadow: 2px 0 8px #22d3ee;
  border-radius: 0 4px 4px 0;
}

.sidebar-item-title {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ============================================================
   NOTES-PRACTICE DARK THEME (matches practice.html)
   ============================================================ */
.np-sidebar-tab-dark {
  display: flex;
  flex-direction: column;
  width: 100%;
  padding: 0.625rem 0.875rem;
  background: transparent;
  border: 1px solid #21262d;
  border-radius: var(--radius-md);
  color: #b1bac4;
  cursor: pointer;
  transition: all var(--transition-fast);
  text-align: left;
  font-family: var(--font-sans);
  font-size: 0.875rem;
}

.np-sidebar-tab-dark:hover {
  background: rgba(148, 163, 184, 0.1);
  color: #e6edf3;
  border-color: #30363d;
}

.np-sidebar-tab-dark.active {
  background: rgba(99, 102, 241, 0.12);
  color: var(--color-primary);
  border-color: var(--color-primary);
  box-shadow: 0 0 8px var(--color-primary-glow);
}

/* Dark-themed grid boxes for notes practice */
.practice-sidebar .np-grid-box {
  background: #0d1117;
  border-color: #21262d;
  color: #8b949e;
}

.practice-sidebar .np-grid-box:hover {
  border-color: var(--color-primary);
  color: var(--color-primary);
}

.practice-sidebar .np-grid-box.opened {
  background: rgba(139, 148, 158, 0.1);
  border-color: #30363d;
  color: #8b949e;
}

.practice-sidebar .np-grid-box.answered {
  background: rgba(16, 185, 129, 0.1);
  border-color: var(--color-success);
  color: var(--color-success);
}

.practice-sidebar .np-grid-box.active {
  box-shadow: 0 0 0 3px #0d1117, 0 0 0 5px var(--color-primary);
  border-color: var(--color-primary) !important;
  color: var(--color-primary) !important;
  background: #0d1117 !important;
}

.practice-sidebar .np-grid-box.answered.active {
  background: rgba(16, 185, 129, 0.1) !important;
  border-color: var(--color-success) !important;
  color: var(--color-success) !important;
  box-shadow: 0 0 0 3px #0d1117, 0 0 0 5px var(--color-success);
}

/* Dark-themed answer bubbles */
[data-theme="dark"] .practice-body .ns-bubble {
  color: rgba(255, 255, 255, 0.85);
}

[data-theme="dark"] .practice-body .ns-bubble:hover {
  color: #ffffff;
}

[data-theme="dark"] .practice-body .ns-bubble.selected {
  background: var(--color-primary);
  border-color: var(--color-primary);
  color: white;
  box-shadow: 0 2px 12px var(--color-primary-glow);
}

/* ============================================================
   ANALYTICS COMMAND CENTER (Visual Rework)
   ============================================================ */
.analytics-dashboard {
  padding-bottom: 4rem !important;
  position: relative;
  background-color: transparent !important;
  border: none !important;
}


@keyframes shimmerGold {
  0% {
    transform: translateX(-100%) rotate(45deg);
  }

  100% {
    transform: translateX(100%) rotate(45deg);
  }
}

/* Stagger Animations */
.stagger-1 {
  animation: fadeInUp 0.5s ease-out 0.1s both;
}

.stagger-2 {
  animation: fadeInUp 0.5s ease-out 0.2s both;
}

.stagger-3 {
  animation: fadeInUp 0.5s ease-out 0.3s both;
}

.stagger-4 {
  animation: fadeInUp 0.5s ease-out 0.4s both;
}

.stagger-5 {
  animation: fadeInUp 0.5s ease-out 0.5s both;
}

.stagger-6 {
  animation: fadeInUp 0.5s ease-out 0.6s both;
}

.stagger-7 {
  animation: fadeInUp 0.5s ease-out 0.7s both;
}

/* ============================================================
   NOTES PRACTICE — REVIEW MODE
   ============================================================ */

/* Review topbar */
.np-review-topbar {
  background: linear-gradient(135deg, #0d1117 0%, #161b22 100%);
}

/* Review badges (shown under question heading) */
.np-review-badge {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 1rem;
  border-radius: var(--radius-full);
  font-size: 0.8125rem;
  font-weight: 700;
}

.np-review-badge-correct {
  background: rgba(16, 185, 129, 0.15);
  color: #10b981;
  border: 1px solid rgba(16, 185, 129, 0.3);
}

.np-review-badge-wrong {
  background: rgba(239, 68, 68, 0.15);
  color: #ef4444;
  border: 1px solid rgba(239, 68, 68, 0.3);
}

.np-review-badge-skipped {
  background: rgba(148, 163, 184, 0.1);
  color: #8b949e;
  border: 1px solid rgba(148, 163, 184, 0.2);
}

/* Review mode bubbles */
.np-dark-bubble.review-locked {
  cursor: default;
  opacity: 0.5;
}

.np-dark-bubble.review-correct {
  background: rgba(16, 185, 129, 0.2) !important;
  border-color: #10b981 !important;
  color: #10b981 !important;
  opacity: 1 !important;
  box-shadow: 0 0 12px rgba(16, 185, 129, 0.3);
}

.np-dark-bubble.review-wrong {
  background: rgba(239, 68, 68, 0.2) !important;
  border-color: #ef4444 !important;
  color: #ef4444 !important;
  opacity: 1 !important;
  box-shadow: 0 0 12px rgba(239, 68, 68, 0.3);
}

.np-dark-bubble.review-expected {
  background: transparent !important;
  border-color: #fbbf24 !important;
  color: #fbbf24 !important;
  opacity: 1 !important;
  border-style: dashed;
  animation: pulseExpected 2s infinite;
}

@keyframes pulseExpected {

  0%,
  100% {
    box-shadow: 0 0 0 0 rgba(251, 191, 36, 0.4);
  }

  50% {
    box-shadow: 0 0 12px 4px rgba(251, 191, 36, 0.2);
  }
}

/* Review mode grid boxes */
.np-grid-box.review-correct {
  background: rgba(16, 185, 129, 0.15) !important;
  border-color: #10b981 !important;
  color: #10b981 !important;
}

.np-grid-box.review-wrong {
  background: rgba(239, 68, 68, 0.15) !important;
  border-color: #ef4444 !important;
  color: #ef4444 !important;
}

.np-grid-box.review-skipped {
  background: rgba(148, 163, 184, 0.05) !important;
  border-color: #30363d !important;
  color: #6e7681 !important;
}

.np-grid-box.review-correct.active {
  box-shadow: 0 0 0 3px #0d1117, 0 0 0 5px #10b981 !important;
}

.np-grid-box.review-wrong.active {
  box-shadow: 0 0 0 3px #0d1117, 0 0 0 5px #ef4444 !important;
}

.np-grid-box.review-skipped.active {
  box-shadow: 0 0 0 3px #0d1117, 0 0 0 5px #6e7681 !important;
}

/* --- GitHub-Style Heatmap --- */
.heatmap-container {
  background: var(--bg-surface);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-md);
  padding: var(--space-md);
  margin-bottom: 0;
  overflow-x: auto;
}

.heatmap-grid {
  display: grid;
  grid-template-rows: repeat(7, 1fr);
  grid-auto-flow: column;
  gap: 4px;
  min-width: max-content;
}

.heatmap-cell {
  width: 12px;
  height: 12px;
  border-radius: 2px;
  background: var(--bg-surface-hover);
  transition: transform 0.1s;
}

.heatmap-cell:hover {
  transform: scale(1.2);
  outline: 1px solid var(--border-color);
}

/* Heatmap Colors (Dark/Light mode adaptive) */
.heatmap-cell[data-level="1"] {
  background: rgba(16, 185, 129, 0.3);
}

.heatmap-cell[data-level="2"] {
  background: rgba(16, 185, 129, 0.6);
}

.heatmap-cell[data-level="3"] {
  background: rgba(16, 185, 129, 0.85);
}

.heatmap-cell[data-level="4"] {
  background: #10b981;
}

/* ============================================================
   ANALYTICS DUAL-PANE STYLES
   ============================================================ */
.analytics-section-label {
  font-size: 0.6875rem;
  font-weight: 700;
  color: var(--text-tertiary);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  margin-bottom: 0.75rem;
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

/* Sits at the end of the section label, so it reads as part of the heading
   rather than as another control competing with the badges under it. */
.badge-see-all {
  margin-left: auto;
  padding: 0;
  border: none;
  background: none;
  font: inherit;
  letter-spacing: inherit;
  color: var(--color-primary, #6366f1);
  cursor: pointer;
}
.badge-see-all:hover { text-decoration: underline; }

.analytics-stat-card {
  background: var(--bg-surface-hover);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-md);
  padding: 0.875rem;
  text-align: center;
  transition: all var(--transition-fast);
}

.analytics-stat-card:hover {
  border-color: var(--color-primary);
  box-shadow: 0 2px 8px var(--color-primary-glow);
}

.analytics-stat-value {
  font-size: 1.25rem;
  font-weight: 800;
  color: var(--text-primary);
  margin-bottom: 0.25rem;
}

.analytics-stat-label {
  font-size: 0.6875rem;
  font-weight: 600;
  color: var(--text-tertiary);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

/* ============================================================
   ANIMATION ENHANCEMENTS — Micro-interactions & Polish
   ============================================================ */

/* --- Button Ripple Effect --- */
.btn::after {
  content: '';
  position: absolute;
  inset: 0;
  background: radial-gradient(circle, rgba(255,255,255,0.3) 10%, transparent 10.01%);
  background-size: 100% 100%;
  background-position: center;
  background-repeat: no-repeat;
  opacity: 0;
  transition: opacity 0.4s ease;
  pointer-events: none;
}

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

.btn-primary:hover {
  background: linear-gradient(135deg, var(--color-primary-hover) 0%, var(--color-primary) 100%);
  box-shadow: 0 4px 16px var(--color-primary-glow);
  transform: translateY(-1px);
}

/* --- Card Hover Shimmer --- */
.card::after {
  content: '';
  position: absolute;
  top: 0;
  left: -100%;
  width: 50%;
  height: 100%;
  background: linear-gradient(90deg, transparent, rgba(255,255,255,0.04), transparent);
  transition: left 0.6s ease;
  pointer-events: none;
}

.card:hover::after {
  left: 100%;
}

/* The subfolder tiles sit in a card grid so they animate with everything
   else, but they are smaller than a program card and should stay that way. */
.card-grid.subfolder-grid {
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 0.75rem;
  margin-bottom: 1.5rem;
}

/* --- Card Entrance Animation ---
   Only for grids that did NOT ask to stagger.

   This rule and the `stagger-children` class on the browse, notes, snippets and
   analytics card grids both date to the first commit, and this one has been
   quietly winning ever since: `.card-grid .card` is (0,2,0) against
   `.stagger-children > *` at (0,1,0), so every card in those grids got the same
   smoothReveal -- and because `animation` is a shorthand it also reset the
   per-child animation-delay to 0, flattening the nth-child delays that make the
   entrance run left to right. Four grids asked for a staggered entrance and all
   four played as a single simultaneous fade.

   The :not() hands staggered grids back to .stagger-children while leaving
   plain card grids -- browse renders one when disableNextStagger is set -- with
   the entrance they have always had. */
.card-grid:not(.stagger-children) .card,
.card-grid:not(.stagger-children) .subfolder-card {
  animation: smoothReveal 0.35s ease-out backwards;
}

/* --- Modal Overlay Entrance ---
   A second .modal-content rule further down the same file was overriding the
   one above with bounceIn, so every dialog overshot its size and sprang back.
   Both now use the same rise-and-settle; keeping this one so the intent stays
   where someone would look for it. */
.modal-content {
  animation: modalRise 0.34s cubic-bezier(0.16, 1, 0.3, 1) both;
}

/* --- Badge Hover Lift --- */
.badge {
  transition: all var(--transition-fast);
}

.badge:hover {
  transform: translateY(-1px);
  filter: brightness(1.15);
}

/* --- Tier Badge Shine --- */
.tier-badge {
  position: relative;
  overflow: hidden;
}

.tier-badge::after {
  content: '';
  position: absolute;
  top: 0;
  left: -100%;
  width: 50%;
  height: 100%;
  background: linear-gradient(90deg, transparent, rgba(255,255,255,0.25), transparent);
  animation: shimmer 3s infinite;
  pointer-events: none;
}

/* --- Score Badge Pop --- */
.score-badge {
  transition: all var(--transition-fast);
}

.score-perfect {
  animation: popIn 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}

/* --- Form Input Focus ---
   Was a 3px glow ring plus a 1px lift, so every field you touched jumped and
   grew a halo. The border alone says which field has focus without any of
   that; it is kept rather than removed outright, since a focused field with
   no indication at all is unusable from the keyboard. */
.form-input:focus,
.form-textarea:focus,
.form-select:focus {
  border-color: var(--color-primary);
  box-shadow: none;
  transform: none;
}

/* --- Form Input Error Shake --- */
.form-input.error {
  animation: shake 0.4s ease-in-out;
  border-color: var(--color-danger);
  box-shadow: 0 0 0 3px var(--color-danger-bg);
}

/* --- Empty State Fade --- */
.empty-state {
  animation: fadeInUp 0.4s ease-out;
}

.empty-state svg,
.empty-state i {
  animation: float 4s ease-in-out infinite;
}

/* Both `.empty-state svg` rules are DESCENDANT selectors, so they also caught
   the icon inside an empty state's own button — "Clear filters" got a 48px
   icon slot, 0.4 opacity, a 16px bottom margin that inflated the button to
   44px, and the float animation, so its little x drifted up and down beside
   the big one. An icon inside a control is part of the control. */
.empty-state button svg, .empty-state button i,
.empty-state a svg, .empty-state a i,
.empty-state .btn svg, .empty-state .btn i {
  animation: none;
  margin: 0;
  opacity: 1;
}

/* --- Table Row Hover Slide --- */
.table tbody tr {
  transition: background-color var(--transition-fast), transform var(--transition-fast);
}

.table tbody tr:hover {
  background: var(--bg-surface-hover);
  transform: translateX(2px);
}

/* --- Analytics Stat Counter Animation --- */
.analytics-stat-card {
  animation: smoothReveal 0.3s ease-out backwards;
}

.analytics-stat-card:nth-child(1) { animation-delay: 0s; }
.analytics-stat-card:nth-child(2) { animation-delay: 0.08s; }
.analytics-stat-card:nth-child(3) { animation-delay: 0.16s; }
.analytics-stat-card:nth-child(4) { animation-delay: 0.24s; }

.analytics-stat-value {
  animation: countUp 0.5s ease-out backwards;
  animation-delay: 0.2s;
}

/* --- Carousel Slide Transition --- */
/* Smooth 3D cylinder rotation: slides persist in the DOM (see _positionCarouselSlides)
   and only their transform/opacity change, so this transition animates the motion.
   The old `animation: popIn` on .active was removed — it caused the pop-in/out. */
.carousel-slide {
  transition: transform 0.6s cubic-bezier(0.22, 1, 0.36, 1),
              opacity 0.5s ease,
              box-shadow 0.5s ease;
}

/* --- Divider Gradient Reveal --- */
.divider {
  background: linear-gradient(90deg, transparent, var(--border-color) 20%, var(--border-color) 80%, transparent);
}

/* --- Category Item Stagger --- */
.category-item {
  animation: smoothReveal 0.3s ease-out backwards;
  transition: all var(--transition-fast);
}

.category-item:hover {
  transform: translateX(3px);
  border-color: var(--color-primary);
  background: var(--color-primary-subtle);
}

/* --- Mindmap Panel Entrance --- */
.mindmap-panel {
  animation: modalRise 0.34s cubic-bezier(0.16, 1, 0.3, 1) both;
}

/* --- Tag Hover Lift --- */
.tag {
  transition: all var(--transition-fast);
}

.tag:hover {
  transform: translateY(-1px);
  box-shadow: 0 2px 6px var(--color-primary-glow);
}

/* --- Premium Carousel Indicator Glow --- */
.carousel-dot.active {
  animation: glowPulse 1.5s ease-in-out infinite;
}

/* ============================================================
   SETTINGS FAB + POPUP (replaces sidebar-bottom section)
   ============================================================ */
/* Hidden on focused routes — attempts and result screens. It's app-shell chrome
   and has no business floating over a timed attempt (it also landed on top of
   the panel's Finish button). */
body.route-focused .settings-fab { display: none; }

/* The canvas runs to the corners of the pane, and the FAB sat on top of the
   minimap and the toolbar's own controls. The router writes the route onto
   <body>, so this needs nothing from the view itself. */
body[data-route="visualization"] .settings-fab { display: none; }

.settings-fab {
  position: fixed;
  right: 20px;
  bottom: 20px;
  width: 46px;
  height: 46px;
  border-radius: 50%;
  background: var(--bg-elevated);
  border: 1px solid var(--border-color);
  color: var(--text-tertiary);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  z-index: 950;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.35);
  transition: color 0.2s ease, border-color 0.2s ease, transform 0.25s cubic-bezier(0.34, 1.56, 0.64, 1), box-shadow 0.2s ease;
}

.settings-fab i,
.settings-fab svg {
  width: 20px;
  height: 20px;
  transition: transform 0.45s cubic-bezier(0.34, 1.56, 0.64, 1);
}

.settings-fab:hover {
  color: var(--color-primary);
  border-color: var(--color-primary);
  transform: translateY(-2px);
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.45), 0 0 14px var(--color-primary-glow);
}

.settings-fab:hover i,
.settings-fab:hover svg {
  transform: rotate(90deg);
}

.settings-modal-content {
  max-width: 420px;
  width: 100%;
  text-align: left;
  padding: 0;
  overflow: hidden;
}

.settings-modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 1.1rem 1.25rem;
  border-bottom: 1px solid var(--border-color);
}

.settings-modal-title {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 1.05rem;
  font-weight: 800;
  color: var(--text-primary);
  margin: 0;
}

.settings-modal-title i,
.settings-modal-title svg {
  width: 18px;
  height: 18px;
  color: var(--color-primary);
}

.settings-close-btn {
  background: none;
  border: none;
  color: var(--text-tertiary);
  cursor: pointer;
  padding: 0.3rem;
  border-radius: var(--radius-sm);
  display: flex;
  transition: color 0.15s, background 0.15s;
}

.settings-close-btn:hover {
  color: var(--text-primary);
  background: var(--bg-surface-hover);
}

.settings-modal-content .cloud-user-badge {
  margin: 0.75rem 1.25rem 0;
}

.settings-items {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 0.75rem;
}

.settings-item {
  display: flex;
  align-items: center;
  gap: 0.8rem;
  width: 100%;
  padding: 0.7rem 0.75rem;
  background: transparent;
  border: none;
  border-radius: var(--radius-md);
  cursor: pointer;
  text-align: left;
  transition: background 0.15s ease;
  margin: 0;
}

.settings-item:hover {
  background: var(--bg-surface-hover);
}

.settings-item-icon {
  width: 36px;
  height: 36px;
  border-radius: var(--radius-md);
  background: var(--bg-surface-hover);
  border: 1px solid var(--border-color);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-secondary);
  flex-shrink: 0;
}

.settings-item-icon i,
.settings-item-icon svg {
  width: 17px;
  height: 17px;
}

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

.settings-item-label {
  font-size: 0.875rem;
  font-weight: 700;
  color: var(--text-primary);
}

.settings-item-desc {
  font-size: 0.72rem;
  color: var(--text-tertiary);
}

.settings-item-chevron {
  width: 15px;
  height: 15px;
  color: var(--text-tertiary);
  opacity: 0;
  transform: translateX(-4px);
  transition: opacity 0.15s ease, transform 0.2s ease;
  flex-shrink: 0;
}

.settings-item:hover .settings-item-chevron {
  opacity: 0.8;
  transform: translateX(0);
}

.settings-item.danger .settings-item-label { color: var(--color-danger); }
.settings-item.danger .settings-item-icon { color: var(--color-danger); border-color: rgba(239, 68, 68, 0.25); }
.settings-item.danger:hover { background: rgba(239, 68, 68, 0.08); }

.settings-divider {
  height: 1px;
  background: var(--border-color);
  margin: 0.4rem 0.5rem;
}

/* --- Version Pill Pop --- */
.version-pill {
  transition: all var(--transition-fast);
}

.version-pill:hover {
  transform: scale(1.05);
  background: var(--color-primary-subtle);
  color: var(--color-primary);
}

/* Quest Board */
.quest-job-label {
  color: var(--color-accent);
  font-weight: 700;
  font-size: 0.875rem;
}

/* Modal size/scroll modifiers */
.modal-content-scroll {
  max-width: 500px;
  width: 95%;
  max-height: 80vh;
  display: flex;
  flex-direction: column;
}

.modal-content-wide {
  max-width: 1200px;
  width: 95%;
  max-height: 90vh;
  display: flex;
  flex-direction: column;
  overflow-y: auto;
  overflow-x: hidden;
}

.modal-content-search {
  max-width: 640px;
  width: 95%;
  padding: 0;
  overflow: hidden;
  border-radius: var(--radius-lg);
  background: var(--bg-nav-solid, var(--bg-elevated));
  border: 1px solid rgba(148, 163, 184, 0.2);
  box-shadow: 0 25px 60px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(148, 163, 184, 0.08);
}

.modal-header-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 1rem;
}

.modal-body-scroll {
  flex: 1;
  overflow-y: auto;
  margin-bottom: 1rem;
  padding-right: 0.5rem;
}

.modal-footer-border {
  margin-top: 0;
  padding-top: 1rem;
  border-top: 1px solid var(--border-color);
}

.icon-picker-grid {
  flex: 1;
  overflow-y: auto;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(48px, 1fr));
  gap: 0.5rem;
  padding: 0.5rem;
}

.modal-search-header {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.875rem 1rem;
  border-bottom: 1px solid var(--border-color);
}

.modal-search-input {
  flex: 1;
  background: transparent;
  border: none;
  outline: none;
  font-size: 1rem;
  font-family: var(--font-sans);
  color: var(--text-primary);
}
/* ============================================================
   NOTEBOOK CARD COVER (Notes Library parity with Coding Library)
   ============================================================ */
.card.has-cover {
  padding-top: 0;
}
.nb-card-cover {
  margin: 0 calc(-1 * var(--space-lg)) var(--space-md);
  height: 110px;
  overflow: hidden;
  border-bottom: 1px solid var(--border-color);
  position: relative;
}
.nb-card-cover img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: transform var(--transition-base);
}
.card.has-cover:hover .nb-card-cover img {
  transform: scale(1.04);
}
.nb-card-cover::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(to top, rgba(0,0,0,0.35), transparent 55%);
  pointer-events: none;
}

/* ============================================================
   CAROUSEL SOURCE SWITCHER (Notebooks / Coding Library)
   ============================================================ */
.carousel-source-toggle { display: flex; justify-content: center; gap: 0.4rem; margin: 0.25rem 0 0.75rem; }
.carousel-source-btn { display: inline-flex; align-items: center; gap: 0.35rem; font-size: 0.72rem; font-weight: 700; padding: 0.3rem 0.8rem; border-radius: 999px; border: 1px solid var(--border-color); background: transparent; color: var(--text-tertiary); cursor: pointer; transition: all 0.15s ease; }
.carousel-source-btn svg { width: 13px; height: 13px; }
.carousel-source-btn:hover { color: var(--text-primary); border-color: var(--text-tertiary); }
.carousel-source-btn.active { color: var(--color-primary); border-color: var(--color-primary); background: var(--color-primary-subtle); }

/* Snippet Library header icon accent */
.snippets-icon-wrap { color: var(--color-accent); }

/* ============================================================
   FILE DIALOG — Add / Rename a companion file (file-dialog.js)
   Shared by the practice page, the practice-set page and the
   admin variant form, which used to carry three drifting copies.
   ============================================================ */
.fd-overlay { z-index: 1200; }
.fd-box {
  max-width: 430px;
  text-align: left;
  padding: 1.5rem;
}
.fd-title {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 1rem;
  font-weight: 700;
  margin-bottom: 1rem;
}
.fd-title svg { width: 18px; height: 18px; color: var(--color-primary); }

/* Headers the code already asks for but that don't exist yet — the reason you
   opened this dialog, one click away. */
.fd-suggest {
  margin-bottom: 0.85rem;
  padding: 0.6rem 0.7rem;
  background: rgba(99, 102, 241, 0.08);
  border: 1px solid rgba(99, 102, 241, 0.28);
  border-radius: var(--radius-md);
}
.fd-suggest-label {
  display: block;
  font-size: 0.72rem;
  color: var(--text-secondary);
  margin-bottom: 0.4rem;
  line-height: 1.4;
}
.fd-chips { display: flex; flex-wrap: wrap; gap: 0.3rem; }
.fd-chip {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  font-weight: 600;
  padding: 0.2rem 0.55rem;
  border-radius: 999px;
  border: 1px solid var(--color-primary);
  background: transparent;
  color: var(--color-primary);
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease;
}
.fd-chip:hover { background: var(--color-primary); color: #fff; }

.fd-row { display: flex; gap: 0.5rem; }
.fd-row .form-input { flex: 1; min-width: 0; }
.fd-row .form-select { width: 92px; flex-shrink: 0; }
.fd-invalid { border-color: var(--color-danger) !important; }

/* The name and the extension are two controls; this is the one filename they
   add up to, so there is nothing to guess about the split. */
.fd-preview {
  min-height: 1.4em;
  margin-top: 0.4rem;
  font-family: var(--font-mono);
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--color-primary);
}
.fd-preview.empty::before {
  content: 'filename.h';
  color: var(--text-tertiary);
  opacity: 0.4;
  font-weight: 400;
}
.fd-hint {
  margin: 0.15rem 0 0;
  font-size: 0.72rem;
  line-height: 1.45;
  color: var(--text-tertiary);
}
.fd-warn {
  margin: 0.4rem 0 0;
  font-size: 0.72rem;
  line-height: 1.45;
  color: var(--color-warning);
}
.fd-warn:empty { display: none; }
.fd-error {
  margin: 0.4rem 0 0;
  font-size: 0.72rem;
  line-height: 1.45;
  color: var(--color-danger);
  min-height: 1em;
}

/* What to pre-fill and wire up, so a new header is usable immediately. */
.fd-opts {
  display: flex;
  flex-direction: column;
  gap: 0.45rem;
  margin-top: 0.85rem;
  padding-top: 0.85rem;
  border-top: 1px solid var(--border-color);
}
.fd-opt {
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
  font-size: 0.76rem;
  color: var(--text-secondary);
  cursor: pointer;
}
.fd-opt input[type="checkbox"] {
  margin-top: 2px;
  width: 14px;
  height: 14px;
  flex-shrink: 0;
  accent-color: var(--color-primary);
  cursor: pointer;
}
.fd-opt strong { display: block; color: var(--text-primary); font-weight: 600; }
.fd-opt em {
  display: block;
  font-style: normal;
  font-size: 0.69rem;
  color: var(--text-tertiary);
  margin-top: 1px;
  line-height: 1.4;
}

.fd-files {
  margin-top: 0.85rem;
  font-size: 0.7rem;
  color: var(--text-tertiary);
  line-height: 1.9;
}
.fd-files:empty { display: none; }
.fd-files-label { margin-right: 0.3rem; }
.fd-file {
  font-family: var(--font-mono);
  font-size: 0.69rem;
  background: var(--bg-surface-hover);
  border: 1px solid var(--border-color);
  border-radius: 4px;
  padding: 1px 5px;
  margin-right: 0.25rem;
  color: var(--text-secondary);
}
.fd-file.self { border-color: var(--color-primary); color: var(--color-primary); }

.fd-actions {
  display: flex;
  gap: 0.5rem;
  justify-content: flex-end;
  margin-top: 1.1rem;
}

/* ── Rich instructions, as the student sees them ──────────────
   The instruction is written in Quill and rendered here. Quill scopes its
   indent classes to .ql-editor, so a nested list authored in the admin form
   arrived in practice completely flat. These carry the same steps across. */

/* The global `*` reset zeroes padding on everything, which leaves a list's
   markers drawn outside its own box — an authored numbered list reached the
   student with no numbers at all. */
#practice-desc ol, #pset-desc ol,
#practice-desc ul, #pset-desc ul {
  padding-left: 1.6em;
  margin: 0.4rem 0;
}
#practice-desc li, #pset-desc li { margin: 0.15rem 0; }

#practice-desc .ql-indent-1, #pset-desc .ql-indent-1 { padding-left: 3em; }
#practice-desc .ql-indent-2, #pset-desc .ql-indent-2 { padding-left: 6em; }
#practice-desc .ql-indent-3, #pset-desc .ql-indent-3 { padding-left: 9em; }
#practice-desc .ql-indent-4, #pset-desc .ql-indent-4 { padding-left: 12em; }

#practice-desc blockquote, #pset-desc blockquote {
  border-left: 3px solid var(--color-primary);
  margin: 0.5rem 0;
  padding: 0.1rem 0 0.1rem 0.75rem;
  color: var(--text-secondary);
}

/* Quill writes an empty paragraph for a blank line; without a height it
   collapses and the spacing the author put in is lost. */
#practice-desc p:empty, #pset-desc p:empty { min-height: 1em; }

/* ── Settings: the volume row ─────────────────────────────────
   A companion to the Sound toggle above it, driving the same stored value the
   attempt topbar's slider does. It is a div rather than a button — the rest of
   the list is buttons, but a range input inside one is invalid markup and the
   button would swallow the drag.
   ------------------------------------------------------------ */
.settings-item-slider {
  cursor: default;
  /* The row above it is the switch; this reads as part of the same control
     rather than a separate destination, so it does not light up on hover. */
  background: transparent;
}
.settings-item-slider:hover { background: transparent; }

.settings-item-slider .settings-item-info {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  flex: 1;
  min-width: 0;
}

.settings-vol-range {
  width: 100%;
  accent-color: var(--color-primary);
  cursor: pointer;
  margin: 0;
}
.settings-vol-range:disabled { cursor: not-allowed; }

.settings-vol-label {
  min-width: 40px;
  text-align: right;
  font-size: 0.75rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--text-secondary);
  flex-shrink: 0;
}

/* Sound off: the slider still shows where it is set, but says plainly that
   nothing it does can be heard right now. */
.settings-item-slider.is-muted { opacity: 0.45; }
.settings-item-slider.is-muted .settings-vol-range { accent-color: var(--text-tertiary); }

/* ── A dialog that asks something, rather than confirming it ──
   The default dialog is 420px and centres a row of buttons, which is right
   for "are you sure". It is wrong for a question with two real answers: two
   five-word pills side by side make the reader infer the difference from the
   labels, and the labels are the shortest part of the answer.

   Wider, and each option is a row with its consequence written underneath.
   Rows also keep the two apart, which matters when one of them destroys
   work. */
.dialog-choice .modal-content {
  max-width: 560px;
  text-align: left;
}
.dialog-choice .modal-icon,
.dialog-choice .modal-title { text-align: center; }
.dialog-choice .modal-desc {
  text-align: center;
  margin-bottom: var(--space-lg, 1.25rem);
}
.dialog-choice .modal-actions { display: block; }

.dlg-choice-list { display: flex; flex-direction: column; gap: 0.6rem; }

.dlg-choice {
  display: flex;
  align-items: flex-start;
  gap: 0.85rem;
  width: 100%;
  padding: 0.9rem 1rem;
  text-align: left;
  border-radius: 12px;
  border: 1px solid var(--border-color, #2f2f3a);
  background: var(--bg-surface, #16161c);
  color: var(--text-primary, #e6e6ee);
  font: inherit;
  cursor: pointer;
  transition: border-color 0.14s ease, background 0.14s ease, transform 0.14s ease;
}
.dlg-choice:hover { transform: translateY(-1px); }
.dlg-choice:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; }

.dlg-choice.is-primary { border-color: color-mix(in srgb, var(--color-primary, #6366f1) 55%, transparent); }
.dlg-choice.is-primary:hover {
  border-color: var(--color-primary, #6366f1);
  background: color-mix(in srgb, var(--color-primary, #6366f1) 12%, transparent);
}
.dlg-choice.is-danger:hover {
  border-color: rgba(239, 68, 68, 0.65);
  background: rgba(239, 68, 68, 0.10);
}

.dlg-choice-ic {
  flex: 0 0 auto;
  width: 34px; height: 34px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 9px;
  background: var(--bg-surface-hover, #22222c);
  color: var(--text-secondary, #a8a8b8);
}
.dlg-choice-ic svg { width: 17px; height: 17px; }
.dlg-choice.is-primary .dlg-choice-ic { color: var(--color-primary, #6366f1); }
.dlg-choice.is-danger .dlg-choice-ic { color: #f87171; }

.dlg-choice-text { display: flex; flex-direction: column; gap: 0.15rem; min-width: 0; flex: 1; }
.dlg-choice-label { font-size: 0.9rem; font-weight: 700; }
.dlg-choice-detail {
  font-size: 0.76rem;
  line-height: 1.45;
  color: var(--text-tertiary, #7c7c8a);
}

.dlg-choice-go {
  flex: 0 0 auto;
  align-self: center;
  color: var(--text-tertiary, #7c7c8a);
  opacity: 0;
  transition: opacity 0.14s ease, transform 0.14s ease;
}
.dlg-choice-go svg { width: 16px; height: 16px; }
.dlg-choice:hover .dlg-choice-go { opacity: 1; transform: translateX(2px); }

.dlg-choice-cancel {
  display: block;
  margin: 0.9rem auto 0;
  padding: 0.4rem 0.9rem;
  border: none; background: none;
  color: var(--text-tertiary, #7c7c8a);
  font: inherit; font-size: 0.8rem; font-weight: 600;
  cursor: pointer; border-radius: 6px;
}
.dlg-choice-cancel:hover { color: var(--text-primary); background: var(--bg-surface-hover, #22222c); }

@media (max-width: 480px) {
  .dlg-choice { padding: 0.75rem 0.8rem; gap: 0.65rem; }
  .dlg-choice-detail { font-size: 0.72rem; }
}

/* ── Icon picker ──────────────────────────────────────────────
   Wide, because the point is to see a lot of icons at once, and a fixed-height
   scroller so the dialog does not resize as you type. */
.dialog-icons .modal-content { max-width: 620px; text-align: left; }
.dialog-icons .modal-icon, .dialog-icons .modal-title { text-align: center; }
.dialog-icons .modal-desc { text-align: center; margin-bottom: 0.9rem; }
.dialog-icons .modal-actions { display: block; }

.iconpick { display: flex; flex-direction: column; gap: 0.7rem; }
.iconpick-search { width: 100%; }

.iconpick-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(46px, 1fr));
  gap: 6px;
  height: 260px;
  overflow-y: auto;
  padding: 4px;
  border-radius: 10px;
  background: var(--bg-surface, #16161c);
  border: 1px solid var(--border-color, #2f2f3a);
}

.iconpick-cell {
  display: inline-flex; align-items: center; justify-content: center;
  aspect-ratio: 1 / 1;
  border-radius: 8px;
  border: 1px solid transparent;
  background: none;
  color: var(--text-secondary, #a8a8b8);
  cursor: pointer;
  transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease;
}
.iconpick-cell svg { width: 19px; height: 19px; }
.iconpick-cell:hover { background: var(--bg-surface-hover, #22222c); color: var(--text-primary); }
.iconpick-cell.is-on {
  border-color: var(--color-primary, #6366f1);
  background: color-mix(in srgb, var(--color-primary, #6366f1) 18%, transparent);
  color: var(--color-primary, #6366f1);
}
.iconpick-cell:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 1px; }

.iconpick-more {
  grid-column: 1 / -1;
  padding: 0.6rem 0.2rem;
  font-size: 0.72rem;
  color: var(--text-tertiary, #7c7c8a);
  text-align: center;
}

.iconpick-foot { display: flex; align-items: center; gap: 0.45rem; }
.iconpick-spacer { flex: 1; }
.iconpick-current { display: inline-flex; align-items: center; gap: 0.4rem; min-width: 0; }
.iconpick-current svg { width: 17px; height: 17px; color: var(--color-primary, #6366f1); }
.iconpick-current code {
  font-size: 0.72rem; color: var(--text-secondary, #a8a8b8);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.iconpick-none { font-size: 0.72rem; color: var(--text-tertiary, #7c7c8a); }

@media (max-width: 520px) {
  .iconpick-grid { height: 200px; }
  .iconpick-foot { flex-wrap: wrap; }
  .iconpick-spacer { flex-basis: 100%; height: 0; }
}

/* ── Compact cards ──────────────────────────────────────────────────────────
   The dense reading of a folder: title, versions, where you got to. No cover,
   no description, no buttons -- those belong to the detailed card, which is for
   choosing what to work on rather than finding something you can already name.
   Narrower tracks, so three sit where two did. */
.card-grid.is-compact {
  /* 300, not 210: at 210 a folder of five packed four to a row and each card
     was barely wider than its own title, which is denser than the reference and
     reads as cramped rather than compact. Three roomy tracks across a library
     pane is the shape being copied. */
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
}

.card.card-compact {
  display: flex;
  flex-direction: column;
  gap: 0.875rem;
  padding: 1.25rem 1.375rem;
  min-height: 0;             /* the detailed card sets a floor it does not need */
}

/* Select mode puts a 22px checkbox at top-left of the card. The detailed card
   has a cover image there and swallows it; this one starts with the title, so
   the box sat on the words. The head steps aside for exactly as long as the box
   is in the DOM -- it is only rendered in select mode, so the sibling selector
   is the same condition without JS having to say so twice. */
.lib-select-box ~ .card-compact-head { padding-left: 30px; }

.card-compact-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 0.5rem;
}

.card-compact-title {
  font-weight: 700;
  font-size: 1.0625rem;
  line-height: 1.3;
  color: var(--text-primary);
  /* Two lines then ellipsis: one line cut half the starter pack's titles, and
     letting them run made the rows in a track disagree about their height. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.card-compact-meta {
  display: flex;
  align-items: center;
  gap: 0.375rem;
  flex-wrap: wrap;
  margin-top: auto;
}

/* Pushed to the end of the row, so the pills read left to right and the action
   sits where the eye finishes. It shrinks before the pills do -- the status is
   what you are scanning for, the button is what you press once you have found
   the right card. */
/* A square at the end of the row. .btn-practice is `width: 100%`, which is
   right for the detailed card where it spans the footer and wrong here -- it
   made the button claim the whole meta row and wrap onto its own line, taking
   the card from 109px to 143px. The width is overridden rather than the class
   dropped, so it keeps the same colours and hover as the detailed card's.

   Icon only: the label it replaced repeated what the status pill beside it
   already says, and the action is on the title in `title` and `aria-label`. */
/* ── What the play button is about to do, in colour ────────────────────────
   Three actions, three tones, keyed off the same test that picks the label so
   the two can never disagree:

     indigo  nothing has been run yet -- a fresh attempt
     amber   an unfinished attempt is waiting to be picked back up
     green   solved once already; this starts it over

   Colour on the border and the glyph rather than a filled block: these sit
   three to a row beside a status pill, and three saturated fills would read as
   the loudest thing on the page. */
.card-compact-action.is-fresh {
  color: var(--color-primary);
  border-color: rgba(99, 102, 241, 0.42);
  background: rgba(99, 102, 241, 0.12);
}
.card-compact-action.is-fresh:hover {
  background: rgba(99, 102, 241, 0.2);
  border-color: rgba(99, 102, 241, 0.65);
}
.card-compact-action.is-resume {
  color: #fbbf24;
  border-color: rgba(245, 158, 11, 0.42);
  background: rgba(245, 158, 11, 0.12);
}
.card-compact-action.is-resume:hover {
  background: rgba(245, 158, 11, 0.2);
  border-color: rgba(245, 158, 11, 0.65);
}
/* Green reads as "this one is behind you" -- the same colour the Solved pill
   beside it uses, so a solved card agrees with itself across the row. */
.card-compact-action.is-retry {
  color: #34d399;
  border-color: rgba(16, 185, 129, 0.38);
  background: rgba(16, 185, 129, 0.10);
}
.card-compact-action.is-retry:hover {
  background: rgba(16, 185, 129, 0.18);
  border-color: rgba(16, 185, 129, 0.6);
}

.card-compact-action {
  margin-left: auto;
  flex: 0 0 auto;
  width: 34px;
  min-width: 34px;
  height: 34px;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* ── The Solved tag ─────────────────────────────────────────────────────────
   Carried by both layouts. On the detailed card it joins the stat row beside
   the attempt count; the green check in the corner says the same thing, but a
   tick is a symbol you have to have learnt and this is a word. */
.card-solved {
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  padding: 0.3rem 0.6rem;
  border-radius: 999px;
  border: 1px solid var(--border-color);
  color: var(--text-tertiary);
  background: var(--bg-surface-hover);
  white-space: nowrap;
}
.card-solved.is-solved {
  color: #34d399;
  border-color: rgba(16, 185, 129, 0.35);
  background: rgba(16, 185, 129, 0.12);
}
.card-solved.is-partial {
  color: var(--color-warning, #f59e0b);
  border-color: rgba(245, 158, 11, 0.3);
  background: rgba(245, 158, 11, 0.1);
}

/* ── Read-aloud ─────────────────────────────────────────────────────────────
   The speaker buttons sit beside text they read, so they stay quiet until
   pointed at: this is an aid, not a primary action, and a row of bright icons
   next to every word would compete with the words. */
.la-speak {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  margin-left: 0.4rem;
  padding: 0;
  border: 1px solid var(--border-color);
  border-radius: var(--radius-md);
  background: transparent;
  color: var(--text-tertiary);
  cursor: pointer;
  vertical-align: middle;
  transition: color 0.15s ease, border-color 0.15s ease, background 0.15s ease;
}
.la-speak:hover {
  color: var(--color-primary);
  border-color: var(--color-primary);
  background: var(--color-primary-subtle);
}
.la-speak svg { width: 15px; height: 15px; }

/* Beside the answer box rather than after it in the flow, so the box keeps the
   width it had before there was a button next to it. */
.la-typed { display: flex; align-items: center; gap: 0.5rem; }
.la-typed .form-input { flex: 1; min-width: 0; }
.la-speak-input { margin-left: 0; flex: 0 0 auto; }

.speech-body { display: flex; flex-direction: column; gap: 0.9rem; text-align: left; }
.speech-row { display: flex; flex-direction: column; gap: 0.35rem; }
.speech-row-label {
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--text-secondary);
  display: flex;
  justify-content: space-between;
}
.speech-row-label em {
  font-style: normal;
  font-family: var(--font-mono);
  color: var(--text-tertiary);
}
.speech-row input[type="range"] { width: 100%; accent-color: var(--color-primary); }
.speech-loading { color: var(--text-tertiary); font-size: 0.875rem; padding: 0.5rem 0; }

/* ── Reading along ──────────────────────────────────────────────────────────
   Karaoke without the karaoke box: no background, no bar sweeping across a
   block of colour. Each letter brightens and grows as the voice reaches it and
   settles back a shade darker once it has been said, so the reading position is
   carried by the type itself rather than by something drawn behind it. */
.rp-reading {
  --rp-hot: var(--color-primary);
  --rp-said: var(--text-tertiary);
}

/* Letters are inline-block so they can be scaled -- which also lets the browser
   break a line between them, so the word holds itself together. */
.rp-w { white-space: nowrap; }
.rp-c {
  display: inline-block;
  transform-origin: center bottom;
}

/* will-change ONLY on the word being read. It asks the compositor for a layer
   per element, and a long description is thousands of letters -- a layer each
   is a lot of memory to hold for text that is not moving. One word is at most
   a dozen. */
.rp-w.is-now .rp-c { will-change: transform; }

/* Long text keeps the word highlight and drops the per-letter sweep, so it
   carries the colours the letters would otherwise have shown. */
.rp-w.rp-w-plain { transition: color 0.12s ease; }
.rp-w.rp-w-plain.is-now { color: var(--rp-hot); }
.rp-w.rp-w-plain.is-said { color: var(--rp-said); }

/* Already read: stepped back, so the eye is drawn to where the voice is now
   rather than to everything it has covered. */
.rp-w.is-said .rp-c { color: var(--rp-said); transition: color 0.25s ease; }

/* Being read: the letters take their turn. --rp-i is the letter's position in
   the word and --rp-step how long each one gets, both written by _rpLight from
   the word's length and the speaking rate. */
.rp-w.is-now .rp-c {
  animation: rpSweep var(--rp-dur, 260ms) cubic-bezier(0.22, 0.61, 0.36, 1) both;
  animation-delay: calc(var(--rp-i, 0) * var(--rp-step, 62ms));
}

@keyframes rpSweep {
  0%   { color: inherit;        transform: scale(1); }
  28%  { color: var(--rp-hot);  transform: scale(1.42); }
  100% { color: var(--rp-said); transform: scale(1); }
}

/* The sweep is the whole feature, so with reduced motion the position still
   has to be legible -- it just stops moving and says it in colour alone. */
@media (prefers-reduced-motion: reduce) {
  .rp-w.is-now .rp-c { animation: none; color: var(--rp-hot); }
  .rp-c { will-change: auto; }
}

/* ── Speakers across the language library ───────────────────────────────────
   Two shapes for one job. The button stands on its own in a toolbar or beside a
   heading; the chip goes INSIDE another button — a drill tile, an option, a
   dialogue line — where a nested <button> would be invalid and would break the
   one containing it. */
.lang-speak-btn svg { width: 15px; height: 15px; }

.lang-speak-chip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  margin-left: 0.4rem;
  border-radius: var(--radius-sm);
  color: var(--text-tertiary);
  opacity: 0.55;
  cursor: pointer;
  vertical-align: middle;
  flex-shrink: 0;
  transition: opacity 0.15s ease, color 0.15s ease, background 0.15s ease;
}
.lang-speak-chip svg { width: 13px; height: 13px; }

/* Quiet until wanted: these sit on tiles and options that are themselves the
   thing to press, so a bright icon on every one would compete with the answer. */
.lang-speak-chip:hover,
.lang-speak-chip:focus-visible {
  opacity: 1;
  color: var(--color-primary);
  background: var(--color-primary-subtle);
  outline: none;
}
.lang-speak-chip:focus-visible { box-shadow: 0 0 0 2px var(--color-primary-glow); }

/* In the dialogue box the line is the content, so the speaker trails it. */
.lq-line .lang-speak-chip { opacity: 0.4; }
.lq-line .lang-speak-chip:hover { opacity: 1; }

/* Beside an example sentence, aligned to the text rather than the row. */
.lang-examples .lang-speak-chip,
.lang-ex-text + .lang-speak-btn { vertical-align: baseline; }

/* The voice-over switch, in the scene's top corner beside the art marker so it
   travels with the run rather than living in a settings screen you would have
   to leave the run to reach. */
.lq-voice {
  position: absolute;
  top: 0.6rem;
  right: 2.6rem;
  z-index: 6;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  padding: 0;
  border: 1px solid rgba(148, 163, 184, 0.28);
  border-radius: var(--radius-sm);
  background: rgba(2, 6, 23, 0.55);
  color: var(--text-tertiary);
  cursor: pointer;
  transition: color 0.15s ease, border-color 0.15s ease, background 0.15s ease;
}
.lq-voice svg { width: 14px; height: 14px; }
.lq-voice:hover { color: var(--text-primary); border-color: rgba(148, 163, 184, 0.5); }
.lq-voice.is-on {
  color: var(--color-primary);
  border-color: rgba(99, 102, 241, 0.55);
  background: rgba(99, 102, 241, 0.16);
}

/* The one checkbox in the Read Aloud panel sits beside its label rather than
   above it, the way the other rows stack their control under theirs. */
.speech-row.speech-row-check {
  flex-direction: row;
  align-items: flex-start;
  gap: 0.55rem;
  cursor: pointer;
}
.speech-row-check input { margin-top: 0.15rem; flex-shrink: 0; }
.speech-row-check strong { display: block; font-size: 0.8125rem; color: var(--text-secondary); }
.speech-row-check em {
  display: block;
  font-style: normal;
  font-size: 0.75rem;
  line-height: 1.45;
  color: var(--text-tertiary);
  margin-top: 0.15rem;
}
