:root {
  --bg: #f4f4f5;
  --panel: #ffffff;
  --text: #18181b;
  --muted: #71717a;
  --border: #e4e4e7;
  --accent: #2563eb;
  --accent-contrast: #ffffff;
  --shadow: 0 10px 30px rgba(0, 0, 0, 0.12);
  --radius: 18px;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #09090b;
    --panel: #18181b;
    --text: #fafafa;
    --muted: #a1a1aa;
    --border: #27272a;
    --accent: #3b82f6;
    --shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
  }
}

* {
  box-sizing: border-box;
}

[hidden] {
  display: none !important;
}

html,
body {
  margin: 0;
  padding: 0;
}

body {
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  background: var(--bg);
  color: var(--text);
  font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto,
    "Helvetica Neue", Arial, sans-serif;
  -webkit-tap-highlight-color: transparent;
}

/* ---------- Top bar ---------- */
.topbar {
  padding: 14px 16px calc(14px + env(safe-area-inset-top));
  border-bottom: 1px solid var(--border);
  background: var(--panel);
}

.topbar h1 {
  margin: 0 0 10px;
  font-size: 1.05rem;
  font-weight: 700;
  letter-spacing: 0.01em;
}

/* ---------- View tabs ---------- */
.viewtoggle {
  display: flex;
  gap: 18px;
  margin: 0 0 12px;
  border-bottom: 1px solid var(--border);
  overflow-x: auto;
  scrollbar-width: none;
  /* This row can scroll sideways once there are more tabs than fit. Without
     this, a touch-drag that isn't perfectly horizontal gets read as an
     ambiguous scroll gesture and can visibly tug the row (and page) up/down
     before settling into the horizontal scroll. */
  touch-action: pan-x;
}

.viewtoggle::-webkit-scrollbar {
  display: none;
}

.viewtoggle__btn {
  flex: none;
  font: inherit;
  font-size: 0.92rem;
  font-weight: 600;
  padding: 8px 2px;
  margin-bottom: -1px;
  border: 0;
  border-bottom: 2px solid transparent;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  white-space: nowrap;
}

.viewtoggle__btn.is-active {
  color: var(--text);
  border-bottom-color: var(--accent);
}

/* ---------- Flashcards sub-tabs (Study / All cards) ---------- */
.subtoggle {
  display: flex;
  gap: 6px;
  margin: 0 0 12px;
}

.subtoggle__btn {
  font: inherit;
  font-size: 0.82rem;
  font-weight: 600;
  padding: 5px 12px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  white-space: nowrap;
}

.subtoggle__btn.is-active {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-contrast, #fff);
}

.speed {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin: 0 0 10px;
  font-size: 0.74rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--muted);
}

.speed select {
  font: inherit;
  font-size: 0.85rem;
  padding: 5px 8px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--text);
  text-transform: none;
  letter-spacing: 0;
}

.controlsWrap {
  position: relative;
}

.filters-toggle {
  font: inherit;
  font-size: 0.78rem;
  font-weight: 600;
  padding: 5px 12px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
}

/* A floating dropdown, not an in-flow panel: opening it never resizes
   .topbar or .stage, so the card (sized off .stage's height on mobile)
   never has to resize live. Live-resizing an already-painted 3D-transform
   element (the flip card) is exactly the kind of thing that's shown itself
   prone to repaint glitches, so the fix is to not do that at all. */
.controls {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  right: 0;
  z-index: 5;
  display: flex;
  flex-wrap: wrap;
  gap: 10px 16px;
  align-items: end;
  padding: 12px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--panel);
  box-shadow: var(--shadow);
}

.controls label {
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--muted);
}

.controls select {
  font: inherit;
  font-size: 0.9rem;
  padding: 7px 10px;
  border-radius: 10px;
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--text);
}

/* ---------- Stage ---------- */
.stage {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 20px 12px;
  position: relative;
}

.scene {
  width: min(78vw, 360px);
  perspective: 1400px;
  touch-action: pan-y;
  container-type: inline-size;
}

.card {
  position: relative;
  width: 100%;
  aspect-ratio: 3 / 4;
  transform-style: preserve-3d;
  transition: transform 0.38s cubic-bezier(0.4, 0.2, 0.2, 1);
  cursor: pointer;
}

.card.is-flipped {
  transform: rotateY(180deg);
}

/* Used to jump to a new card without animating the un-flip. */
.card.no-anim {
  transition: none;
}

.card__face {
  position: absolute;
  inset: 0;
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 14px;
  padding: 22px;
  text-align: center;
  border-radius: var(--radius);
  border: 1px solid var(--border);
  background: var(--panel);
  box-shadow: var(--shadow);
  overflow: hidden;
}

.card__face--back {
  transform: rotateY(180deg);
}

.card__face-inner {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 14px;
  padding: 22px;
  border-radius: var(--radius);
  overflow: hidden;
}

/* Image face */
.card__img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.card__img-fallback {
  font-size: 1.6rem;
  font-weight: 700;
  color: var(--muted);
  padding: 16px;
  word-break: break-word;
}

/* Word face */
.card__emoji {
  font-size: clamp(2.6rem, 14cqw, 3.6rem);
  line-height: 1;
}

.card__vi {
  font-size: clamp(1.8rem, 9cqw, 2.6rem);
  font-weight: 700;
  line-height: 1.15;
}

.card__en {
  font-size: 1.05rem;
  color: var(--muted);
}

.card__note {
  font-size: 0.82rem;
  color: var(--muted);
  font-style: italic;
  max-width: 26ch;
}

.speak {
  margin-top: 4px;
  font: inherit;
  font-size: 1.25rem;
  line-height: 1;
  width: 48px;
  height: 48px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--text);
  cursor: pointer;
}

.speak:active {
  transform: scale(0.94);
}

/* ---------- Nav arrows ---------- */
.nav {
  flex: none;
  width: 44px;
  height: 44px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--panel);
  color: var(--text);
  font-size: 1.5rem;
  line-height: 1;
  cursor: pointer;
}

.nav:active {
  transform: scale(0.94);
}

.empty {
  position: absolute;
  color: var(--muted);
}

/* ---------- Status bar ---------- */
.statusbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px;
  padding: 12px 16px calc(12px + env(safe-area-inset-bottom));
  border-top: 1px solid var(--border);
  background: var(--panel);
  font-size: 0.9rem;
}

#counter {
  font-variant-numeric: tabular-nums;
  color: var(--muted);
  min-width: 4ch;
}

.btn {
  font: inherit;
  font-weight: 600;
  padding: 8px 16px;
  border-radius: 10px;
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--text);
  cursor: pointer;
}

.statusbar .btn:first-of-type,
.btn--primary {
  background: var(--accent);
  color: var(--accent-contrast);
  border-color: transparent;
}

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

.hint {
  margin-left: auto;
  color: var(--muted);
  font-size: 0.78rem;
}

/* ---------- All-cards grid ---------- */
.grid {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  padding: 16px 16px calc(24px + env(safe-area-inset-bottom));
}

.grid__group {
  margin-bottom: 22px;
}

.grid__group-title {
  margin: 0 0 8px 2px;
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--muted);
}

.grid__cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap: 10px;
}

.grid__empty {
  color: var(--muted);
  text-align: center;
  padding: 40px 0;
}

.tile {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  text-align: center;
  padding: 14px 10px 12px;
  border: 1px solid var(--border);
  border-radius: 14px;
  background: var(--panel);
  box-shadow: var(--shadow);
}

.tile__emoji {
  font-size: 1.9rem;
  line-height: 1;
  margin-bottom: 2px;
}

.tile__vi {
  font-size: 1.1rem;
  font-weight: 700;
  line-height: 1.2;
}

.tile__en {
  font-size: 0.82rem;
  color: var(--muted);
}

.tile__note {
  font-size: 0.72rem;
  font-style: italic;
  color: var(--muted);
}

.tile .speak {
  margin-top: 8px;
  width: 38px;
  height: 38px;
  font-size: 1rem;
}

/* ---------- Build game ---------- */
.game {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  display: flex;
  padding: 16px 16px calc(24px + env(safe-area-inset-bottom));
}

.game__inner {
  width: 100%;
  max-width: 540px;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.game__empty {
  margin: auto;
  color: var(--muted);
}

.game__prompt {
  margin: 0;
  font-size: clamp(1.15rem, 4.5vw, 1.5rem);
  font-weight: 700;
  line-height: 1.3;
}

.game__answer {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-content: flex-start;
  min-height: 68px;
  padding: 12px;
  border: 2px dashed var(--border);
  border-radius: 14px;
}

.game__answer.is-right {
  border-style: solid;
  border-color: #16a34a;
  background: color-mix(in oklab, #16a34a 10%, transparent);
}

.game__answer.is-wrong {
  border-style: solid;
  border-color: #dc2626;
  background: color-mix(in oklab, #dc2626 10%, transparent);
}

.game__placeholder {
  color: var(--muted);
  font-size: 0.9rem;
  align-self: center;
}

.game__bank {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.gtile {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 6px 6px 6px 14px;
  border-radius: 12px;
  border: 1px solid var(--border);
  background: var(--panel);
  color: var(--text);
  box-shadow: var(--shadow);
  cursor: pointer;
  user-select: none;
}

.gtile:active {
  transform: translateY(1px);
}

.gtile__main {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 1.05rem;
  font-weight: 600;
}

.gtile__emoji {
  font-size: 1.2rem;
  line-height: 1;
}

.gtile__speak {
  flex: none;
  width: 30px;
  height: 30px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--text);
  font-size: 0.85rem;
  line-height: 1;
  cursor: pointer;
}

.gtile__speak:active {
  transform: scale(0.9);
}

.game__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
}

.game__praise {
  font-weight: 700;
  color: #16a34a;
}

.game__hint {
  font-size: 0.85rem;
  color: var(--muted);
}

.gtile.is-right {
  border-color: #16a34a;
  background: color-mix(in oklab, #16a34a 12%, var(--panel));
}

.gtile.is-wrong {
  border-color: #dc2626;
  background: color-mix(in oklab, #dc2626 12%, var(--panel));
}

/* ---------- Q&A row / options (shared with Story) ---------- */
.qgame__q {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  font-size: clamp(1.3rem, 5vw, 1.7rem);
  font-weight: 700;
}

.qgame__en {
  margin: -8px 0 0;
  text-align: center;
  color: var(--muted);
  font-size: 0.9rem;
}

.qgame__options {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.gtile--option {
  justify-content: space-between;
  padding: 12px 8px 12px 16px;
  text-align: left;
}

.gtile--option .gtile__vi {
  white-space: normal;
  font-weight: 600;
}

.qgame__gloss {
  margin: -4px 0 0;
  text-align: center;
  font-size: 0.95rem;
  color: var(--text);
}

/* ---------- Story game ---------- */
.story {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  display: flex;
  padding: 16px 16px calc(24px + env(safe-area-inset-bottom));
}

.story__play {
  align-self: flex-start;
  font-size: 1rem;
  padding: 10px 20px;
}

.story__toggle {
  align-self: flex-start;
  font-size: 0.82rem;
  padding: 6px 12px;
}

.story__transcript {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 12px 14px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--bg);
}

.story__line {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  font-size: 1rem;
  line-height: 1.4;
}

.story__line.is-key {
  font-weight: 700;
  color: var(--accent);
}

.story__qmeta {
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--muted);
}

/* ---------- Grammar (generated study sentences) ---------- */
.grammar {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  display: flex;
  padding: 16px 16px calc(24px + env(safe-area-inset-bottom));
}

.grammar__pick {
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--muted);
}

.grammar__pick select {
  font: inherit;
  font-size: 0.9rem;
  padding: 7px 10px;
  border-radius: 10px;
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--text);
  max-width: 100%;
}

.grammar__title {
  margin: 0;
  font-size: 1.15rem;
  font-weight: 700;
}

.grammar__point {
  margin: 0;
  color: var(--muted);
  font-size: 0.9rem;
  line-height: 1.45;
}

.grammar__sentence {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 16px 14px;
  border: 1px solid var(--border);
  border-radius: 14px;
  background: var(--bg);
}

.grammar__vi {
  flex: 1;
  font-size: clamp(1.3rem, 5vw, 1.7rem);
  font-weight: 700;
  line-height: 1.3;
}

.grammar__en {
  margin: -6px 0 0;
  color: var(--muted);
  font-size: 0.95rem;
}

.grammar__reftitle {
  margin: 4px 0 0;
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--muted);
}

/* ---------- Tales (pick-a-story reading, no quiz) ---------- */
.tales {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  display: flex;
  padding: 16px 16px calc(24px + env(safe-area-inset-bottom));
}

.tales__inner {
  width: 100%;
  max-width: 540px;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.tales__list {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.tales__item {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  width: 100%;
  padding: 14px 16px;
  border: 1px solid var(--border);
  border-radius: 14px;
  background: var(--panel);
  box-shadow: var(--shadow);
  color: var(--text);
  text-align: left;
  cursor: pointer;
  font: inherit;
}

.tales__item:active {
  transform: translateY(1px);
}

.tales__item-title {
  font-size: 1.1rem;
  font-weight: 700;
}

.tales__item-titleEn {
  font-size: 0.85rem;
  color: var(--muted);
}

.tales__back {
  align-self: flex-start;
  font: inherit;
  font-size: 0.82rem;
  font-weight: 600;
  padding: 6px 14px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
}

.tales__title {
  margin: 0;
  font-size: 1.3rem;
  font-weight: 700;
}

.tales__titleEn {
  margin: -6px 0 0;
  color: var(--muted);
  font-size: 0.95rem;
}

.tales__transcript {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 14px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--bg);
}

.tales__line {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 10px;
}

.tales__line-text {
  flex: 1;
}

.tales__line-vi {
  font-size: 1.15rem;
  font-weight: 700;
  line-height: 1.35;
}

.tales__line-en {
  margin: 2px 0 0;
  color: var(--muted);
  font-size: 0.88rem;
}

.tales__glossary-title {
  margin: 4px 0 0;
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--muted);
}

.tales__glossary {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.tales__word {
  padding: 6px 12px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--bg);
  font-size: 0.85rem;
}

.tales__word b {
  font-weight: 700;
}

@media (max-width: 560px) {
  /* Fit the Flashcards view in one screen: no page scroll, so a
     horizontal swipe never fights a vertical scroll gesture. .nav is
     hidden (swipe replaces it), which frees up the space beside the
     card — the counter and Shuffle button move there instead of taking
     their own bottom row, and the card is sized to whatever room is
     actually left rather than a fixed width. */
  html,
  body {
    height: 100dvh;
    overflow: hidden;
  }
  /* Its content (counter/Shuffle) is pulled out via fixed positioning
     below, and .hint is hidden — nothing left to give this a footprint. */
  .statusbar {
    padding: 0;
    border: none;
    min-height: 0;
  }
  .hint {
    display: none;
  }
  .nav {
    display: none;
  }
  #counter {
    position: fixed;
    left: calc(10px + env(safe-area-inset-left));
    top: 50%;
    transform: translateY(-50%);
    font-size: 0.8rem;
  }
  #reshuffle {
    position: fixed;
    right: calc(10px + env(safe-area-inset-right));
    top: 50%;
    transform: translateY(-50%);
    display: flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    padding: 0;
    border-radius: 999px;
    font-size: 0;
  }
  #reshuffle::before {
    content: "\1F500";
    font-size: 1.3rem;
  }
  .stage {
    min-height: 0;
    padding: 12px 12px calc(12px + env(safe-area-inset-bottom));
  }
  /* Sized by JS (sizeSceneForMobile in app.js), not CSS: a chain of
     percentage/calc-derived heights through .stage -> .scene -> .card ->
     .card__face -> .card__face-inner -> .card__img (three nested
     position:absolute; inset:0 layers) has proven unreliable in practice —
     the card background not filling all the way down on real phones,
     across multiple attempts at reconciling it in pure CSS. Measuring
     .stage's actual box in JS and setting .scene's width/height as
     explicit pixels sidesteps that chain entirely: every layer below it
     then resolves against a plain pixel size, not a computed one. This is
     just the pre-JS fallback so there's no flash of an oversized card
     before the script runs.
  */
  .scene {
    width: min(86vw, 380px);
    aspect-ratio: 3 / 4;
  }
  .card {
    width: 100%;
    height: 100%;
    aspect-ratio: auto;
  }
  .grid__cards {
    grid-template-columns: repeat(auto-fill, minmax(112px, 1fr));
  }
}
