:root {
  /* type (ui-kit.md) */
  --font-display: "Cause", system-ui, sans-serif;   /* logo, titles, buttons */
  --font-text: "Andika", system-ui, sans-serif;     /* body, reading, inputs */

  /* ink + surfaces */
  --ink: #1C1D26;
  --surface: #FFFFFF;
  --cream: #ECE2CC;
  --cream-d: #D8C9A8;

  /* kit palette — base (fill) + deep (hard-shadow block) */
  --c-blue:#2BA8E0;   --c-blue-d:#1C6FB8;
  --c-red:#E85C54;    --c-red-d:#C8413B;
  --c-yellow:#F4C12E; --c-yellow-d:#E1A115;
  --c-green:#5CBF5A;  --c-green-d:#3E9E54;
  --c-purple:#8A5BC2; --c-purple-d:#6A3F9E;
  --c-teal:#2FB9AE;   --c-teal-d:#1E8E86;
  --c-orange:#F2902F; --c-orange-d:#D9701C;
  --c-pink:#F2849F;   --c-pink-d:#D85F80;
  --c-slate:#3C4656;  --c-slate-d:#2A3340;

  /* shape + press */
  --radius: 26px;
  --radius-btn: 18px;
  --press: 6px;
  --bw: 3px;
  --shadow-card: 0 8px 0 var(--cream-d), 0 12px 18px rgba(28,29,38,.20);

  /* legacy aliases → kit palette (recolours existing selectors in place) */
  --brand: var(--c-orange);  --brand2: var(--c-pink);
  --grape: var(--c-purple);  --gold: var(--c-yellow);
  --green: var(--c-green);   --green-dark: var(--c-green-d);
  --cyan: var(--c-teal);     --blue: var(--c-blue);
  --muted-d: var(--c-slate); --muted: var(--c-slate); --line: var(--cream-d);
  /* de-frosted: glass tokens now resolve to flat kit surfaces (per ui-kit.md).
     consumers go opaque white/cream + ink outline + hard block in place; per-surface
     border/text polish happens in the game-chrome pass. */
  --glass: var(--surface);
  --glass-brd: var(--ink);
  --glass-sh: 0 7px 0 var(--cream-d), 0 11px 16px rgba(28,29,38,.18);
  --panel: var(--surface);
  --txt-sh: none;
}

* { box-sizing: border-box; }

html, body { height: 100%; }
body {
  margin: 0;
  font-family: var(--font-text);
  color: #fff;
  -webkit-tap-highlight-color: transparent;
  overflow: hidden;                 /* game shell: no page scroll */
  position: fixed; inset: 0;        /* kill mobile rubber-band bounce */
  background: #14241c;
  touch-action: manipulation;       /* disable double-tap zoom */
  -webkit-touch-callout: none;      /* iOS: no long-press link/image sheet */
}

/* native feel: kill pull-to-refresh & scroll chaining at the root */
html, body { overscroll-behavior-y: none; }

/* native feel: no long-press text-selection loupe on touch devices
   (desktop parents can still copy receipts/errors) */
@media (pointer: coarse) {
  body { -webkit-user-select: none; user-select: none; }
  input, textarea, select { -webkit-user-select: text; user-select: text; }
}
img { -webkit-user-drag: none; }

/* ---------- game shell (landscape) ---------- */
/* Fills phone & tablet edge-to-edge; on desktop it's capped (letterboxed). */
.app-shell {
  position: fixed; inset: 0; margin: auto;
  width: min(100vw, 1366px); height: 100dvh; max-height: 100dvh;
  display: flex; flex-direction: column; overflow: hidden;
  padding-left: env(safe-area-inset-left);   /* notch (landscape) */
  padding-right: env(safe-area-inset-right);
}
/* tall desktop windows: contained landscape canvas instead of full-height stretch */
@media (min-width: 1400px) {
  .app-shell { height: min(100dvh, 900px); border-radius: 24px; overflow: hidden; }
}

/* portrait touch devices: ask to rotate (game is landscape-only) */
.rotate-note { display: none; }
@media (orientation: portrait) and (pointer: coarse) {
  body:not(.allow-portrait) .app-shell,
  body:not(.allow-portrait) .sheet-backdrop { display: none !important; }
  body:not(.allow-portrait) .rotate-note {
    position: fixed; inset: 0; z-index: 200; display: flex; flex-direction: column;
    align-items: center; justify-content: center; gap: 18px; text-align: center;
    padding: calc(30px + env(safe-area-inset-top)) 30px calc(30px + env(safe-area-inset-bottom));
  }
}
.rotate-note img { width: 140px;
  animation: rot-wobble 1.8s ease-in-out infinite; }
.rotate-note h2 { font-family: "Cause", sans-serif; font-size: 26px; color: #fff; text-shadow: var(--txt-sh); margin: 0; }
.rotate-note p { color: #ffe9dc; font-weight: 700; max-width: 300px; margin: 0; }
@keyframes rot-wobble { 0%,100% { transform: rotate(-12deg); } 50% { transform: rotate(12deg); } }
.game-top {
  flex: 0 0 auto; display: flex; align-items: center; gap: 10px;
  padding: 12px 14px; z-index: 30;
}
.gt-btn {
  width: 48px; height: 48px; flex: 0 0 auto; border-radius: 15px;
  border: var(--bw) solid var(--ink);
  background: var(--c-orange);
  display: grid; place-items: center; font-size: 20px; color: #fff; cursor: pointer;
  text-shadow: none;
  box-shadow: 0 5px 0 var(--c-orange-d);
  transition: transform .08s, filter .15s, box-shadow .08s;
}
@media (hover: hover) and (pointer: fine) {
  .gt-btn:hover { filter: brightness(1.08); }
}
.gt-btn img { width: 30px; height: 30px; object-fit: contain; display: block; }
.sheet-ic { width: 26px; height: 26px; object-fit: contain; }
.btn-ic { width: 22px; height: 22px; object-fit: contain; vertical-align: -5px; }
.title-ic { height: 36px; width: auto; object-fit: contain; vertical-align: -8px; }
.gt-btn:active {
  transform: translateY(4px);
  box-shadow: 0 1px 0 var(--c-orange-d);
}
.gt-stats { flex: 1; display: flex; gap: 8px; justify-content: center; align-items: center; }
.gt-chip {
  display: inline-flex; align-items: center; gap: 6px; font-weight: 800; font-size: 15px; color: var(--ink);
  font-family: var(--font-display);
  background: var(--cream);
  border: var(--bw) solid var(--ink);
  padding: 6px 14px; border-radius: 999px; text-shadow: none;
  box-shadow: 0 3px 0 var(--cream-d);
}
.gt-chip .badge-ic { width: 22px; height: 22px; }
/* game-name chip: lives in the navbar so the play area stays clean */
.gt-chip.gt-game { max-width: 38vw; }
.gt-chip.gt-game .gt-game-txt { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.gt-brand {
  font-family: var(--font-display); font-weight: 700; font-size: 22px; padding-left: 4px;
  color: var(--c-orange);
}
.gt-brand span { -webkit-text-fill-color: #fff; color: #fff; }
.gt-brand-img { height: 30px; display: block; margin-left: 4px; }
/* phone landscape: compact shell header (72px -> 56px, keeps 44px touch targets) */
@media (max-height: 480px) {
  .game-top { padding: 6px 12px; }
  .gt-btn { width: 44px; height: 44px; border-radius: 13px; }
  .gt-btn img { width: 26px; height: 26px; }
  .gt-chip { font-size: 13px; padding: 4px 12px; }
  .gt-chip .badge-ic { width: 18px; height: 18px; }
  .gt-brand-img { height: 26px; }
  .sheet { padding: 12px; }
  .sheet-title { padding: 2px 12px 6px; }
  .sheet a { padding: 10px 14px; font-size: 16px; }
  .sheet-ic { width: 22px; height: 22px; }
  .sheet-help { padding: 8px 14px 4px; }
  .sheet-close { padding: 11px; margin-top: 6px; }
}
/* narrow portrait phones (≈iPhone SE 375px): the full in-game HUD (home + back +
   game-name + stars + level + settings) is too wide for a 375px row and used to
   overflow — clipping the level chip and pushing the settings button off-screen.
   Compact the buttons + chips so every FUNCTIONAL control stays on-screen, and
   drop the decorative game-name chip on the tightest widths (the kid is already
   in the game; the title still shows on the course grid + wider screens). */
@media (max-width: 430px) {
  .game-top { gap: 7px; padding: 10px 10px; }
  .gt-btn { width: 44px; height: 44px; border-radius: 13px; }
  .gt-btn img { width: 26px; height: 26px; }
  .gt-stats { gap: 6px; }
  .gt-chip { font-size: 13px; padding: 5px 11px; gap: 5px; }
  .gt-chip .badge-ic { width: 18px; height: 18px; }
  .gt-chip.gt-game { max-width: 30vw; }
}
@media (max-width: 400px) {
  .gt-chip.gt-game { display: none; }
}
.stage-row { flex: 1 1 auto; display: flex; min-height: 0; }
.cikgu-rail { flex: 0 0 0; position: relative; transition: flex-basis .3s ease; }
.app-shell.has-cikgu .cikgu-rail { flex-basis: clamp(104px, 16vw, 200px); }
/* Portrait kid games: the cikgu rail otherwise eats ~28% of a phone's width and
   cramps the playfield. On allow-portrait pages, collapse the rail so the stage
   spans the full screen, and float the teacher as a small bottom-left mascot
   behind the play (pointer-events:none + low z-index → taps fall through, game
   art paints over her). Subject-scoped for a staged rollout — other subjects opt
   in by adding their `subject-<x>` body class to this selector list. Landscape
   keeps the full rail (this block is portrait-only). */
@media (orientation: portrait) {
  body.subject-pi.allow-portrait .app-shell.has-cikgu .cikgu-rail,
  body.subject-jawi.allow-portrait .app-shell.has-cikgu .cikgu-rail,
  body.subject-bm.allow-portrait .app-shell.has-cikgu .cikgu-rail,
  body.subject-english.allow-portrait .app-shell.has-cikgu .cikgu-rail,
  body.subject-matematik.allow-portrait .app-shell.has-cikgu .cikgu-rail,
  body.subject-math.allow-portrait .app-shell.has-cikgu .cikgu-rail,
  body.subject-sains.allow-portrait .app-shell.has-cikgu .cikgu-rail,
  body.subject-science.allow-portrait .app-shell.has-cikgu .cikgu-rail,
  body.subject-muzik.allow-portrait .app-shell.has-cikgu .cikgu-rail { flex-basis: 0; min-width: 0; }
  body.subject-pi.allow-portrait .app-shell.has-cikgu .cikgu-rail .cikgu-guide,
  body.subject-jawi.allow-portrait .app-shell.has-cikgu .cikgu-rail .cikgu-guide,
  body.subject-bm.allow-portrait .app-shell.has-cikgu .cikgu-rail .cikgu-guide,
  body.subject-english.allow-portrait .app-shell.has-cikgu .cikgu-rail .cikgu-guide,
  body.subject-matematik.allow-portrait .app-shell.has-cikgu .cikgu-rail .cikgu-guide,
  body.subject-math.allow-portrait .app-shell.has-cikgu .cikgu-rail .cikgu-guide,
  body.subject-sains.allow-portrait .app-shell.has-cikgu .cikgu-rail .cikgu-guide,
  body.subject-science.allow-portrait .app-shell.has-cikgu .cikgu-rail .cikgu-guide,
  body.subject-muzik.allow-portrait .app-shell.has-cikgu .cikgu-rail .cikgu-guide {
    position: fixed; inset: auto auto 4px 4px; align-items: flex-start;
    width: clamp(64px, 18vw, 84px); z-index: 1;
  }
}
.stage {
  flex: 1 1 auto; overflow-y: auto; overflow-x: hidden; position: relative; min-width: 0;
  -webkit-overflow-scrolling: touch; scrollbar-width: none;
  overscroll-behavior: contain;
}
.stage::-webkit-scrollbar { width: 0; height: 0; }

/* ---------- parent settings sheet ---------- */
.sheet-backdrop {
  position: fixed; inset: 0; z-index: 90; background: rgba(0,0,0,.5);
  display: flex; align-items: center; justify-content: center;
  padding: 12px; animation: sheet-fade .18s ease;
}
.sheet-backdrop[hidden] { display: none; }
.sheet {
  width: min(420px, 100%); background: var(--surface); color: var(--ink);
  border: 4px solid var(--ink); border-radius: 22px; padding: 16px;
  box-shadow: var(--shadow-card); animation: sheet-up .2s cubic-bezier(.2,.8,.2,1);
  max-height: 100%; overflow-y: auto; -webkit-overflow-scrolling: touch;
}
.sheet-title { font-weight: 800; font-size: 13px; letter-spacing: .5px; text-transform: uppercase;
  color: var(--muted); padding: 4px 12px 10px; }
.sheet a { display: flex; align-items: center; gap: 12px; padding: 15px 14px; border-radius: 14px;
  font-weight: 800; font-size: 17px; color: var(--ink); }
.sheet a:hover { background: var(--cream); }
.sheet-help { padding: 12px 14px 6px; text-align: center; font-size: 13px; font-weight: 600; color: var(--muted); }
.sheet-help a { display: inline; padding: 0; font-size: 13px; font-weight: 800; color: var(--ink); text-decoration: underline; }
.sheet-help a:hover { background: none; }
.sheet-close { width: 100%; margin-top: 8px; padding: 14px; border: var(--bw) solid var(--ink); border-radius: 14px;
  background: var(--cream); color: var(--ink); font-family: var(--font-display); font-weight: 800; font-size: 16px; cursor: pointer;
  box-shadow: 0 4px 0 var(--cream-d); }
@keyframes sheet-fade { from { opacity: 0; } }
@keyframes sheet-up { from { transform: scale(.92); opacity: 0; } }

/* ---------- immersive world background ---------- */
.world-bg {
  position: fixed; inset: 0; z-index: -3;
  background: url("https://desaverse.sgp1.cdn.digitaloceanspaces.com/assets/base/ui/bg.jpg?v=20260619") center/cover no-repeat;
  transform: scale(1.08);
  animation: drift 26s ease-in-out infinite alternate;
}
/* flat darkener over the flat-vector kampung bg so white chrome text stays
   legible — a single flat translucent fill, no gradient (kit: no gradient chrome) */
.world-veil {
  position: fixed; inset: 0; z-index: -2;
  background: rgba(12,22,17,.34);
}
/* flat hard-edged star dots (no soft radial bloom) */
.twinkles { position: fixed; inset: 0; z-index: -1; pointer-events: none;
  background-image:
    radial-gradient(circle at 20% 30%, #fff 0 1.4px, transparent 1.5px),
    radial-gradient(circle at 70% 20%, #fff 0 1.4px, transparent 1.5px),
    radial-gradient(circle at 40% 70%, #fff 0 1.1px, transparent 1.2px),
    radial-gradient(circle at 85% 60%, #fff 0 1.1px, transparent 1.2px),
    radial-gradient(circle at 55% 45%, #fff 0 1.4px, transparent 1.5px);
  animation: twinkle 4s ease-in-out infinite;
}
@keyframes drift { to { transform: scale(1.16) translate(-1.5%, -1%); } }
@keyframes twinkle { 0%,100% { opacity: .5; } 50% { opacity: 1; } }

.jawi { font-family: "Amiri", serif; }
a { color: #fff; text-decoration: none; }

/* ---------- HUD top bar (floating) ---------- */
.hud {
  position: sticky; top: 0; z-index: 40;
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 12px 20px; margin: 12px;
}
.brand {
  font-family: "Cause", sans-serif; font-weight: 700; font-size: 27px; letter-spacing: .5px;
  color: var(--c-orange);
}
.brand span { -webkit-text-fill-color: #fff; color: #fff; }
.hud nav { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.hud-pill {
  font-family: "Cause", sans-serif; font-weight: 700; font-size: 14px; color: #fff;
  padding: 9px 15px; border-radius: 999px;
  border: var(--bw) solid var(--ink);
  background: var(--c-orange);
  text-shadow: none;
  box-shadow: 0 4px 0 var(--c-orange-d);
  transition: transform .1s, filter .15s, box-shadow .1s;
}
.hud-pill:hover { filter: brightness(1.08); }
.hud-pill:active {
  transform: translateY(3px);
  box-shadow: 0 1px 0 var(--c-orange-d);
}
.hud-pill.ghost {
  background: var(--c-green);
  border-color: var(--ink); color: #fff;
  box-shadow: 0 4px 0 var(--c-green-d);
}

/* ---------- layout ---------- */
.wrap { width: 100%; margin: 0;
  padding: clamp(16px, 2.6vw, 32px) clamp(16px, 3vw, 40px) calc(30px + env(safe-area-inset-bottom)); }
.center { width: 100%; min-height: 100%;
  padding: 18px 18px calc(18px + env(safe-area-inset-bottom));
  display: flex; flex-direction: column; justify-content: center; }
/* short landscape phones: trim vertical padding so pages fit the stage */
@media (max-height: 480px) {
  .wrap { padding-top: 10px; padding-bottom: calc(8px + env(safe-area-inset-bottom)); }
}

/* ---------- glass + panels ---------- */
.glass {
  background: var(--glass);
  border: var(--bw) solid var(--ink); box-shadow: var(--glass-sh); border-radius: var(--radius);
}
.card, .panel {
  color: var(--ink); background: var(--surface);
  border: 4px solid var(--ink); border-radius: var(--radius);
  padding: 24px 26px;
  box-shadow: var(--shadow-card);
}
.card.flat { border-image: none; background: var(--surface); border: 4px solid var(--ink);
  border-radius: var(--radius); padding: 26px; filter: none; box-shadow: var(--shadow-card); }

/* reward badge images */
.badge-ic { width: 30px; height: 30px; object-fit: contain; vertical-align: middle; }
.badge-ic.lg { width: 40px; height: 40px; }

/* bespoke flat-vector emoji (kit-style, gen'd) — drop-in replacement for native
   glyphs, scales with the surrounding font-size so big game pieces stay big */
img.emoji-flat { width: 1em; height: 1em; vertical-align: -0.15em; object-fit: contain;
  display: inline-block; margin: 0 .04em; -webkit-user-select: none; user-select: none;
  -webkit-user-drag: none; }
.sheet-credit { margin-top: 6px; font-size: 11px; opacity: .55; }

/* flat-vector title chip (replaces the skeuomorphic wooden-ribbon PNG) */
.ribbon {
  display: inline-block; color: #fff; font-family: "Cause", sans-serif; font-weight: 700;
  font-size: 26px; line-height: 1;
  padding: 16px 40px; background: var(--c-orange);
  border: 4px solid var(--ink); border-radius: var(--radius);
  box-shadow: 0 6px 0 var(--c-orange-d);
}

/* ---------- buttons (flat hard-press, per ui-kit.md) ---------- */
.btn {
  --fill: var(--c-orange); --deep: var(--c-orange-d);
  position: relative; display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  cursor: pointer; font-family: var(--font-display); font-weight: 700; font-size: 18px;
  color: #fff; text-shadow: none; min-width: 130px; padding: 14px 28px;
  background: var(--fill); border: var(--bw) solid var(--ink); border-radius: var(--radius-btn);
  box-shadow: 0 var(--press) 0 var(--deep), 0 calc(var(--press) + 3px) 10px rgba(28,29,38,.22);
  transition: transform .06s ease, box-shadow .06s ease, filter .12s;
}
.btn:hover { filter: brightness(1.03); }
.btn:active { transform: translateY(calc(var(--press) - 1px));
  box-shadow: 0 1px 0 var(--deep), 0 2px 5px rgba(28,29,38,.3); }
.btn.green     { --fill: var(--c-green);  --deep: var(--c-green-d); }
.btn.secondary { --fill: var(--c-orange); --deep: var(--c-orange-d); }
.btn.red       { --fill: var(--c-red);    --deep: var(--c-red-d); }
.btn.blue      { --fill: var(--c-blue);   --deep: var(--c-blue-d); }
.btn.yellow    { --fill: var(--c-yellow); --deep: var(--c-yellow-d); color: var(--ink); }
.btn.block { width: 100%; }
.btn.lg { font-size: 22px; padding: 18px 36px; --press: 7px; }
.btn.sm { font-size: 15px; padding: 10px 18px; min-width: 0; --press: 5px; }
/* ghost = SECONDARY / cancel. Cream soft-panel surface (NOT transparent): these
   buttons float on the busy kampung bg (result cluster, in-game HUD), and a
   see-through plate left the ink label + ink outline melting into the dark paddy
   band — low-contrast, "not really visible". Cream gives the label a readable
   surface per the kit rule "cream/white surfaces behind any text, so words read
   over sky or grass", while staying clearly secondary vs the saturated
   green/orange/red primaries. Rides the base .btn hard-press via --fill/--deep. */
.btn.ghost {
  --fill: var(--cream); --deep: var(--cream-d); color: var(--ink);
  min-width: 0; padding: 13px 22px;
}

/* round image icon buttons */
.iconbtn {
  width: 58px; height: 58px; flex: 0 0 auto; padding: 0; border: 0; cursor: pointer; background: transparent;
  background-size: contain; background-repeat: no-repeat; background-position: center;
  transition: transform .08s, filter .15s;
}
.iconbtn:hover { filter: brightness(1.06); }
.iconbtn:active { transform: translateY(3px) scale(.94); }
.iconbtn.lg { width: 72px; height: 72px; }
.iconbtn.sound { background-image: url("https://desaverse.sgp1.cdn.digitaloceanspaces.com/assets/base/ui/ic_sound.png"); }
.iconbtn.back  { background-image: url("https://desaverse.sgp1.cdn.digitaloceanspaces.com/assets/base/ui/ic_back.png"); }
.iconbtn.play  { background-image: url("https://desaverse.sgp1.cdn.digitaloceanspaces.com/assets/base/ui/ic_play.png"); }
.iconbtn.check { background-image: url("https://desaverse.sgp1.cdn.digitaloceanspaces.com/assets/base/ui/ic_check.png"); }
.iconbtn.close { background-image: url("https://desaverse.sgp1.cdn.digitaloceanspaces.com/assets/base/ui/ic_close.png"); }
.iconbtn.left  { background-image: url("https://desaverse.sgp1.cdn.digitaloceanspaces.com/assets/base/ui/ic_left.png"); }
.iconbtn.right { background-image: url("https://desaverse.sgp1.cdn.digitaloceanspaces.com/assets/base/ui/ic_right.png"); }

/* ---------- forms ---------- */
.field { margin-bottom: 16px; }
.field label { display: block; font-weight: 700; margin-bottom: 6px; font-size: 14px; color: var(--ink); }
.field input {
  width: 100%; padding: 14px 16px; border-radius: 14px; border: var(--bw) solid var(--ink);
  font-family: var(--font-text); font-size: 16px; background: #fff; color: var(--ink);
}
.field input:focus { outline: none; box-shadow: 0 0 0 3px var(--c-blue); }

/* flash messages: native-style overlay toasts (don't push page content) */
.flash-stack {
  position: absolute; top: 10px; left: 50%; transform: translateX(-50%); z-index: 80;
  display: flex; flex-direction: column; gap: 8px; align-items: center;
  width: max-content; max-width: min(90%, 480px); pointer-events: none;
}
.flash { padding: 12px 16px; border-radius: 14px; font-weight: 700;
  background: var(--surface); border: var(--bw) solid var(--ink); color: var(--ink);
  box-shadow: 0 5px 0 var(--cream-d);
  animation: flash-out .3s ease 3.5s forwards; }
/* errors must stay readable — no auto-dismiss */
.flash-error { background: var(--c-red); color: #fff; animation: none; }
.flash-success { background: var(--c-green); color: #fff; }
@keyframes flash-out { to { opacity: 0; visibility: hidden; } }

h1 { font-family:"Cause",sans-serif; font-size: 34px; margin: 0 0 6px; }
h2 { font-family:"Cause",sans-serif; font-size: 24px; margin: 0 0 14px; }
.muted { color: var(--muted); }
.card .muted, .panel .muted { color: var(--muted); }
.sub { color: var(--muted); margin: 0 0 22px; }
.on-dark { color:#fff; text-shadow: var(--txt-sh); }
.on-dark .muted, .dim { color: rgba(255,255,255,.82); }

/* ---------- entrance anim ---------- */
.pop { animation: pop .5s cubic-bezier(.2,.9,.3,1.3) both; }
@keyframes pop { from { opacity:0; transform: translateY(16px) scale(.96); } to { opacity:1; transform:none; } }
.float { animation: floaty 3.4s ease-in-out infinite; }
@keyframes floaty { 0%,100%{ transform: translateY(0);} 50%{ transform: translateY(-12px);} }

/* ---------- kid home (game menu) ---------- */
.hero {
  position: relative; border-radius: 30px; overflow: hidden; margin-bottom: 16px;
  min-height: clamp(150px, 34vh, 260px); display: flex; align-items: center; padding: 24px 28px;
  background: var(--surface);
  border: 4px solid var(--ink); box-shadow: var(--shadow-card);
}
.hero .hi { flex: 1; z-index: 2; }
.hero .eyebrow { font-weight:700; letter-spacing:2px; text-transform:uppercase; font-size:13px; color: var(--c-orange-d); }
.hero h1 { font-size: 42px; color: var(--ink); line-height: 1.05; margin: 4px 0 10px; }
.hero .tag { font-size: 19px; font-weight: 700; color: var(--ink); opacity:.85; }
.hero .mascot { width: auto; height: clamp(110px, 28vh, 220px); align-self: center; z-index:2; }
.hero .glow { display: none; }

/* HUD stat chips */
.stats { display: flex; gap: 12px; flex-wrap: wrap; margin: 14px 2px 26px; }
.stats.tight { margin-bottom: 0; }
.stats .home-cta { align-self: center; font-size: 15px; }
.stat { display:flex; align-items:center; gap:10px; }
.stat + .stat { padding-left: 16px; border-left: 2px solid var(--cream-d); }
.stat .ic { font-size: 24px; }
.stat .n { font-family: var(--font-display); font-size: 24px; font-weight: 700; color: var(--ink); line-height:1; display:block; }
.stat .l { font-size: 12px; color: var(--c-slate); font-weight: 700; }

.section-title { font-family:"Cause",sans-serif; font-size: 22px; font-weight: 700; color:#fff; margin: 6px 2px 16px; text-shadow: var(--txt-sh); }
/* course-home header: course name + profile rank chip on one row */
.home-head { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.home-head .section-title { margin-bottom: 16px; }
.rank-chip { font-family: var(--font-display); font-weight: 800; font-size: 14px; color: var(--ink);
  background: var(--c-yellow); border: var(--bw) solid var(--ink);
  padding: 5px 12px; border-radius: 999px; margin: 0 0 16px;
  box-shadow: 0 3px 0 var(--c-yellow-d); white-space: nowrap; }
/* course-home Main/Latih section labels (only shown when both sections exist) */
.cat-label { font-family:"Cause",sans-serif; font-size: 15px; font-weight: 800; letter-spacing: 1.5px; text-transform: uppercase; color: rgba(255,255,255,.85); margin: 4px 2px 12px; text-shadow: var(--txt-sh); }
.module-grid + .cat-label { margin-top: 28px; }

/* course-home section tabs (Main / Latih) — kit chips, hard-press, one active.
   Inactive = cream + ink outline; active carries the semantic hue (Main slate,
   Latih teal) per docs/design/ui-kit.md. */
.cat-tabs { display: flex; justify-content: center; gap: 12px; margin: 4px 2px 18px; flex-wrap: wrap; }
.cat-tab {
  font-family: var(--font-display); font-weight: 800; font-size: 18px; letter-spacing: .5px;
  color: var(--ink); background: var(--cream); border: 3px solid var(--ink);
  border-radius: var(--radius-btn); padding: 12px 30px; cursor: pointer;
  box-shadow: 0 5px 0 var(--cream-d);
  transition: transform .06s ease, box-shadow .06s ease, background .12s ease, color .12s ease;
}
.cat-tab:active { transform: translateY(3px); box-shadow: 0 2px 0 var(--cream-d); }
/* active tab pops out: semantic hue, white text, slight lift so the current
   section is unmistakable against the cream inactive chip */
.cat-tab.is-active { color: #fff; transform: scale(1.06); box-shadow: 0 5px 0 rgba(28,29,38,.4); }
.cat-tab.main.is-active  { background: var(--c-slate); border-color: var(--c-slate-d, var(--ink)); }
.cat-tab.latih.is-active { background: var(--c-teal); border-color: var(--c-teal-d, var(--ink)); }
.cat-panel[hidden] { display: none; }

/* game module cards */
.module-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 20px; }
.module {
  position: relative; border-radius: var(--radius); padding: 18px; min-height: 200px; overflow: hidden;
  display: flex; flex-direction: column; justify-content: flex-end; color: var(--ink);
  background: var(--cream); border: 4px solid var(--ink);
  box-shadow: 0 7px 0 var(--cream-d), 0 11px 16px rgba(28,29,38,.18);
  transition: transform .1s ease, box-shadow .1s ease;
}
.module:active { transform: translateY(5px); box-shadow: 0 2px 0 var(--cream-d), 0 3px 6px rgba(28,29,38,.22); }
.module .icon-img { align-self: flex-end; margin: -8px -6px auto 0; width: 96px; height: 96px; object-fit: contain;
  transition: transform .25s; }
/* hover lift only on real pointers — on touch, :hover sticks after tap (reads as a glitch) */
@media (hover: hover) and (pointer: fine) {
  .module:hover { transform: translateY(-3px); box-shadow: 0 10px 0 var(--cream-d), 0 14px 18px rgba(28,29,38,.2); }
  .module:hover .icon-img { transform: translateY(-3px) rotate(-3deg) scale(1.05); }
}
.module h3 { font-family: var(--font-display); margin: 8px 0 3px; font-size: 26px; color: var(--ink); }
.module p { margin: 0 0 10px; opacity: .85; font-weight: 600; color: var(--ink); }
.module .play { align-self:flex-start; font-weight:800; font-size:13px; letter-spacing:1px; color: var(--ink);
  background: var(--surface); padding: 5px 12px; border-radius: 11px; border: 2px solid var(--ink); }
/* per-game mastery badge (best-stars earned / achievable) — arcade "Main" tiles
   only. Pinned top-left so it sits clear of the flush-right icon. */
.module .mastery { position: absolute; top: 12px; left: 12px; z-index: 1;
  font-family: var(--font-display); font-weight: 800; font-size: 13px; color: var(--ink);
  background: var(--surface); padding: 3px 10px; border-radius: 999px;
  border: 2px solid var(--ink); }
/* free-trial badge: a leveled game whose Level 1 is free to try (the rest is
   behind the paywall). Same pinned pill as .mastery but gold, so a parent sees
   at a glance which tiles are "try free" vs fully owned. */
.module .trial-badge { position: absolute; top: 12px; left: 12px; z-index: 1;
  font-family: var(--font-display); font-weight: 800; font-size: 12px; color: var(--ink);
  background: var(--gold); padding: 3px 10px; border-radius: 999px;
  border: 2px solid var(--ink); }
@media (max-width: 560px) { .module .trial-badge { top: 8px; left: 8px; font-size: 11px; padding: 3px 8px; } }
/* guest "try-first" banner: a slim CTA bar under the header on menu pages
   (course picker / course home) nudging a guest to sign up to save progress. */
.guest-banner {
  display: flex; align-items: center; justify-content: center; gap: 6px 12px;
  flex-wrap: wrap; text-align: center; text-decoration: none;
  margin: 0 auto 12px; max-width: 720px; width: calc(100% - 24px);
  padding: 8px 16px; border-radius: 14px; color: var(--ink);
  background: var(--gold); border: 2px solid var(--ink); box-shadow: var(--glass-sh);
  font-family: "Cause", sans-serif; font-weight: 700; font-size: 14px;
}
.guest-banner strong { text-decoration: underline; }
@media (max-width: 560px) { .guest-banner { font-size: 12px; padding: 7px 12px; } }
/* No per-slug tile colour: each game icon is its own coloured tile (see icon.md),
   so .module stays a calm cream frame and the icon carries the hue — per ui-kit.md. */

/* course-home redesign: icon-first tiles — big centered art, name only, no
   description/play button. Scoped to .course-home so play/home + beli keep the
   classic flush-right-icon card. */
/* calm theme: drop the per-slug gradients (too stimulating for a kid grid) for
   uniform translucent "liquid glass" — frosted, low-chroma, art carries colour. */
.course-home .module {
  background: var(--cream); border: 4px solid var(--ink);
  box-shadow: 0 7px 0 var(--cream-d), 0 11px 16px rgba(28,29,38,.18);
  justify-content: center; align-items: center; text-align: center; padding: 16px;
}
.course-home .module.locked { filter: grayscale(.6); opacity: .8; }
.course-home .module .icon-img { align-self: center; margin: 0 auto 8px; }
.course-home .module h3 { margin: 0; }
.course-home .module .play { align-self: center; }
@media (hover: hover) and (pointer: fine) {
  .course-home .module:hover .icon-img { transform: translateY(-4px) scale(1.08); }
}

/* Every game-menu asset sits in the same brand tile, including newer artwork
   exported as a transparent standalone object. Existing full-tile PNGs cover
   this foundation; transparent icons reveal it, so both sets keep one rounded-
   square silhouette without regenerating or forking the source art. */
.game-menu-icon {
  --game-icon-tile: var(--c-blue);
  background: var(--game-icon-tile);
  border: 3px solid var(--ink);
  border-radius: 22%;
  box-sizing: border-box;
  overflow: hidden;
}
.module-grid .module:nth-child(8n+1) .game-menu-icon,
.v2-game-grid .v2-tile:nth-child(8n+1) .game-menu-icon { --game-icon-tile: var(--c-blue); }
.module-grid .module:nth-child(8n+2) .game-menu-icon,
.v2-game-grid .v2-tile:nth-child(8n+2) .game-menu-icon { --game-icon-tile: var(--c-yellow); }
.module-grid .module:nth-child(8n+3) .game-menu-icon,
.v2-game-grid .v2-tile:nth-child(8n+3) .game-menu-icon { --game-icon-tile: var(--c-green); }
.module-grid .module:nth-child(8n+4) .game-menu-icon,
.v2-game-grid .v2-tile:nth-child(8n+4) .game-menu-icon { --game-icon-tile: var(--c-red); }
.module-grid .module:nth-child(8n+5) .game-menu-icon,
.v2-game-grid .v2-tile:nth-child(8n+5) .game-menu-icon { --game-icon-tile: var(--c-purple); }
.module-grid .module:nth-child(8n+6) .game-menu-icon,
.v2-game-grid .v2-tile:nth-child(8n+6) .game-menu-icon { --game-icon-tile: var(--c-teal); }
.module-grid .module:nth-child(8n+7) .game-menu-icon,
.v2-game-grid .v2-tile:nth-child(8n+7) .game-menu-icon { --game-icon-tile: var(--c-orange); }
.module-grid .module:nth-child(8n+8) .game-menu-icon,
.v2-game-grid .v2-tile:nth-child(8n+8) .game-menu-icon { --game-icon-tile: var(--c-pink); }
.v2-featured .game-menu-icon { --game-icon-tile: var(--c-teal); }

/* billing/beli product cards: dense, non-interactive variant of .module —
   ~90px of content (name + price + owned chip), no game-card lift on tap. */
.module-grid.products { grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 14px; }
.module.product { min-height: 0; padding: 14px 18px; justify-content: flex-start; cursor: default; }
.module.product h3 { font-size: 20px; margin-top: 0; }
.module.product:hover, .module.product:active { transform: none;
  box-shadow: 0 7px 0 var(--cream-d), 0 11px 16px rgba(28,29,38,.18); }
.module.product .icon-img { transition: none; }

/* small landscape (phones ~844x390/932x430) + narrow: compact module cards so a
   20-game grid is ~2 swipe screens instead of ~5. Applies to all .module-grid
   menu pages (play/home, play/course_home, billing/beli). */
@media (max-width: 760px), (max-height: 480px) {
  .module-grid { grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px; }
  .module { min-height: 128px; padding: 14px 16px; border-radius: 20px; }
  .module .icon-img { width: 56px; height: 56px; margin: -4px -2px auto 0; }
  .course-home .module .icon-img { width: 100%; height: auto; margin: 0 0 4px; }
  .module h3 { font-size: 18px; margin: 4px 0 2px; }
  .module p { font-size: 12px; margin: 0 0 6px; }
  .module .play { font-size: 11px; padding: 4px 10px; }
  .module .mastery { top: 8px; left: 8px; font-size: 11px; padding: 3px 8px; }
  .section-title, .home-head .section-title { margin: 2px 2px 8px; }
  .course-home .section-title { font-size: 24px; }
  .rank-chip { font-size: 12px; padding: 4px 10px; margin-bottom: 8px; }
  .back { margin-bottom: 8px; }
  .cat-tabs { gap: 10px; margin: 2px 2px 12px; }
  .cat-tab { font-size: 16px; padding: 10px 24px; }
}

/* course home (play/course_home): centred course name, kept pinned while the
   game grid scrolls beneath — .stage is the scrolling ancestor, so sticky top:0
   pins at the stage top. White text relies on text-shadow (--txt-sh) to read
   over bright cards. */
.course-home .section-title {
  position: sticky; top: 0; z-index: 5; margin: 0 -4px 14px; padding: 12px 6px;
  flex: 1 1 100%; text-align: center; font-size: 30px;
}
/* centre the course name + rank chip (chip wraps below the full-width title) */
.course-home .home-head { justify-content: center; }

/* ---------- course picker (play/home) — landscape-height compaction ----------
   Scoped to .home-pick (main_class on play/home.html) so course_home/beli keep
   the default module grid. Compaction keys off viewport HEIGHT — this is a
   landscape-first app, so width is never the limiting axis on target devices.
   Tablet tier (≤900px tall) trims the hero/cards; phone tier (≤520px tall)
   collapses the hero to a slim banner and turns the course grid into a
   horizontal snap row so the whole page fits the stage with no vertical scroll. */
@media (max-height: 900px) {
  .home-pick .hero { min-height: clamp(120px, 22vh, 200px); padding: 16px 24px; margin-bottom: 12px; }
  .home-pick .hero h1 { font-size: clamp(26px, 4.6vh, 38px); }
  .home-pick .hero .mascot { height: clamp(110px, 20vh, 170px); }
  .home-pick .section-title { margin: 2px 2px 8px; }
  .home-pick .module-grid { gap: 14px; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); }
  .home-pick .module { min-height: clamp(132px, 20vh, 170px); padding: 16px; }
  .home-pick .module h3 { font-size: 22px; }
}
@media (max-height: 520px) {
  .home-pick { padding-top: 10px; }
  .home-pick .hero { min-height: 0; padding: 6px 16px; margin-bottom: 6px; }
  .home-pick .hero h1 { font-size: 22px; margin: 2px 0 2px; }
  .home-pick .hero .eyebrow { display: none; }
  .home-pick .hero .tag { font-size: 14px; }
  .home-pick .hero .mascot { height: clamp(64px, 24vh, 110px); }
  .home-pick .hero .glow { width: 200px; height: 200px; }
  .home-pick .hero .stats { margin: 6px 2px 0; gap: 8px; }
  .home-pick .hero .stat + .stat { padding-left: 10px; }
  .home-pick .hero .stat .n { font-size: 18px; }
  .home-pick .hero .badge-ic.lg { width: 26px; height: 26px; }
  .home-pick .hero .home-cta { font-size: 13px; padding: 11px 14px; }
  .home-pick .section-title { font-size: 17px; margin: 0 2px 4px; }
  /* courses as a swipe row instead of a stacked grid */
  .home-pick .course-row { display: flex; gap: 14px; overflow-x: auto; overflow-y: hidden;
    padding: 4px 2px 8px; scroll-snap-type: x mandatory; scrollbar-width: none;
    -webkit-overflow-scrolling: touch; }
  .home-pick .course-row::-webkit-scrollbar { display: none; }
  .home-pick .course-row .module { flex: 0 0 clamp(200px, 26vw, 240px);
    min-height: clamp(110px, 30vh, 140px); padding: 12px 16px; scroll-snap-align: start; }
  .home-pick .course-row .module h3 { font-size: 20px; margin: 4px 0 2px; }
  .home-pick .course-row .module p { font-size: 13px; margin-bottom: 6px; }
  .home-pick .course-row .module .play { font-size: 11px; padding: 4px 10px; }
}

/* ---------- auth (cinematic) ---------- */
/* .stage gives .auth-wrap a definite height so min-height:100% resolves and
   .center's justify-content:center actually engages (no top-aligned dead space) */
.auth-wrap { min-height: 100%; display: flex; flex-direction: column; }
.auth-wrap .center { flex: 1 1 auto; }
/* width cap: 40px border-image frame + 18px padding per side ⇒ 500px ≈ 384px inner form */
.auth-card { width: 100%; max-width: 500px; margin: 0 auto; }
.auth-mascot { display:block; width: 170px; margin: 0 auto -36px; position: relative; z-index: 3; }
.brand-img { display:block; height: 58px; margin: 0 auto 10px; }
.auth-divider { display: flex; align-items: center; gap: 12px; margin: 16px 0;
  color: var(--ink-soft, #8a7f74); font-weight: 700; font-size: 13px; }
.auth-divider::before, .auth-divider::after { content: ""; flex: 1; height: 2px;
  background: var(--line); border-radius: 2px; }
.btn.google-btn {
  border-image: none; border: 2px solid var(--line); border-radius: 16px;
  background: #fff; color: var(--ink); text-shadow: none; text-decoration: none;
  padding: 14px 20px; min-width: 0; filter: none;
  box-shadow: 0 6px 0 var(--cream-d); font-size: 18px;
}
.btn.google-btn::after { display: none; }
.btn.google-btn:hover { filter: brightness(.98); box-shadow: 0 6px 0 var(--cream-d); }
.btn.google-btn:active { transform: translateY(3px); }
.google-g { width: 22px; height: 22px; flex: 0 0 auto; }
/* footer swap-page link: padded to a >=44px touch target without shifting layout */
.auth-alt-link { color: var(--brand); font-weight: 800; display: inline-block;
  padding: 12px 8px; margin: -12px -8px; }

/* tablet landscape / short desktop: compact so register fits 1024x768 */
@media (max-height: 860px) {
  .auth-mascot { width: 120px; margin-bottom: -32px; }
  .auth-card .brand-img { height: 42px; }
  .auth-card .sub { margin-bottom: 12px; }
  .auth-card .field { margin-bottom: 10px; }
  .auth-card .field input { padding: 10px 14px; }
  .auth-card .btn.lg { font-size: 18px; }
  /* google-btn carries .lg too — keep its flat 2px border, not the 9-slice widths */
  .auth-card .btn.lg.google-btn { border-width: 2px; padding: 10px 20px; }
  .auth-card .auth-divider { margin: 10px 0; }
}

/* phone landscape: mascot beside card (row) + aggressive vertical compaction */
@media (orientation: landscape) and (max-height: 480px) {
  .auth-wrap { padding: 8px 12px calc(8px + env(safe-area-inset-bottom)); }
  .auth-layout { flex-direction: row; align-items: center; justify-content: center;
    gap: 16px; padding: 8px 18px; }
  /* mascot PNG is ~1.8:1 tall — size by height, not width */
  .auth-layout .auth-mascot { height: clamp(96px, 45vh, 170px); width: auto;
    margin: 0; flex: 0 0 auto; }
  .auth-layout .auth-card { flex: 0 1 460px; width: auto; margin: 0; }
  .card.auth-card { padding: 18px 20px; }
  .auth-card .brand-img { height: 24px; margin-bottom: 2px; }
  .auth-card .sub { display: none; }
  .auth-card .field { margin-bottom: 4px; }
  .auth-card .field label { margin-bottom: 2px; font-size: 12px; }
  .auth-card .field input { padding: 6px 12px; font-size: 15px; }
  /* two-up fields so register's 3 fields fit a 390px-tall viewport */
  .auth-card form { display: grid; grid-template-columns: 1fr 1fr; gap: 0 10px; }
  .auth-card form .btn { grid-column: 1 / -1; }
  .auth-card .btn.lg { font-size: 16px; }
  .auth-card .btn.lg.google-btn { border-width: 2px; padding: 6px 14px; font-size: 14px; }
  .auth-card .auth-divider { margin: 2px 0; }
  .auth-card p.muted { margin: 4px 0 0 !important; font-size: 12px; }
  .auth-layout { padding-top: 0; padding-bottom: 0; }
  /* flex:1 already fills the wrap — min-height:100% double-counts and adds 1px scroll */
  .auth-wrap .center { min-height: 0; }
}

/* portrait phone: the height tiers above never fire on a tall phone (>860px),
   so base art + the wrap→center→card padding stack triple-strangle the width —
   subtitle and the Google label wrap, the card spills below the fold. Trim the
   padding stack, thin the 9-slice frames, and shrink the hero so the card fits
   one screen with labels on single lines. */
@media (max-width: 560px) {
  .auth-wrap { padding-left: 12px; padding-right: 12px; }
  .auth-layout { padding-left: 0; padding-right: 0; }
  .card.auth-card { padding: 22px 24px; }
  .auth-mascot { width: 124px; margin-bottom: -24px; }
  .auth-card .brand-img { height: 46px; }
  .auth-card .sub { font-size: 15px; margin-bottom: 14px; }
  .auth-card .field { margin-bottom: 12px; }
  .auth-card .btn.lg { font-size: 20px; }
  .auth-card .btn.lg.google-btn { border-width: 2px; padding: 13px 16px; font-size: 16px; }
  .auth-card .auth-divider { margin: 12px 0; }
}

/* ---------- profile picker ---------- */
.profiles-grid { display: flex; gap: 26px; flex-wrap: wrap; justify-content: center; }
.profile-pick { text-align: center; cursor: pointer; border: 0; background: none; font-family: inherit; }
.avatar {
  width: 124px; height: 124px; border-radius: 34px; display: grid; place-items: center;
  font-size: 60px; transition: transform .12s; border: 2px solid rgba(255,255,255,.5);
  background: var(--glass); box-shadow: var(--glass-sh);
}
.profile-pick:hover .avatar { transform: translateY(-8px) scale(1.05) rotate(-2deg); }
.profile-pick .name { display: block; margin-top: 12px; font-weight: 800; font-size: 18px; color:#fff; text-shadow: var(--txt-sh); }
.profile-pick .meta { display:block; font-size:13px; color: rgba(255,255,255,.85); font-weight:700; }
.avatar.add { color: var(--muted); }  /* was #fff = invisible + on the light "add" card */
.avatar-img { width: 104px; height: 104px; object-fit: contain; }
.avatar-opt img { width: 100%; height: 100%; object-fit: contain; }
/* corner gear badge on each kid tile — 44px touch target, 20px icon centered */
.edit-profile { position: absolute; top: -10px; right: -10px; width: 44px; height: 44px;
  border-radius: 50%; border: 2px solid var(--line); background: #fff; cursor: pointer;
  padding: 0; font-size: 15px; line-height: 1; }
.edit-profile img { width: 20px; height: 20px; display: block; margin: auto; }

/* short landscape phones: profile picker must fit the stage without scroll */
@media (max-height: 480px) {
  .page-title { font-size: clamp(24px, 6.5vh, 34px); }
  .page-sub { font-size: 13px; }
  .profiles-grid { gap: 18px; }
  .profile-pick .avatar { width: 96px; height: 96px; border-radius: 26px; font-size: 46px; }
  .profile-pick .avatar-img { width: 80px; height: 80px; }
  .profile-pick .name { margin-top: 6px; font-size: 16px; }
  /* the add/edit form's avatar picker is inline-sized in profiles/index.html, so
     it can't compact via class — shrink it here (!important beats inline) so the
     open form + Save button still fit a short landscape stage without scrolling */
  #add-form .avatar-opt, #edit-form .avatar-opt { width: 66px !important; height: 78px !important; }
  #add-form .avatar-opt img, #edit-form .avatar-opt img { width: 46px !important; height: 46px !important; }
}

/* ---------- Cikgu Intan guide (lives in the side rail) ---------- */
.cikgu-guide { position: absolute; inset: 0; z-index: 60; pointer-events: none;
  display: flex; flex-direction: column; justify-content: flex-end; align-items: center;
  padding: 6px; opacity: 0; transform: translateY(20px);
  transition: opacity .3s ease, transform .3s ease; }
.cikgu-guide.show { opacity: 1; transform: translateY(0); }
.cikgu-guide img { width: 100%; max-width: 190px; display: block; }
.cikgu-guide.bounce img { animation: cikgu-pop .42s ease; }
@keyframes cikgu-pop { 0%{transform:scale(.94)} 50%{transform:scale(1.05)} 100%{transform:scale(1)} }
/* speech bubble sits ABOVE her, tail pointing down */
.cikgu-bubble { position: relative; width: 100%; margin-bottom: 10px;
  background: var(--surface); color: var(--ink); border: var(--bw) solid var(--ink); font-family: var(--font-text); font-weight: 700;
  font-size: clamp(12px, 1.5vw, 15px); line-height: 1.22; text-align: center;
  padding: 10px 12px; border-radius: 16px; box-shadow: 0 5px 0 var(--cream-d);
  opacity: 0; transform: translateY(6px) scale(.96); transform-origin: 50% 100%;
  transition: opacity .2s ease, transform .2s ease; }
.cikgu-bubble.show { opacity: 1; transform: translateY(0) scale(1); }
.cikgu-bubble:after { content: ""; position: absolute; left: 50%; bottom: -8px; transform: translateX(-50%);
  border: 9px solid transparent; border-top-color: #fff; border-bottom: 0; }

/* short viewports (phone + tablet landscape, laptops): tighter page header
   so the game stage gets the height */
@media (max-width: 760px), (max-height: 900px) {
  .wrap > .back { margin-bottom: 8px; }
  .row-between .page-title, .wrap > .page-title { font-size: clamp(22px, 3.6vh, 30px); margin-bottom: 4px; }
  .row-between .stat { padding: 6px 12px; }
  .wrap > .dim { margin: 6px 0 12px; }
}

.quiz { max-width: 560px; margin: 0 auto; text-align: center; }
.quiz .progress-dots { display: flex; gap: 8px; justify-content: center; margin-bottom: 18px; }
.quiz .dot { width: 14px; height: 14px; border-radius: 50%; background: rgba(255,255,255,.3); }
.quiz .dot.done { background: var(--green); box-shadow: 0 0 10px var(--green); }
.quiz .dot.current { background: var(--gold); box-shadow: 0 0 12px var(--gold); }
.quiz .big-glyph {
  font-size: 150px; line-height: 1.1; color: #d4541f;
  background: var(--surface); border-radius: 30px;
  box-shadow: 0 8px 0 var(--cream-d), 0 12px 18px rgba(28,29,38,.2);
  padding: 14px 30px; margin: 0 auto 22px; display: inline-block; min-width: 240px; border: 4px solid var(--ink);
}
.quiz .ask { font-size: 22px; font-weight: 800; color:#fff; margin-bottom: 18px; text-shadow: var(--txt-sh); }
.options { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
.opt {
  padding: 22px; border-radius: 18px; background: var(--panel); box-shadow: 0 6px 0 var(--cream-d);
  border: 3px solid var(--ink); font-family:"Cause",sans-serif; font-size: 23px; font-weight: 700; cursor: pointer; color: var(--ink);
  transition: transform .08s;
}
.opt:hover:not(:disabled) { transform: translateY(-3px); }
/* shared option sizing — one place to keep touch targets ≥64px for small fingers */
.opt.glyph-opt { font-size: 38px; padding: 16px 22px; min-width: 64px; min-height: 64px; }
.opt.glyph-opt.lg { font-size: 44px; }
.opt.emoji-opt { font-size: 52px; padding: 14px 20px; line-height: 1; min-width: 64px; min-height: 64px; }
.opt.correct { border-color: var(--green); background: #e7faf0; }
.opt.wrong { border-color: #ff5252; background: #ffe9e9; }
.opt:disabled { cursor: default; }
.dg-ear { font-size: 80px; line-height: 1; margin: 10px 0; }
#dg-play { margin-bottom: 16px; }
/* shared quiz-card prompt pieces (bina etc.) */
.qz-pic { font-size: 80px; line-height: 1; margin: 10px 0; }
.qz-sub { font-size: 18px; font-weight: 700; color: rgba(255,255,255,.9); margin-bottom: 8px; }
#bn-slots { direction: rtl; margin: 10px 0 18px; }
.bn-slot { display: inline-block; min-width: 1.8em; height: 1.4em; line-height: 1.4em;
  font-size: 48px; border-bottom: 4px solid var(--gold); margin: 0 8px; vertical-align: middle; }
#tk-name { font-size: 22px; font-weight: 800; color: rgba(255,255,255,.95); margin: 10px 0 0; }
#tk-skeleton { font-size: 110px; line-height: 1.4; margin: 4px 0 10px; direction: rtl; }
/* tablet landscape / laptops: quiz cards sit a touch tighter */
@media (max-width: 760px), (max-height: 900px) {
  .quiz .progress-dots { margin-bottom: 10px; }
  #tk-skeleton { font-size: 96px; line-height: 1.3; }
}
/* phone landscape: quiz-card games must fit a ~314px stage — tighter rhythm,
   options as a centered wrap row instead of stacked grid */
@media (max-height: 480px) {
  .quiz .progress-dots { margin-bottom: 8px; }
  .quiz .ask { font-size: 17px; margin-bottom: 8px; }
  .quiz .big-glyph { font-size: 90px; padding: 8px 22px; margin-bottom: 12px; min-width: 170px; }
  .options { display: flex; flex-wrap: wrap; justify-content: center; gap: 10px; }
  .opt { padding: 12px 18px; font-size: 19px; min-height: 48px; }
  .opt.glyph-opt { font-size: 32px; padding: 10px 18px; }
  .opt.emoji-opt { font-size: 40px; padding: 10px 16px; }
  .dg-ear { font-size: 38px; margin: 2px 0; }
  .qz-pic { font-size: 40px; margin: 2px 0; }
  .qz-sub { font-size: 14px; margin-bottom: 4px; }
  .bn-slot { font-size: 32px; border-bottom-width: 3px; }
  #bn-slots { margin: 4px 0 10px; }
  #dg-play { margin-bottom: 0; font-size: 16px; }
  .hud { margin: 0 0 6px; padding: 4px 8px; }
  .hud .page-title { font-size: clamp(20px, 3.8vh, 28px); }
  .hud .back { margin-bottom: 0; }
  /* prompt column left, answers right — fits the 314px stage */
  #dg-question { display: grid; grid-template-columns: auto 1fr; column-gap: 20px;
    align-items: center; justify-items: center;
    grid-template-areas: "ear opts" "ask opts" "play opts"; }
  #dg-question .dg-ear { grid-area: ear; }
  #dg-question .ask { grid-area: ask; max-width: 240px; }
  #dg-question #dg-play { grid-area: play; }
  #dg-question .options { grid-area: opts; align-content: center; }
  #bn-question { display: grid; grid-template-columns: auto 1fr; column-gap: 20px;
    align-items: center; justify-items: center;
    grid-template-areas: "pic opts" "sub opts" "ask opts" "slots opts" "built opts"; }
  #bn-question .qz-pic { grid-area: pic; }
  #bn-question .qz-sub { grid-area: sub; }
  #bn-question .ask { grid-area: ask; max-width: 240px; font-size: 15px; }
  #bn-question #bn-slots { grid-area: slots; }
  #bn-question #bn-built { grid-area: built; }
  #bn-question .options { grid-area: opts; align-content: center; }
  #tk-name { font-size: 16px; margin: 0; }
  #tk-skeleton { font-size: 64px; line-height: 1.3; margin: 0 0 4px; }
  #tk-question { display: grid; grid-template-columns: auto 1fr; column-gap: 20px;
    align-items: center; justify-items: center;
    grid-template-areas: "name opts" "glyph opts" "ask opts"; }
  #tk-question #tk-name { grid-area: name; }
  #tk-question #tk-skeleton { grid-area: glyph; }
  #tk-question .ask { grid-area: ask; max-width: 240px; font-size: 15px; }
  #tk-question .options { grid-area: opts; align-content: center; }
}
/* hint after repeated wrongs: quiz.js pulses the .is-answer option */
.opt.hint { animation: hint-pulse 1s ease-in-out infinite; border-color: var(--gold); }
@keyframes hint-pulse {
  0%, 100% { box-shadow: 0 10px 22px rgba(8,20,14,.32); transform: scale(1); }
  50% { box-shadow: 0 0 26px var(--gold); transform: scale(1.07); }
}
/* S6 first-miss reteach: a brief, FINITE gold pulse on the right option (the
   sticky infinite .hint above still waits for the 2nd miss) */
.opt.reteach { animation: hint-pulse 0.8s ease-in-out 2; border-color: var(--gold); }

.score-pill { font-size: 24px; font-weight: 800; color:#fff; text-shadow: var(--txt-sh); }
/* small replay-the-question speaker button (tangkap, jejak, roda...) */
.mini-play { background: rgba(255,255,255,.18); border: 1px solid rgba(255,255,255,.35);
  border-radius: 999px; font-size: 18px; padding: 4px 10px; cursor: pointer; vertical-align: middle; }
/* cahaya celebration burst (App.celebrate): wrapper is a 0×0 anchor at the
   tap point; children center on it with negative offsets. Decorative only —
   pointer-events: none, no game logic waits on it. ~1.6s total:
   pop-in bounce (0–.45s) → brief hold → drift up + fade (.6–1.6s). */
.cele { position: fixed; width: 0; height: 0; pointer-events: none; z-index: 99; }
.cele-cahaya {
  position: absolute; left: -42px; top: -42px; width: 84px; height: auto;
  filter: drop-shadow(0 0 10px var(--gold));
  animation: cele-pop .45s cubic-bezier(.2, 1.6, .4, 1) both,
             cele-rise 1s ease-in .6s forwards;
}
@keyframes cele-pop {
  from { transform: scale(0) rotate(-14deg); opacity: 0; }
  to   { transform: scale(1) rotate(0); opacity: 1; }
}
@keyframes cele-rise { to { transform: translateY(-120px) scale(1.3); opacity: 0; } }
.cele-ring {
  position: absolute; left: -46px; top: -46px; width: 92px; height: 92px;
  border-radius: 50%; border: 3px solid var(--gold);
  box-shadow: 0 0 16px var(--gold), inset 0 0 12px var(--gold);
  animation: cele-ring .8s ease-out .1s both;
}
@keyframes cele-ring {
  from { transform: scale(.25); opacity: .9; }
  to   { transform: scale(2);   opacity: 0; }
}
.cele-spark {
  position: absolute; left: -8px; top: -8px; color: var(--gold);
  text-shadow: 0 0 8px var(--gold); line-height: 1;
  animation: cele-spark .9s ease-out both;
}
@keyframes cele-spark {
  from { transform: translate(0, 0) scale(.4) rotate(0); opacity: 0; }
  20%  { opacity: 1; }
  to   { transform: translate(var(--dx), var(--dy)) scale(1.15) rotate(120deg); opacity: 0; }
}

/* ---------- sukukata ---------- */
/* fills the stage, no scroll: dots row + two columns (build | letter banks) */
.sk-game { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
.sk-game .progress-dots { flex: 0 0 auto; display: flex; gap: 8px; justify-content: center; margin: 2px 0 10px; }
.sk-game .progress-dots .dot { width: 14px; height: 14px; border-radius: 50%; background: rgba(255,255,255,.3); }
.sk-game .progress-dots .dot.done { background: var(--green); box-shadow: 0 0 10px var(--green); }
.sk-game .progress-dots .dot.current { background: var(--gold); box-shadow: 0 0 12px var(--gold); }

.sk-stage {
  position: relative; flex: 1 1 auto; min-height: 0;
  display: grid; grid-template-columns: minmax(260px, 44%) 1fr;
  gap: clamp(20px, 4vw, 56px); align-items: center; padding: 0 clamp(8px, 2vw, 32px);
}

/* drag-direction cue: banks (right) → slots (left), so it points + drifts left.
   shown until the first letter is placed, then hidden for the rest of the game */
.sk-dragcue {
  position: absolute; top: 50%; left: 46%; transform: translate(-50%, -50%);
  display: flex; align-items: center; gap: 8px; pointer-events: none; z-index: 5;
  font-family: "Cause", sans-serif; font-weight: 800; font-size: clamp(15px, 1.8vw, 19px);
  color: #fff; text-shadow: var(--txt-sh); white-space: nowrap;
  animation: sk-cue 1.4s ease-in-out infinite;
}
.sk-cue-arrow { width: clamp(15px, 1.9vw, 22px); height: clamp(15px, 1.9vw, 22px); flex: 0 0 auto;
  border-left: 5px solid #fff; border-bottom: 5px solid #fff; border-radius: 3px;
  transform: rotate(45deg); }
@keyframes sk-cue { 0%, 100% { transform: translate(-50%, -50%); } 50% { transform: translate(calc(-50% - 18px), -50%); } }

/* left: listen + build slots, grouped in a frosted card so the scene behind
   doesn't bleed through the empty drop slots */
.sk-build {
  display: flex; flex-direction: column; align-items: center; gap: 16px; text-align: center;
  justify-self: center; max-width: 420px; width: 100%;
  padding: clamp(20px, 3.4vh, 36px) clamp(20px, 3vw, 38px) clamp(30px, 4vh, 44px);
  border-radius: var(--radius); background: var(--glass);
  border: var(--bw) solid var(--ink); box-shadow: var(--glass-sh);
}
.sk-build .ask { font-size: clamp(18px, 2.4vw, 24px); font-weight: 800; color: var(--ink); margin: 0; text-shadow: var(--txt-sh); }
.sk-listen {
  display: inline-flex; align-items: center; gap: 10px; cursor: pointer;
  font-family: "Cause", sans-serif; font-weight: 700; font-size: clamp(20px, 2.6vw, 26px); color: #fff;
  padding: 14px 34px; border-radius: 999px; border: var(--bw) solid var(--ink);
  background: var(--c-orange); text-shadow: none;
  box-shadow: 0 6px 0 var(--c-orange-d), 0 12px 20px rgba(8,20,14,.45);
  transition: transform .1s, filter .15s, box-shadow .1s;
}
.sk-listen:hover { filter: brightness(1.07); }
.sk-listen:active { transform: translateY(4px); box-shadow: 0 2px 0 var(--c-orange-d), 0 5px 10px rgba(8,20,14,.4); }
.sk-spk { width: 1.25em; height: 1.25em; flex: 0 0 auto; color: #fff; }
.sk-rumi { font-size: clamp(26px, 3.6vw, 40px); font-weight: 800; color: var(--ink); text-shadow: var(--txt-sh); min-height: 1.1em; }

/* RTL: huruf (consonant) sits right, bunyi (vowel) left — real jawi order */
.sk-slots { direction: rtl; display: flex; align-items: center; justify-content: center; gap: clamp(12px, 2vw, 22px); margin-top: 8px; }
.sk-slot {
  position: relative; width: clamp(88px, 11vw, 128px); height: clamp(88px, 11vw, 128px);
  border-radius: 24px; background: rgba(20,40,30,.28); border: 3px dashed rgba(255,255,255,.6);
  display: grid; place-items: center; transition: transform .12s, background .15s, border-color .15s;
}
.sk-slot:not(.filled) { animation: sk-breathe 1.8s ease-in-out infinite; }
@keyframes sk-breathe { 0%, 100% { border-color: rgba(255,255,255,.55); } 50% { border-color: var(--gold); } }
.sk-slot .sk-glyph { font-size: clamp(42px, 6vw, 66px); line-height: 1; color: #fff; }
.sk-slot.filled { background: var(--panel); border-style: solid; border-color: var(--gold); box-shadow: 0 6px 0 var(--cream-d); animation: sk-pop .28s cubic-bezier(.2,.9,.3,1.4) both; }
@keyframes sk-pop { 0% { transform: scale(.6); } 60% { transform: scale(1.12); } 100% { transform: scale(1); } }
.sk-slot.filled .sk-glyph { color: #d4541f; }
.sk-slot.drop-hi { transform: scale(1.07); border-color: var(--gold); background: rgba(255,201,60,.28); }
.sk-slot-lbl { position: absolute; bottom: -24px; left: 0; right: 0; font-size: 13px; font-weight: 800; color: var(--muted); }
.sk-plus { font-size: clamp(26px, 3vw, 36px); font-weight: 800; color: var(--muted); }
.sk-built {
  font-size: clamp(64px, 9vw, 104px); line-height: 1.2; direction: rtl; color: #d4541f;
  background: var(--surface); border-radius: 26px; padding: 6px 30px;
  border: 4px solid var(--ink); box-shadow: 0 8px 0 var(--cream-d), 0 12px 18px rgba(28,29,38,.2);
}

/* LTR (rumi courses, e.g. bm): normal reading order + Latin font for the
   slots/built which carry a hardcoded .jawi class in the markup */
.sk-ltr .sk-slots, .sk-ltr .sk-built { direction: ltr; }
.sk-ltr .sk-glyph, .sk-ltr .sk-built { font-family: "Cause", sans-serif; }

/* right: letter banks */
.sk-banks { display: flex; flex-direction: column; gap: clamp(14px, 3vh, 30px); justify-content: center; }
.sk-bank-lbl { font-size: clamp(14px, 1.8vw, 17px); font-weight: 800; color: #fff; text-shadow: var(--txt-sh); text-align: center; margin-bottom: 10px; }
.sk-tiles { display: grid; gap: clamp(10px, 1.6vw, 16px); }
#sk-cons { grid-template-columns: repeat(2, 1fr); }
#sk-vowels { grid-template-columns: repeat(3, 1fr); max-width: 420px; margin: 0 auto; }
.sk-tile { touch-action: none; user-select: none; -webkit-user-select: none; }
.sk-tile.picked { outline: 3px solid var(--gold); outline-offset: 2px; }
.sk-tile.drag-src { opacity: .35; }
.sk-ghost {
  position: fixed; z-index: 120; transform: translate(-50%, -50%); pointer-events: none;
  display: grid; place-items: center; font-size: 38px; color: var(--ink); border-radius: 18px;
  background: var(--panel); border: 3px solid var(--gold); box-shadow: 0 8px 0 var(--cream-d), 0 12px 18px rgba(28,29,38,.2);
}

@media (max-width: 760px) {
  .sk-stage { grid-template-columns: 1fr; align-content: center; gap: 22px; }
  .sk-built { font-size: clamp(56px, 16vw, 90px); }
  .sk-dragcue { display: none !important; } /* columns stack — left arrow no longer maps (!important beats the JS toggle's inline display) */
}

/* phone landscape (short height, wide enough to keep 2 columns): tighten the
   build card so slots + listen + result never overflow the clipped stage */
@media (max-height: 480px) {
  .sk-build { gap: 8px; padding: 12px 18px 18px; max-width: 380px; }
  .sk-build .ask { font-size: 16px; }
  .sk-listen { font-size: 18px; padding: 9px 24px; }
  .sk-rumi { font-size: 24px; min-height: 0; }
  .sk-slots { gap: 12px; margin-top: 2px; }
  .sk-slot { width: clamp(64px, 18vh, 92px); height: clamp(64px, 18vh, 92px); border-radius: 18px; }
  .sk-slot .sk-glyph { font-size: clamp(36px, 10vh, 50px); }
  .sk-slot-lbl { bottom: -18px; font-size: 11px; }
  .sk-built { font-size: clamp(48px, 13vh, 74px); padding: 4px 22px; }
  .sk-banks { gap: 8px; }
  .sk-bank-lbl { margin-bottom: 4px; }
  .sk-tiles { gap: 8px; }
  .sk-game .sk-tile { min-width: 0; min-height: 0; height: clamp(46px, 13vh, 64px);
    display: grid; place-items: center; font-size: clamp(24px, 7vh, 34px); padding: 0 8px; }
  #sk-vowels { max-width: 320px; }
}

/* ---------- padan ---------- */
/* fills the stage, no scroll: dots row + picture board on top + word-card
   dock below. The child lifts the word card onto its picture (or taps the
   picture) — the classroom "padankan kad" gesture. */
.pd-game { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
.pd-game .progress-dots { flex: 0 0 auto; display: flex; gap: 8px; justify-content: center; margin: 2px 0 10px; }
.pd-game .progress-dots .dot { width: 14px; height: 14px; border-radius: 50%; background: rgba(255,255,255,.3); }
.pd-game .progress-dots .dot.done { background: var(--green); box-shadow: 0 0 10px var(--green); }
.pd-game .progress-dots .dot.current { background: var(--gold); box-shadow: 0 0 12px var(--gold); }

.pd-stage {
  flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column;
  gap: clamp(10px, 2vh, 22px); padding: 0 clamp(8px, 2vw, 32px);
}

/* picture board: frosted card so tiles read as one answer zone over the scene */
.pd-board-card {
  flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; justify-content: center;
  gap: clamp(8px, 1.6vh, 16px); width: min(100%, 960px); margin: 0 auto;
  padding: clamp(12px, 2.4vh, 24px) clamp(14px, 2.4vw, 30px);
  border-radius: var(--radius); background: var(--glass);
  border: var(--bw) solid var(--ink); box-shadow: var(--glass-sh);
}
.pd-ask { font-size: clamp(16px, 2.2vw, 22px); font-weight: 800; color: var(--ink); margin: 0; text-shadow: var(--txt-sh); }
.pd-board {
  display: grid; gap: clamp(10px, 1.6vw, 18px);
  grid-template-columns: repeat(auto-fit, minmax(clamp(96px, 14vw, 170px), 1fr));
  align-content: center;
}
.pd-pic {
  position: relative; display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 2px; padding: clamp(8px, 1.6vh, 16px); min-height: clamp(86px, 16vh, 150px);
  font-size: clamp(38px, 8vh, 62px); line-height: 1.15;
  transition: transform .12s, border-color .15s, background .15s;
}
.pd-pic .pd-emoji { display: block; word-break: break-all; }
/* picture banks (sains): CDN image fills the tile like the emoji glyph does */
.pd-pic .pd-img { display: block; width: auto; height: clamp(56px, 12vh, 104px);
  max-width: 100%; object-fit: contain; pointer-events: none; }
/* multi-emoji picture banks (counting): smaller, wrapping rows */
.pd-board.pd-sm .pd-pic { font-size: clamp(22px, 4.6vh, 34px); }
/* on match the word lands inside its picture — word next to meaning */
.pd-pic .pd-match { display: none; font-size: clamp(20px, 4vh, 32px); line-height: 1.4; color: #d4541f; font-weight: 700; }
.pd-pic.matched .pd-match { display: block; animation: pd-pop .28s cubic-bezier(.2,.9,.3,1.4) both; }
@keyframes pd-pop { 0% { transform: scale(.6); } 60% { transform: scale(1.12); } 100% { transform: scale(1); } }
.pd-pic.drop-hi { transform: scale(1.07); border-color: var(--gold); background: rgba(255,231,186,.95); }

/* word-card dock */
.pd-dock { position: relative; flex: 0 0 auto; display: flex; flex-direction: column; align-items: center;
  padding: clamp(6px, 1.4vh, 14px) 0 clamp(10px, 2vh, 18px); }
.pd-card {
  cursor: grab; touch-action: none; user-select: none; -webkit-user-select: none;
  text-align: center; padding: clamp(6px, 1.2vh, 12px) clamp(26px, 4vw, 48px);
  color: #d4541f; background: var(--surface);
  border: 4px solid var(--ink); border-radius: 26px;
  box-shadow: 0 8px 0 var(--cream-d), 0 12px 18px rgba(28,29,38,.2);
  animation: pd-breathe 1.8s ease-in-out infinite;
}
@keyframes pd-breathe { 0%, 100% { border-color: rgba(255,255,255,.7); } 50% { border-color: var(--gold); } }
.pd-word { font-size: clamp(44px, 9vh, 80px); line-height: 1.3; }
.pd-word.jawi { direction: rtl; }
.pd-jawi { direction: rtl; font-size: clamp(26px, 5vh, 46px); line-height: 1.25; color: #1f6f3f; }
.pd-jawi:empty { display: none; }
.pd-rumi { font-size: clamp(14px, 2.4vh, 19px); font-weight: 800; color: #a8623f; }
.pd-rumi:empty { display: none; }
.pd-card.drag-src { opacity: .35; animation: none; }
/* correct: the card has flown into the picture */
.pd-card.away { opacity: 0; transform: translateY(-18px) scale(.6); transition: transform .3s, opacity .3s; animation: none; }
/* wrong: the card shakes "no" and stays home */
.pd-card.shake { animation: pd-shake .4s ease; }
@keyframes pd-shake { 0%, 100% { transform: translateX(0); } 25% { transform: translateX(-10px); } 75% { transform: translateX(10px); } }
.pd-ghost {
  position: fixed; z-index: 120; transform: translate(-50%, -50%); pointer-events: none;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  color: #d4541f; background: var(--surface);
  border: 4px solid var(--gold); border-radius: 26px; box-shadow: 0 8px 0 var(--cream-d), 0 12px 18px rgba(28,29,38,.2);
}

/* drag cue: card travels up to the pictures — points + drifts up. Shown
   until the first answer, then hidden for the rest of the game */
.pd-cue {
  position: absolute; top: 0; left: 50%; transform: translate(-50%, -100%);
  display: flex; align-items: center; gap: 8px; pointer-events: none; z-index: 5;
  font-family: "Cause", sans-serif; font-weight: 800; font-size: clamp(14px, 1.8vw, 18px);
  color: #fff; text-shadow: var(--txt-sh); white-space: nowrap;
  animation: pd-cue 1.4s ease-in-out infinite;
}
.pd-cue-arrow { width: clamp(13px, 1.7vw, 20px); height: clamp(13px, 1.7vw, 20px); flex: 0 0 auto;
  border-left: 5px solid #fff; border-top: 5px solid #fff; border-radius: 3px;
  transform: rotate(45deg); }
@keyframes pd-cue { 0%, 100% { transform: translate(-50%, -100%); } 50% { transform: translate(-50%, calc(-100% - 14px)); } }


/* reverse mode (opts.reverse): the dock card holds the situation picture and
   the board holds doa word tiles — read the picture, pick its doa. Higher
   specificity than the base/mobile .pd-pic so the word tiles stay readable. */
.pd-reverse .pd-word { font-size: clamp(46px, 11vh, 96px); line-height: 1.1; }
.pd-reverse .pd-pic { font-size: clamp(15px, 2.5vh, 23px); line-height: 1.2; }
.pd-reverse .pd-pic .pd-wordtile { display: block; font-weight: 800;
  color: #d4541f; word-break: break-word; }
.pd-reverse .pd-pic .pd-match { font-size: clamp(30px, 7vh, 52px); }

@media (max-width: 760px) {
  .pd-board { grid-template-columns: repeat(auto-fit, minmax(82px, 1fr)); }
  .pd-pic { min-height: clamp(64px, 15vh, 96px); font-size: clamp(30px, 7vh, 44px); }
  .pd-word { font-size: clamp(34px, 8vh, 54px); }
  .pd-cue { font-size: 13px; } /* layout stays vertical — up arrow still maps */
}

/* ---------- sambung (Titi Ayam) ---------- */
/* fills the stage, no scroll: stream scene with a 3-plank footbridge = the
   word (RTL: first plank rightmost). The asked position is the missing plank;
   an animal walks in and waits at the hole — no positional text labels.
   Right bank: signpost (letter + name + listen). Bottom: plank tiles. */
.sb-game { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
.sb-game .progress-dots { flex: 0 0 auto; display: flex; gap: 8px; justify-content: center; margin: 2px 0 8px; }
.sb-game .progress-dots .dot { width: 14px; height: 14px; border-radius: 50%; background: rgba(255,255,255,.3); }
.sb-game .progress-dots .dot.done { background: var(--green); box-shadow: 0 0 10px var(--green); }
.sb-game .progress-dots .dot.current { background: var(--gold); box-shadow: 0 0 12px var(--gold); }

.sb-stage {
  flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column;
  gap: clamp(8px, 1.6vh, 16px); padding: 0 clamp(8px, 2vw, 32px);
}

/* the stream scene */
.sb-scene { position: relative; flex: 1 1 auto; min-height: 0; border-radius: var(--radius); overflow: hidden; }
.sb-water {
  position: absolute; left: 0; right: 0; bottom: 0; height: 46%;
  background:
    repeating-linear-gradient(90deg, rgba(255,255,255,.10) 0 26px, rgba(255,255,255,0) 26px 78px),
    var(--c-teal);
  animation: sb-ripple 9s linear infinite;
}
@keyframes sb-ripple { from { background-position: 0 0, 0 0; } to { background-position: 156px 0, 0 0; } }
@media (prefers-reduced-motion: reduce) { .sb-water { animation: none; } }
.sb-bank { position: absolute; bottom: 0; height: 58%; width: clamp(80px, 14%, 170px); background: var(--c-green-d); }
.sb-bank-r { right: 0; border-top-left-radius: 70% 55%; }
.sb-bank-l { left: 0; border-top-right-radius: 70% 55%; }
.sb-home { position: absolute; left: clamp(8px, 2.6%, 28px); bottom: 52%; font-size: clamp(30px, 6vh, 52px); line-height: 1; z-index: 3; }

/* signpost on the right bank: whose plank do we need? */
.sb-signpost {
  position: absolute; right: clamp(6px, 1.6%, 20px); top: clamp(6px, 2vh, 16px); z-index: 4;
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  padding: clamp(8px, 1.4vh, 14px) clamp(14px, 1.8vw, 22px);
  border-radius: 20px; background: var(--glass);
  border: var(--bw) solid var(--ink); box-shadow: var(--glass-sh);
}
.sb-glyph { font-size: clamp(44px, 9vh, 80px); line-height: 1.6; color: #d4541f; text-shadow: var(--txt-sh); direction: rtl; }
.sb-name { font-family: "Cause", sans-serif; font-size: clamp(15px, 1.8vw, 20px); font-weight: 700; color: var(--ink); text-shadow: var(--txt-sh); margin: clamp(4px, 1vh, 10px) 0 4px; }
.sb-listen {
  width: clamp(40px, 5.6vh, 52px); height: clamp(40px, 5.6vh, 52px); border-radius: 50%;
  display: grid; place-items: center; cursor: pointer; color: #fff;
  border: var(--bw) solid var(--ink);
  background: var(--c-orange);
  box-shadow: 0 5px 0 var(--c-orange-d), 0 10px 16px rgba(8,20,14,.45);
  transition: transform .1s, filter .15s, box-shadow .1s;
}
.sb-listen:hover { filter: brightness(1.07); }
.sb-listen:active { transform: translateY(3px); box-shadow: 0 2px 0 var(--c-orange-d), 0 5px 8px rgba(8,20,14,.4); }
.sb-listen svg { width: 55%; height: 55%; }

/* the footbridge (RTL: first plank rightmost — jawi reading order) */
.sb-bridge {
  position: absolute; left: 50%; top: 54%; transform: translate(-50%, -50%); z-index: 2;
  direction: rtl; display: flex; align-items: center;
}
.sb-plank {
  position: relative; width: clamp(88px, 13vw, 132px); height: clamp(60px, 10vh, 88px);
  border-radius: 14px; display: grid; place-items: center;
  background:
    repeating-linear-gradient(90deg, rgba(120,70,20,.16) 0 3px, rgba(120,70,20,0) 3px 26px),
    var(--c-orange);
  border: 3px solid var(--ink);
  box-shadow: 0 6px 0 var(--c-orange-d);
  transition: transform .12s, background .15s, border-color .15s;
}
.sb-plank-glyph { font-size: clamp(32px, 5.8vh, 56px); line-height: 1; direction: rtl; color: rgba(80,45,12,.5); }
.sb-plank.gap {
  background: rgba(8,28,38,.3); border: 3px dashed rgba(255,255,255,.65); box-shadow: none;
  animation: sb-bob 1.8s ease-in-out infinite;
}
@keyframes sb-bob {
  0%, 100% { transform: translateY(0); border-color: rgba(255,255,255,.55); }
  50% { transform: translateY(4px); border-color: var(--gold); }
}
.sb-plank.filled {
  background: var(--panel); border: 3px solid var(--gold);
  box-shadow: 0 6px 0 var(--cream-d); animation: sb-thunk .32s cubic-bezier(.3,1.4,.5,1) both;
}
.sb-plank.filled .sb-plank-glyph { color: #d4541f; }
@keyframes sb-thunk { 0% { transform: translateY(-28px) scale(1.05); } 70% { transform: translateY(2px) rotate(1.5deg); } 100% { transform: none; } }
.sb-plank.sink { animation: sb-sink .7s ease-in both; }
@keyframes sb-sink { 0% { transform: rotate(0); } 30% { transform: rotate(7deg); } 100% { transform: translateY(48px) rotate(16deg); opacity: 0; } }
.sb-splash {
  position: absolute; left: 50%; bottom: -14px; width: 12px; height: 12px; border-radius: 50%;
  opacity: 0; pointer-events: none;
  background: radial-gradient(circle, rgba(255,255,255,.95), rgba(116,214,255,0) 70%);
}
.sb-plank.sink .sb-splash { animation: sb-splash .6s ease-out .3s both; }
@keyframes sb-splash { 0% { opacity: 0; transform: translateX(-50%) scale(.4); } 30% { opacity: .95; } 100% { opacity: 0; transform: translateX(-50%) scale(7); } }
.sb-plank.drop-hi { transform: scale(1.08); border-color: var(--gold); background: rgba(255,201,60,.3); }
.sb-rope { width: clamp(16px, 2.6vw, 36px); height: 7px; border-radius: 4px; flex: 0 0 auto;
  background: var(--c-orange-d); }
/* correct plank: lash only the rope(s) on the joining side(s) — the tatweel
   direction made visible (awal lights its left rope, tengah both, akhir right) */
.sb-rope.lit { background: var(--gold); box-shadow: 0 0 12px var(--gold); animation: sb-link .4s ease both; }
@keyframes sb-link { from { transform: scaleX(.2); } to { transform: scaleX(1); } }

/* the walker: waits at the hole, hops across when the word links up */
.sb-walker {
  position: absolute; z-index: 6; left: 0; top: 0; font-size: clamp(28px, 5.6vh, 46px); line-height: 1;
  pointer-events: none;
  transition: left .45s ease, top .45s ease;
  animation: sb-idle 1.6s ease-in-out infinite;
}
.sb-walker.no-anim { transition: none; }
@keyframes sb-idle {
  0%, 100% { transform: translate(-50%, -92%) translateY(0); }
  50% { transform: translate(-50%, -92%) translateY(-5px); }
}
.sb-walker.startle { animation: sb-startle .55s ease; }
@keyframes sb-startle {
  0%, 100% { transform: translate(-50%, -92%); }
  25% { transform: translate(-50%, -92%) rotate(-14deg) translateY(-4px); }
  60% { transform: translate(-50%, -92%) rotate(10deg) translateY(-7px); }
}

/* one-time cue above the first hole; dies after first placement */
.sb-cue {
  position: absolute; bottom: calc(100% + 10px); left: 50%; transform: translateX(-50%);
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  pointer-events: none; z-index: 5; white-space: nowrap; direction: ltr;
  font-family: "Cause", sans-serif; font-weight: 800; font-size: clamp(14px, 1.7vw, 18px);
  color: #fff; text-shadow: var(--txt-sh);
  animation: sb-cue 1.4s ease-in-out infinite;
}
.sb-cue-arrow { width: clamp(13px, 1.7vw, 18px); height: clamp(13px, 1.7vw, 18px);
  border-left: 5px solid #fff; border-bottom: 5px solid #fff; border-radius: 3px;
  transform: rotate(-45deg); }
@keyframes sb-cue { 0%, 100% { transform: translate(-50%, 0); } 50% { transform: translate(-50%, 8px); } }

/* plank tiles (kept .opt so quiz.js disable/hint machinery works) */
.sb-tiles { flex: 0 0 auto; direction: rtl; display: flex; flex-wrap: wrap; justify-content: center; gap: clamp(10px, 1.6vw, 16px); padding-bottom: 4px; }
.sb-tile { direction: rtl; touch-action: none; user-select: none; -webkit-user-select: none;
  border: 3px solid rgba(154,95,40,.75); }
.sb-tile.drag-src { opacity: .35; }
.sb-ghost {
  position: fixed; z-index: 120; transform: translate(-50%, -50%); pointer-events: none;
  display: grid; place-items: center; font-size: 38px; direction: rtl; color: var(--ink); border-radius: 18px;
  background: var(--panel); border: 3px solid var(--gold); box-shadow: 0 8px 0 var(--cream-d), 0 12px 18px rgba(28,29,38,.2);
}

@media (max-width: 760px) {
  .sb-signpost { padding: 6px 12px; }
  .sb-glyph { font-size: clamp(36px, 9vw, 52px); }
  .sb-plank { width: clamp(64px, 15vw, 88px); height: clamp(46px, 8vh, 64px); }
  .sb-plank-glyph { font-size: clamp(24px, 6.5vw, 36px); }
  .sb-rope { width: 14px; height: 6px; }
  .sb-home { font-size: clamp(24px, 5vw, 34px); }
  .sb-walker { font-size: clamp(22px, 5vw, 34px); }
}

/* ---------- eja ---------- */
/* repair-the-word: meaning anchor (picture + listen) → word strip of letter
   cards with one breathing gap → letter tray below. fills the stage, no scroll */
.eja-game { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
.eja-game .progress-dots { flex: 0 0 auto; display: flex; gap: 8px; justify-content: center; margin: 2px 0 8px; }
.eja-game .progress-dots .dot { width: 14px; height: 14px; border-radius: 50%; background: rgba(255,255,255,.3); }
.eja-game .progress-dots .dot.done { background: var(--green); box-shadow: 0 0 10px var(--green); }
.eja-game .progress-dots .dot.current { background: var(--gold); box-shadow: 0 0 12px var(--gold); }

.eja-stage {
  position: relative; flex: 1 1 auto; min-height: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: space-evenly;
  gap: clamp(6px, 1.4vh, 16px); padding: 0 clamp(8px, 2vw, 32px);
}

/* drag-direction cue: tray (bottom) → gap (above), so it points + drifts up.
   shown until the first letter is placed, then hidden for the rest of the game */
.eja-cue {
  position: absolute; left: 50%; top: 67%; transform: translate(-50%, -50%);
  display: flex; align-items: center; gap: 8px; pointer-events: none; z-index: 5;
  font-family: "Cause", sans-serif; font-weight: 800; font-size: clamp(15px, 1.8vw, 19px);
  color: #fff; text-shadow: var(--txt-sh); white-space: nowrap;
  animation: eja-cue 1.4s ease-in-out infinite;
}
.eja-cue-arrow { width: clamp(15px, 1.9vw, 22px); height: clamp(15px, 1.9vw, 22px); flex: 0 0 auto;
  border-left: 5px solid #fff; border-top: 5px solid #fff; border-radius: 3px;
  transform: rotate(45deg); }
@keyframes eja-cue { 0%, 100% { transform: translate(-50%, -50%); } 50% { transform: translate(-50%, calc(-50% - 16px)); } }

/* meaning anchor: what the word *means* (picture) + how it sounds (listen).
   one low-key inline row — the wordcard below is the hero, not this */
.eja-meaning { flex: 0 0 auto; display: flex; align-items: center; justify-content: center; gap: clamp(10px, 2vw, 20px); }
.eja-pic { font-size: clamp(36px, 7vh, 60px); line-height: 1; }
.eja-meaning-txt { display: flex; flex-direction: row; align-items: center; gap: clamp(8px, 1.6vw, 14px); }
.eja-listen {
  display: inline-flex; align-items: center; gap: 8px; cursor: pointer;
  font-family: "Cause", sans-serif; font-weight: 700; font-size: clamp(14px, 2vh, 18px); color: #fff;
  padding: clamp(5px, 1.1vh, 8px) clamp(14px, 2.2vw, 20px); border-radius: 999px; border: 2px solid var(--ink);
  background: var(--c-orange); text-shadow: none;
  box-shadow: 0 4px 0 var(--c-orange-d), 0 8px 14px rgba(8,20,14,.4);
  transition: transform .1s, filter .15s, box-shadow .1s;
}
.eja-listen:hover { filter: brightness(1.07); }
.eja-listen:active { transform: translateY(4px); box-shadow: 0 2px 0 var(--c-orange-d), 0 5px 10px rgba(8,20,14,.4); }
.eja-spk { width: 1.25em; height: 1.25em; flex: 0 0 auto; color: #fff; }
.eja-rumi { font-size: clamp(16px, 2.4vh, 24px); font-weight: 800; color: rgba(255,255,255,.95); text-shadow: var(--txt-sh); min-height: 1.1em; }

/* the word strip, grouped in a frosted card so the scene behind doesn't
   bleed through the empty gap */
.eja-wordcard {
  position: relative; flex: 0 1 auto; max-width: 100%;
  padding: clamp(16px, 3vh, 30px) clamp(22px, 4vw, 48px);
  border-radius: var(--radius); background: var(--glass);
  border: var(--bw) solid var(--ink); box-shadow: var(--glass-sh);
}
/* letter cards read in script order — .jawi (rtl courses) flips right→left */
.eja-word { display: flex; align-items: center; justify-content: center; flex-wrap: wrap; gap: clamp(6px, 1vw, 12px); }
.eja-word.jawi { direction: rtl; }
.eja-letter {
  display: grid; place-items: center; min-width: clamp(54px, 9vh, 92px); height: clamp(64px, 11vh, 104px);
  padding: 0 clamp(6px, 1vw, 14px); font-size: clamp(38px, 7vh, 66px); line-height: 1; color: #d4541f;
  background: var(--surface); border-radius: 18px;
  border: 3px solid var(--ink); box-shadow: 0 6px 0 var(--cream-d);
}
.eja-gap {
  display: grid; place-items: center; width: clamp(54px, 9vh, 92px); height: clamp(64px, 11vh, 104px);
  border-radius: 18px; background: rgba(20,40,30,.28); border: 3px dashed rgba(255,255,255,.6);
  transition: transform .12s, background .15s, border-color .15s;
}
.eja-gap:not(.filled) { animation: eja-breathe 1.8s ease-in-out infinite; }
@keyframes eja-breathe { 0%, 100% { border-color: rgba(255,255,255,.55); } 50% { border-color: var(--gold); } }
.eja-gap-glyph { font-size: clamp(38px, 7vh, 66px); line-height: 1; color: #fff; }
.eja-gap.filled { background: var(--panel); border-style: solid; border-color: var(--gold);
  box-shadow: 0 6px 0 var(--cream-d); animation: eja-pop .28s cubic-bezier(.2,.9,.3,1.4) both; }
.eja-gap.filled .eja-gap-glyph { color: #d4541f; }
.eja-gap.drop-hi { transform: scale(1.07); border-color: var(--gold); background: rgba(255,201,60,.28); }
@keyframes eja-pop { 0% { transform: scale(.6); } 60% { transform: scale(1.12); } 100% { transform: scale(1); } }
.eja-word-lbl { position: absolute; bottom: -24px; left: 0; right: 0; text-align: center;
  font-size: 13px; font-weight: 800; color: rgba(255,255,255,.85); }
/* success reveal: letter cards swap to the connected (sambung) word */
.eja-built {
  font-size: clamp(52px, 11vh, 96px); line-height: 1.3; color: #d4541f;
  text-align: center; background: var(--surface); border-radius: 26px;
  padding: 6px 30px; border: 4px solid var(--ink); box-shadow: 0 8px 0 var(--cream-d), 0 12px 18px rgba(28,29,38,.2);
}

/* letter tray (drag sources) */
.eja-tray { flex: 0 0 auto; display: flex; flex-direction: column; align-items: center; gap: 10px; }
.eja-tray-lbl { font-size: clamp(14px, 1.8vw, 17px); font-weight: 800; color: #fff; text-shadow: var(--txt-sh); }
.eja-tiles { display: flex; flex-wrap: wrap; justify-content: center; gap: clamp(10px, 1.6vw, 16px); }
.eja-tile { touch-action: none; user-select: none; -webkit-user-select: none; }
.eja-tile.picked { outline: 3px solid var(--gold); outline-offset: 2px; }
.eja-tile.drag-src { opacity: .35; }
.eja-ghost {
  position: fixed; z-index: 120; transform: translate(-50%, -50%); pointer-events: none;
  display: grid; place-items: center; font-size: 38px; color: var(--ink); border-radius: 18px;
  background: var(--panel); border: 3px solid var(--gold); box-shadow: 0 8px 0 var(--cream-d), 0 12px 18px rgba(28,29,38,.2);
}


@media (max-width: 760px), (max-height: 480px) {
  .eja-pic { font-size: clamp(38px, 8vh, 56px); }
  .eja-letter, .eja-gap { height: clamp(52px, 12vh, 76px); min-width: clamp(44px, 9vh, 68px); }
  .eja-gap { width: clamp(44px, 9vh, 68px); }
  .eja-letter, .eja-gap-glyph { font-size: clamp(30px, 7vh, 50px); }
  .eja-built { font-size: clamp(38px, 10vh, 60px); }
  .eja-word-lbl { display: none; } /* too tight under the card when stacked */
}

/* ---------- cari ---------- */
/* hidden-object search: the desa scene IS the play area (full-bleed, no card).
   Target card floats top-left, collection tray bottom-center; glyph plaques
   bob in the scene with depth scaling (lower = nearer = bigger). */
.cr-game { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
.cr-game .progress-dots { flex: 0 0 auto; display: flex; gap: 8px; justify-content: center; margin: 2px 0 10px; }
.cr-game .progress-dots .dot { width: 14px; height: 14px; border-radius: 50%; background: rgba(255,255,255,.3); }
.cr-game .progress-dots .dot.done { background: var(--green); box-shadow: 0 0 10px var(--green); }
.cr-game .progress-dots .dot.current { background: var(--gold); box-shadow: 0 0 12px var(--gold); }

.cr-stage { flex: 1 1 auto; min-height: 0; display: flex; }
.cr-scene {
  position: relative; flex: 1 1 auto; min-width: 0; border-radius: var(--radius); overflow: hidden;
  background: var(--cr-scene-img) center/cover no-repeat, linear-gradient(160deg, #7ec850, #2e8b57);
  border: var(--bw) solid var(--ink); box-shadow: var(--glass-sh);
}
/* time-of-day veil: each round visits the desa at a different hour */
.cr-veil { position: absolute; inset: 0; pointer-events: none; transition: background 1s ease; }
.cr-scene.tint-1 .cr-veil { background: linear-gradient(180deg, rgba(255,201,90,.20), rgba(255,122,61,.12)); }
.cr-scene.tint-2 .cr-veil { background: linear-gradient(180deg, rgba(139,92,246,.22), rgba(255,77,141,.12)); }

/* glyph plaques: little wooden signs hidden in the scene */
.cr-field { position: absolute; inset: 0; }
.cr-glyph {
  position: absolute; left: var(--x); top: var(--y); cursor: pointer;
  font-family: inherit; font-size: clamp(26px, 4.8vh, 46px); line-height: 1;
  min-width: 64px; min-height: 64px; padding: clamp(8px, 1.4vh, 14px) clamp(12px, 1.8vh, 18px);
  color: #4a3322; background: var(--cream);
  border: 3px solid var(--ink); border-radius: 16px;
  box-shadow: 0 5px 0 var(--cream-d), 0 12px 16px rgba(8,20,14,.35);
  transform: translate(-50%, -50%) rotate(var(--rot)) scale(var(--scale));
  animation: cr-bob var(--bob-dur) ease-in-out var(--bob-delay) infinite;
  touch-action: manipulation; user-select: none; -webkit-user-select: none;
}
/* this block sits after the global .jawi rule, so restate Amiri for RTL glyphs */
.cr-glyph.jawi { font-family: "Amiri", serif; }
@keyframes cr-bob {
  0%, 100% { transform: translate(-50%, -50%) rotate(var(--rot)) scale(var(--scale)); }
  50% { transform: translate(-50%, calc(-50% - 7px)) rotate(var(--rot)) scale(var(--scale)); }
}
.cr-glyph.wrong { border-color: #ff5252; background: #ffe9e9; animation: cr-shake .4s ease; }
@keyframes cr-shake {
  0%, 100% { transform: translate(-50%, -50%) rotate(var(--rot)) scale(var(--scale)); }
  25% { transform: translate(-50%, -50%) rotate(calc(var(--rot) - 8deg)) scale(var(--scale)); }
  75% { transform: translate(-50%, -50%) rotate(calc(var(--rot) + 8deg)) scale(var(--scale)); }
}
/* quiz.js hint pulses .opt.hint with a bare scale() — that would wipe the
   plaque's centering translate, so cari overrides with a full transform */
.cr-glyph.hint { animation: cr-hint 1s ease-in-out infinite; border-color: var(--gold); }
@keyframes cr-hint {
  0%, 100% { transform: translate(-50%, -50%) rotate(var(--rot)) scale(var(--scale)); box-shadow: 0 5px 0 rgba(120,80,30,.5), 0 12px 16px rgba(8,20,14,.35); }
  50% { transform: translate(-50%, -50%) rotate(var(--rot)) scale(calc(var(--scale) * 1.12)); box-shadow: 0 0 26px var(--gold); }
}
/* idle glint: a shine sweeps one remaining target so a stuck child looks there */
.cr-glyph.glint { box-shadow: 0 5px 0 rgba(120,80,30,.5), 0 0 22px var(--gold); }
.cr-glyph.glint::after {
  content: ""; position: absolute; inset: 0; border-radius: inherit; pointer-events: none;
  background: linear-gradient(100deg, transparent 35%, rgba(255,255,255,.85) 50%, transparent 65%);
  background-size: 220% 100%; animation: cr-glint 1.5s ease;
}
@keyframes cr-glint { from { background-position: 150% 0; } to { background-position: -50% 0; } }
.cr-glyph.found { opacity: 0; transition: opacity .2s; pointer-events: none; }

/* target card: what we're hunting, always in the corner of the eye */
.cr-target-card {
  position: absolute; top: clamp(10px, 2vh, 18px); left: clamp(10px, 1.6vw, 18px); z-index: 5;
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  padding: clamp(10px, 1.8vh, 16px) clamp(14px, 2vw, 22px);
  border-radius: 20px; background: var(--glass);
  border: var(--bw) solid var(--ink); box-shadow: var(--glass-sh);
}
.cr-target-lbl { font-family: "Cause", sans-serif; font-weight: 700; font-size: clamp(13px, 1.7vw, 17px); color: #fff; text-shadow: var(--txt-sh); }
.cr-target-glyph {
  min-width: clamp(64px, 10vh, 92px); padding: 4px 14px; text-align: center;
  font-size: clamp(38px, 7.5vh, 64px); line-height: 1.25; color: #d4541f;
  background: var(--surface); border-radius: 18px;
  border: 3px solid var(--ink); box-shadow: 0 6px 0 var(--cream-d);
}
.cr-listen {
  position: relative; width: clamp(48px, 7vh, 58px); height: clamp(48px, 7vh, 58px); border-radius: 50%;
  display: grid; place-items: center; cursor: pointer; color: #fff;
  border: var(--bw) solid var(--ink);
  background: var(--c-orange);
  box-shadow: 0 5px 0 var(--c-orange-d), 0 10px 16px rgba(8,20,14,.45);
  transition: transform .1s, filter .15s, box-shadow .1s;
}
/* invisible halo keeps the tap target >= 64px even when the button bottoms out */
.cr-listen::before { content: ""; position: absolute; inset: -8px; border-radius: 50%; }
.cr-listen:hover { filter: brightness(1.07); }
.cr-listen:active { transform: translateY(3px); box-shadow: 0 2px 0 var(--c-orange-d), 0 5px 8px rgba(8,20,14,.4); }
.cr-spk { width: 55%; height: 55%; }

/* one-time cue: drifts from the target card toward the scene, dies on first find */
.cr-cue {
  position: absolute; top: clamp(110px, 24vh, 200px); left: clamp(24px, 4vw, 60px); z-index: 5;
  display: flex; align-items: center; gap: 8px; pointer-events: none; white-space: nowrap;
  font-family: "Cause", sans-serif; font-weight: 800; font-size: clamp(14px, 1.8vw, 19px);
  color: #fff; text-shadow: var(--txt-sh);
  animation: cr-cue 1.4s ease-in-out infinite;
}
.cr-cue-arrow { width: clamp(14px, 1.8vw, 20px); height: clamp(14px, 1.8vw, 20px); flex: 0 0 auto;
  border-right: 5px solid #fff; border-bottom: 5px solid #fff; border-radius: 3px;
  transform: rotate(-45deg); }
@keyframes cr-cue { 0%, 100% { transform: translate(0, 0); } 50% { transform: translate(16px, 8px); } }

/* collection tray: one slot per copy — the slots themselves say "find N" */
.cr-tray {
  position: absolute; bottom: clamp(10px, 2vh, 18px); left: 50%; transform: translateX(-50%); z-index: 5;
  display: flex; gap: clamp(8px, 1.2vw, 14px); padding: clamp(8px, 1.4vh, 12px) clamp(12px, 1.8vw, 20px);
  border-radius: 999px; background: var(--glass);
  border: var(--bw) solid var(--ink); box-shadow: var(--glass-sh);
}
.cr-slot {
  width: clamp(42px, 7vh, 58px); height: clamp(42px, 7vh, 58px); border-radius: 14px;
  display: grid; place-items: center; font-size: clamp(20px, 3.6vh, 32px); line-height: 1;
  color: rgba(255,255,255,.45); background: rgba(20,40,30,.30);
  border: 3px dashed rgba(255,255,255,.55);
}
.cr-slot:not(.filled) { animation: cr-breathe 1.8s ease-in-out infinite; }
@keyframes cr-breathe { 0%, 100% { border-color: rgba(255,255,255,.55); } 50% { border-color: var(--gold); } }
.cr-slot.filled { border-style: solid; border-color: var(--gold); background: var(--panel); color: #d4541f; animation: cr-pop .28s cubic-bezier(.2,.9,.3,1.4) both; }
/* visually-hidden live counter: rendered text so role="status" actually announces */
.cr-count { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
@keyframes cr-pop { 0% { transform: scale(.6); } 60% { transform: scale(1.12); } 100% { transform: scale(1); } }

/* found glyph flies from the scene into its tray slot */
.cr-fly {
  position: fixed; z-index: 130; pointer-events: none; display: grid; place-items: center;
  font-size: clamp(26px, 4.8vh, 46px); line-height: 1; color: #d4541f;
  background: var(--surface); border: 3px solid var(--gold);
  border-radius: 16px; padding: 8px 12px; box-shadow: 0 8px 0 var(--cream-d), 0 12px 18px rgba(28,29,38,.2);
  transform: translate(-50%, -50%);
  transition: transform .55s cubic-bezier(.3,.7,.3,1.1), opacity .55s ease;
}

@media (max-width: 760px), (max-height: 480px) {
  .cr-target-card { flex-direction: row; gap: 10px; padding: 8px 12px; }
  .cr-target-lbl { display: none; } /* card shrinks to glyph + speaker; label adds nothing */
  .cr-target-glyph { font-size: clamp(30px, 9vw, 44px); min-width: 56px; }
  .cr-glyph { font-size: clamp(22px, 6.4vw, 34px); min-width: 56px; min-height: 56px; }
  .cr-slot { width: clamp(34px, 9vw, 44px); height: clamp(34px, 9vw, 44px); font-size: clamp(16px, 4.6vw, 24px); }
}

/* ---------- cerita ---------- */
.story-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); gap: 18px; }
.story-card { background: var(--panel); border-radius: var(--radius); box-shadow: 0 8px 0 var(--cream-d), 0 12px 18px rgba(28,29,38,.2); overflow: hidden; cursor: pointer; transition: transform .1s; }
.story-card:hover { transform: translateY(-6px); }
.story-card .cover { position: relative; height: 140px; display: grid; place-items: center; font-size: 64px; background: var(--c-yellow); }
.story-card .cover img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.story-card .t { padding: 14px 16px; font-weight: 800; color: var(--ink); }
/* short viewports: denser story shelf */
@media (max-width: 760px), (max-height: 900px) {
  .story-grid { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 12px; }
  .story-card .cover { height: 96px; font-size: 44px; }
  .story-card .t { padding: 8px 12px; font-size: 14px; }
}
.reader { max-width: 700px; margin: 0 auto; text-align: center;
  display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; }
/* Picture-book layout: the illustration + controls stay FIXED at the top while
   the story text scrolls inside its own panel (the active line auto-centres
   during playback, see cerita.js). So the scene never scrolls out of view while
   the child reads a sentence lower down — the original "scroll to read → lose the
   picture" problem. The phone-landscape grid below keeps its own sticky spread. */
.reader > h1, .reader .scene-side, .reader .praise, .reader .story-recall { flex: 0 0 auto; }
.reader .scene-side { margin-bottom: 18px; }
.reader .scene-wrap { margin-bottom: 14px; border-radius: var(--radius); overflow: hidden; box-shadow: 0 8px 0 var(--cream-d), 0 12px 18px rgba(28,29,38,.2); }
.reader #scene-img { display: block; width: 100%; max-height: min(44vh, 340px); object-fit: cover; transition: opacity .16s; }
/* the text panel is the only scroller; min-height:0 lets it shrink inside the
   flex column so overflow-y actually engages instead of pushing the page taller */
.reader .lines { background: var(--panel); border-radius: var(--radius); box-shadow: 0 8px 0 var(--cream-d), 0 12px 18px rgba(28,29,38,.2); padding: 36px 26px;
  flex: 1 1 auto; min-height: 0; overflow-y: auto; -webkit-overflow-scrolling: touch; }
.reader .line { font-size: 44px; line-height: 1.9; margin: 0 0 10px; direction: rtl; color: #d4541f; }
.reader .word { padding: 2px 8px; border-radius: 10px; transition: background .15s; cursor: pointer; }
.reader .word:hover { background: rgba(255,201,60,.35); }
.reader .word.active { background: var(--gold); color: var(--ink); box-shadow: 0 0 16px rgba(255,201,60,.7); }
.reader .rumi { font-size: 15px; color: var(--muted); direction: ltr; margin-top: 4px; font-weight: 600; }
/* icon-only buttons — compact plates, one row everywhere */
.reader-controls { display: flex; gap: 10px; justify-content: center; flex-wrap: nowrap; }
/* compact icon-only plates with BIG icons: drop the 130px min-width + the lg
   padding so the plate hugs the icon, then size the icon via font-size (the
   play/stop SVGs + the 🏠 are 1em). Square-ish, comfortably > 44px touch target. */
.reader-controls .btn { white-space: nowrap; min-width: 0; flex-shrink: 0;
  font-size: 30px; padding: 8px 16px; }
/* phone landscape: picture-book spread — scene + controls left (sticky), lines right */
@media (max-height: 480px) {
  /* keep landscape's proven page-scroll sticky spread: opt out of the .wrap.fill
     clipping the tall/portrait layouts use (the image is sticky here, not fixed). */
  .wrap.fill { height: auto; min-height: 100%; overflow: visible; display: block; }
  .reader { display: grid; max-width: none; column-gap: 14px; align-items: start;
    flex: 0 1 auto;
    grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
    grid-template-areas: "h h" "img text" "praise praise"; }
  .reader > h1 { grid-area: h; font-size: 24px; margin: 0 0 8px; }
  .reader .scene-side { grid-area: img; margin: 0; position: sticky; top: 0; }
  .reader .scene-wrap { margin: 0 0 10px; }
  /* cap image so image + controls fit the viewport (sticky column must not overflow) */
  .reader #scene-img { aspect-ratio: 4 / 3; max-height: calc(100dvh - 195px); }
  .reader .lines { grid-area: text; padding: 16px 14px; }
  .reader .line { font-size: 32px; line-height: 1.7; }
  .reader-controls { margin-top: 0; }
  .reader .praise { grid-area: praise; }
}

/* ---------- menulis ---------- */
/* menulis fills the stage height — no scroll. square canvas sized by available height.
   overflow:hidden so decorative .btn::after shine boxes can't create phantom scroll */
.wrap.fill { height: 100%; display: flex; flex-direction: column; overflow: hidden; }
.trace-wrap { max-width: 760px; width: 100%; margin: 0 auto; text-align: center;
  flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; gap: 6px; }
.trace-head { flex: 0 0 auto; }
.trace-hint { flex: 0 0 auto; margin: 0; }
.trace-stage { flex: 1 1 auto; min-height: 0; display: flex; align-items: center; justify-content: center; }
.trace-square { position: relative; height: 100%; aspect-ratio: 1; max-width: 100%; margin: 0 auto; }
#guide, #pad { position: absolute; inset: 0; width: 100%; height: 100%; border-radius: var(--radius); }
#guide { background: var(--surface); box-shadow: 0 8px 0 var(--cream-d), 0 12px 18px rgba(28,29,38,.2); border: 4px solid var(--ink); }
#pad { z-index: 2; touch-action: none; cursor: crosshair; }
.trace-controls { flex: 0 0 auto; display: flex; gap: 12px; justify-content: center; margin-top: 8px; }
.praise { flex: 0 0 auto; min-height: 28px; font-size: clamp(16px, 2.2vw, 22px); font-weight: 800;
  color: var(--gold); margin: 0; text-shadow: var(--txt-sh); }
/* phone landscape: stacked chrome eats all the height and the square collapses —
   move nav/controls into a left column so the canvas gets the full stage height */
@media (max-height: 480px) {
  .wrap.fill { padding-top: 8px; }
  .trace-wrap { display: grid; max-width: none;
    grid-template-columns: 1fr auto; column-gap: 16px; align-items: center;
    grid-template-rows: auto auto auto 1fr auto;
    grid-template-areas: "back stage" "head stage" "hint stage" "praise stage" "controls stage"; }
  .trace-wrap > .back { grid-area: back; justify-self: start; }
  .trace-head { grid-area: head; }
  .trace-hint { grid-area: hint; }
  .trace-wrap .praise { grid-area: praise; min-height: 0; }
  .trace-stage { grid-area: stage; height: 100%; min-height: 0; }
  .trace-controls { grid-area: controls; margin-top: 0; }
}

/* ---------- kembar ---------- */
/* fills the stage, no scroll: head row + "ampaian" clothesline rail + woven
   felt holding a two-row card board. Matched pairs fly up to wooden pegs on
   the rope, slotted in letter order (row reversed for rtl courses) — the rail
   is progress bar, recap strip and ordering lesson at once. Cards are 3:4,
   board sized by container queries so 8/12/16 cards fit both axes. */
.km-game { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; gap: clamp(4px, 1vh, 10px); }
.km-head { flex: 0 0 auto; display: flex; align-items: center; justify-content: center; gap: 10px; }
.km-ask { font-size: clamp(14px, 2vh, 18px); font-weight: 800; color: #fff; text-shadow: var(--txt-sh); }
.km-count { font-family: "Cause", sans-serif; font-weight: 700; font-size: clamp(13px, 1.8vh, 16px);
  color: #fff; text-shadow: var(--txt-sh); background: rgba(0,0,0,.28);
  border: 2px solid rgba(255,255,255,.5); border-radius: 999px; padding: 2px 14px; }
.km-count.tick { animation: km-tick .3s ease; }
@keyframes km-tick { 50% { transform: scale(1.18); } }

/* ampaian: sagging rope + one wooden peg per pair, faded until its pair lands */
.km-rail { flex: 0 0 auto; position: relative; height: clamp(56px, 11vh, 88px); }
.km-rope { position: absolute; left: 3%; right: 3%; top: clamp(8px, 1.6vh, 12px); height: 4px;
  background: var(--c-orange-d); border-radius: 0 0 50% 50% / 0 0 8px 8px;
  box-shadow: 0 2px 0 rgba(0,0,0,.28); }
.km-pegs { position: absolute; inset: 0; display: flex; justify-content: center; gap: clamp(8px, 1.8vw, 20px); }
.km-rtl .km-pegs { flex-direction: row-reverse; } /* jawi fills right → left */
.km-peg { position: relative; width: clamp(40px, 6.4vh, 60px); display: flex; flex-direction: column;
  align-items: center; transform-origin: top center; }
.km-pin { width: 9px; height: clamp(15px, 2.8vh, 22px); margin-top: clamp(2px, .6vh, 5px); border-radius: 4px;
  background: var(--c-orange); opacity: .55; }
.km-peg.filled .km-pin { opacity: 1; }
.km-peg.filled { animation: km-swing .55s ease-out; }
@keyframes km-swing { 0% { transform: rotate(6deg); } 40% { transform: rotate(-4deg); }
  75% { transform: rotate(2deg); } 100% { transform: rotate(0); } }
.km-pair { display: flex; margin-top: -2px; }
.km-mini { width: clamp(22px, 3.8vh, 36px); aspect-ratio: 3 / 4; display: flex; align-items: center;
  justify-content: center; overflow: hidden; font-size: clamp(12px, 2.2vh, 20px); line-height: 1;
  color: #d4541f; font-weight: 700; background: var(--surface);
  border: 2px solid var(--ink); border-radius: 6px; box-shadow: 0 3px 0 var(--cream-d);
  transform: rotate(-6deg); animation: km-mini-pop .3s cubic-bezier(.2,.9,.3,1.4) both; }
.km-mini + .km-mini { margin-left: -8px; transform: rotate(6deg); }
@keyframes km-mini-pop { 0% { scale: .3; } 60% { scale: 1.15; } 100% { scale: 1; } }
/* finish: silent wave across the hung pairs, staggered per peg from JS */
.km-game.over .km-peg.filled { animation: km-wave .5s ease both; animation-delay: var(--wd, 0ms); }
@keyframes km-wave { 0% { transform: translateY(0); } 40% { transform: translateY(-5px) rotate(3deg); }
  100% { transform: translateY(0); } }

/* woven felt: the play surface; cards are opaque so no frosted card needed —
   a subtle kelarai weave over the scene marks the board's home */
.km-felt { position: relative; flex: 1 1 auto; min-height: 0; container-type: size;
  display: flex; align-items: center; justify-content: center;
  border-radius: var(--radius); border: 2px solid rgba(120,82,30,.35);
  background:
    repeating-linear-gradient(45deg, rgba(214,178,94,.10) 0 12px, transparent 12px 24px),
    repeating-linear-gradient(-45deg, rgba(120,82,30,.10) 0 12px, transparent 12px 24px),
    rgba(60,40,18,.18); }

/* two-row board: width fits BOTH axes by construction, so it can never
   scroll. --c set per board from JS; --ar = board width/height for 3:4 cards */
.km-board { --c: 4; --ar: calc(var(--c) * 3 / 8);
  height: 94%; max-width: 97%; aspect-ratio: var(--ar);
  display: grid; grid-template-columns: repeat(var(--c), 1fr); grid-template-rows: 1fr 1fr;
  gap: clamp(6px, 1.6vh, 16px); }
@supports (width: 1cqw) {
  .km-board { height: auto; max-width: none; width: min(97cqw, calc(94cqh * var(--ar))); }
}

.km-card { position: relative; border: 0; padding: 0; background: transparent; cursor: pointer;
  container-type: size; perspective: 700px; border-radius: clamp(10px, 2vh, 16px);
  -webkit-user-select: none; user-select: none;
  box-shadow: 0 5px 0 rgba(90,55,20,.85), 0 10px 14px rgba(8,20,14,.35);
  transition: transform .1s, box-shadow .1s; }
.km-card:not(.matched):not(.open):active { transform: translateY(3px);
  box-shadow: 0 1px 0 rgba(90,55,20,.85), 0 4px 8px rgba(8,20,14,.3); }
.km-card.gone { visibility: hidden; pointer-events: none; } /* keeps its cell — board never reflows */

/* 3D flip: the inner owns ONLY the rotation; rim shadow stays on the button */
.km-inner { position: absolute; inset: 0; transform-style: preserve-3d;
  transition: transform .45s cubic-bezier(.4,1.4,.6,1); }
.km-card.open .km-inner { transform: rotateY(180deg); }
.km-rtl .km-card.open .km-inner { transform: rotateY(-180deg); } /* jawi cards open the jawi way */
.km-card.peek .km-inner { transform: rotateY(32deg); }
.km-rtl .km-card.peek .km-inner { transform: rotateY(-32deg); }
.km-f { position: absolute; inset: 0; border-radius: inherit; display: flex; align-items: center;
  justify-content: center; -webkit-backface-visibility: hidden; backface-visibility: hidden; }
.km-back {
  background:
    repeating-linear-gradient(45deg, rgba(255,255,255,.08) 0 6px, transparent 6px 12px),
    repeating-linear-gradient(-45deg, rgba(0,0,0,.10) 0 6px, transparent 6px 12px),
    var(--c-orange);
  border: 3px solid rgba(255,231,178,.5); }
.km-back::after { content: "❖"; color: rgba(255,231,178,.55); font-size: clamp(18px, 4vh, 34px); }
.km-card:not(.open):not(.gone) .km-back::after { animation: km-breathe 3.2s ease-in-out infinite; }
@keyframes km-breathe { 0%, 100% { opacity: .45; scale: 1; } 50% { opacity: .8; scale: 1.1; } }
.km-front { transform: rotateY(180deg); background: var(--surface);
  border: var(--bw) solid var(--ink); color: #d4541f; font-weight: 700;
  font-size: clamp(20px, 7vh, 60px); line-height: 1.1; padding: 4%;
  overflow-wrap: anywhere; text-align: center; }
.km-front.multi { font-size: clamp(12px, 3vh, 24px); } /* matematik count faces */
@supports (width: 1cqw) { /* size glyphs to the card itself, not the viewport */
  .km-back::after { font-size: 38cqh; }
  .km-front { font-size: 52cqh; }
  .km-front.multi { font-size: 20cqh; }
}

/* deal ripple: the delay lives inside .deal, so stripping the class can
   never leave a stale animation-delay behind */
.km-card.deal { animation: km-deal .42s cubic-bezier(.2,.8,.3,1) both; animation-delay: var(--d, 0ms); }
@keyframes km-deal { 0% { opacity: 0; transform: scale(.3) rotate(-6deg); }
  70% { opacity: 1; transform: scale(1.06) rotate(2deg); } 100% { opacity: 1; transform: none; } }

/* match: green ring while the pair is admired, then it flies to its peg */
.km-card.hit .km-front { box-shadow: inset 0 0 0 3px var(--green), 0 0 18px var(--green); }
/* mismatch: amber head-shake on the OUTER button so it composes with the
   inner flip — a memory miss is information, not failure (no red) */
.km-card.no { animation: km-no .5s ease; }
@keyframes km-no { 20%, 60% { transform: translateX(-5px) rotate(-2deg); }
  40%, 80% { transform: translateX(5px) rotate(2deg); } }
.km-card.no .km-front { box-shadow: inset 0 0 0 3px rgba(255,170,60,.85); }

/* matched pair flying to its peg (fixed-position clones) */
.km-fly { position: fixed; z-index: 130; pointer-events: none; display: flex; align-items: center;
  justify-content: center; line-height: 1.1; color: #d4541f; font-weight: 700;
  overflow: hidden; overflow-wrap: anywhere; text-align: center;
  background: var(--surface); border: 2px solid var(--ink);
  border-radius: 10px; box-shadow: 0 6px 0 var(--cream-d);
  transform-origin: top left; transition: transform .6s cubic-bezier(.3,.8,.3,1); }

/* one-time ghost-fingertip cue (first board ever): glides pair → twin */
.km-cue { position: absolute; z-index: 20; pointer-events: none;
  font-size: clamp(28px, 6vh, 44px);
  transition: left .5s ease, top .5s ease; animation: km-cue-bob 1.1s ease-in-out infinite; }
.km-cue[hidden] { display: none; }
@keyframes km-cue-bob { 0%, 100% { transform: translate(-50%, -50%); }
  50% { transform: translate(-50%, -50%) translateY(8px) scale(.92); } }

/* done: glass overlay over the felt only — the rope of finished pairs stays
   visible above it through the score moment */
.km-done { position: absolute; inset: 0; z-index: 15; display: flex; flex-direction: column;
  align-items: center; justify-content: center; border-radius: var(--radius);
  background: rgba(10,24,16,.5);
  animation: km-done-pop .35s cubic-bezier(.2,.9,.3,1.3) both; }
.km-done[hidden] { display: none; }
@keyframes km-done-pop { from { opacity: 0; scale: .9; } }

/* landscape phones are wide but SHORT — compaction must key on height too */
@media (max-width: 760px), (max-height: 480px) {
  .km-wrap { padding-top: 8px; padding-bottom: 12px; }
  .km-rail { height: clamp(36px, 11vh, 60px); }
  .km-pin { height: clamp(10px, 2.8vh, 22px); }
  .km-peg { width: clamp(30px, 6.4vh, 60px); }
  .km-mini { width: clamp(16px, 3.8vh, 36px); font-size: clamp(9px, 2.2vh, 20px); }
  .km-pegs { gap: 8px; }
  .km-game { gap: 4px; }
  .km-ask { display: none; } /* head row shrinks to the counter alone */
}

/* ---------- dashboard ---------- */
.dash-child { background: var(--surface); border: 4px solid var(--ink); border-radius: var(--radius); box-shadow: var(--shadow-card); padding: 24px; margin-bottom: 18px; color: var(--ink); }
.dash-head { display: flex; align-items: center; gap: 14px; margin-bottom: 16px; }
.dash-head .avatar { width: 64px; height: 64px; border-radius: 18px; font-size: 30px; background: var(--cream); box-shadow:none; border: var(--bw) solid var(--ink); overflow:hidden; }
.dash-head .avatar .avatar-img { width: 54px; height: 54px; }
.dash-head h2 { color: var(--ink); }
.bars { display: flex; gap: 14px; flex-wrap: wrap; }
.bar-item { flex: 1; min-width: 120px; }
.bar-item .l { font-size: 13px; color: var(--muted); font-weight: 800; }
.bar-item .v { font-family:"Cause",sans-serif; font-size: 26px; font-weight: 700; color: var(--ink); }
.progress-bar { height: 16px; border-radius: 8px; background: var(--cream); border: 2px solid var(--ink); overflow: hidden; margin-top: 6px; }
.progress-bar > span { display: block; height: 100%; background: var(--c-green); }
.recent { margin-top: 16px; font-size: 14px; color: var(--muted); }
.recent ul { margin: 6px 0 0; padding-left: 18px; }

/* dashboard v2: weekly stats */
.dash-insights { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 14px; }
.insight-chip { background: var(--cream); border: var(--bw) solid var(--ink);
  border-radius: 999px; padding: 6px 14px; font-size: 13px; font-weight: 700; color: var(--ink); }
.dash-tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 12px; }
.tile { background: var(--cream); border: var(--bw) solid var(--ink); border-radius: 14px; padding: 12px 14px; }
.tile .l { font-size: 12px; color: var(--muted); font-weight: 800; }
.tile .v { font-family: "Cause",sans-serif; font-size: 26px; font-weight: 700; color: var(--ink); }
.tile .v small { font-size: 14px; color: var(--muted); font-weight: 600; }
.tile .sub { font-size: 12px; color: var(--muted); }
.tile .delta { font-size: 13px; font-weight: 800; margin-left: 4px; }
.tile .delta.up { color: var(--green); }
.tile .delta.down { color: var(--c-red); }
.dash-cols { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-top: 12px; }
.dash-panel { background: var(--cream); border: var(--bw) solid var(--ink); border-radius: 14px; padding: 12px 14px; margin-top: 12px; }
.dash-cols .dash-panel { margin-top: 0; }
.dash-panel h3 { margin: 0 0 8px; font-size: 14px; color: var(--muted); font-weight: 800; }
.week-chart { display: flex; gap: 6px; align-items: stretch; height: 120px; }
.wc-col { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 2px; }
.wc-n { font-size: 11px; font-weight: 800; color: var(--muted); min-height: 14px; }
.wc-bar { flex: 1; width: 100%; max-width: 34px; display: flex; align-items: flex-end;
  background: var(--cream); border: 2px solid var(--ink); border-radius: 8px; overflow: hidden; }
.wc-bar > span { display: block; width: 100%; border-radius: 5px 5px 0 0;
  background: var(--c-teal); }
.wc-col.today .wc-bar > span { background: var(--c-yellow); }
.wc-day { font-size: 11px; font-weight: 800; color: var(--muted); }
.wc-col.today .wc-day { color: var(--ink); }
.game-table { width: 100%; border-collapse: collapse; font-size: 14px; color: var(--ink); }
.game-table td { padding: 4px 2px; border-bottom: 1px dashed var(--line); }
.game-table tr:last-child td { border-bottom: none; }
.game-table .num { text-align: right; font-weight: 700; white-space: nowrap; }
.trouble-row { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 6px; }
.trouble-chip { background: var(--cream); border: var(--bw) solid var(--ink); border-radius: 999px;
  padding: 5px 12px; font-size: 13px; font-weight: 700; color: var(--ink); }
.tr-glyph { font-size: 18px; }
.confusion { margin: 4px 0 0; font-size: 13px; }
.letter-map { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 10px; }
.lm-cell { width: 34px; height: 34px; display: grid; place-items: center; border-radius: 8px;
  font-size: 18px; border: 2px solid var(--ink); background: var(--cream); color: var(--c-slate); }
.lm-cell.learning { background: var(--c-yellow); color: var(--ink); border-color: var(--ink); }
.lm-cell.mastered { background: var(--c-green); color: var(--ink); border-color: var(--ink); }
.lm-key { display: inline-block; width: 11px; height: 11px; border-radius: 3px;
  background: var(--cream); border: 1px solid var(--ink); margin: 0 3px 0 10px; vertical-align: -1px; }
.lm-key.learning { background: var(--c-yellow); border-color: var(--ink); }
.lm-key.mastered { background: var(--c-green); border-color: var(--ink); }

/* collapse to one column only in portrait or truly-narrow viewports — landscape
   SE-class phones (667x375) keep the space-efficient 2-column layout */
@media (max-width: 700px) and (orientation: portrait), (max-width: 560px) {
  .dash-cols { grid-template-columns: 1fr; }
}

/* short landscape (phones ~844x390/932x430): compact the per-child dashboard
   card so each child is ~1.5 screenfuls instead of ~2.5. Dashboard-only
   selectors — .tile/.dash-panel/.week-chart appear in no other template. */
@media (max-height: 480px) {
  .dash-child { padding: 14px 16px; margin-bottom: 12px; }
  .dash-head { margin-bottom: 10px; }
  .dash-head .avatar { width: 48px; height: 48px; font-size: 24px; }
  .dash-head .avatar .avatar-img { width: 40px; height: 40px; }
  .dash-insights { margin-bottom: 10px; }
  .dash-tiles { gap: 8px; }
  .tile { padding: 8px 10px; }
  .tile .v { font-size: 20px; }
  .dash-cols { gap: 8px; margin-top: 8px; }
  .dash-panel { padding: 8px 10px; margin-top: 8px; }
  .week-chart { height: 88px; }
  .recent { margin-top: 10px; }
}

.back { display: inline-flex; align-items: center; min-height: 44px; margin-bottom: 16px; font-weight: 800;
  color: var(--ink);
  background: var(--cream); padding:11px 16px; border-radius: 12px; border: var(--bw) solid var(--ink); box-shadow: 0 3px 0 var(--cream-d); }
.row-between { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.page-title { font-family:"Cause",sans-serif; color:#fff; text-shadow: var(--txt-sh); }
/* subtitle under .page-title — deterministic spacing instead of default <p> margins */
.page-sub { margin: 2px 0 0; font-size: 15px; }

@media (max-width: 560px) {
  .options { grid-template-columns: 1fr; }
  .hud .brand { font-size: 22px; }
}

/* ---------- replay levels (shared ladder, see js/app.js App.levels) ---------- */
.level-chip {
  align-self: center; font-family: var(--font-display); font-size: 13px; font-weight: 700;
  color: #fff; background: var(--c-slate); border: 2px solid var(--ink);
  border-radius: 999px; padding: 2px 12px; margin-right: 8px; white-space: nowrap;
}
.level-up-note {
  font-family: "Cause", sans-serif; font-size: 22px; font-weight: 700;
  color: var(--gold); text-shadow: var(--txt-sh); margin-bottom: 12px;
}

/* ---------- 3-star result panel (shared, see js/app.js App.finishGame) ----------
   Built by App.renderStars() into a game's done panel: a "Tahap N/10" caption,
   three star slots (earned ones lit + animated), and a best-ever line. */
.star-result {
  display: flex; flex-direction: column; align-items: center;
  gap: 6px; margin-bottom: 12px;
}
.star-level {
  font-family: "Cause", sans-serif; font-size: 14px; font-weight: 700;
  letter-spacing: .5px; color: #fff; text-shadow: var(--txt-sh);
  background: rgba(0,0,0,.24); border: 1.5px solid rgba(255,255,255,.45);
  border-radius: 999px; padding: 2px 12px;
}
.star-row { display: flex; gap: clamp(8px, 2vw, 16px); line-height: 1; }
/* gold star ICON used inside .star-slot / .lvl-stars / .star-best — replaces the
   bare ★ glyph, which fell back to a colour-emoji font that ignored CSS `color`
   and painted a dull grey star. 1em follows the host font-size; currentColor
   follows the host colour (empty = faint), and .on forces the earned gold. */
.star-ic { width: 1em; height: 1em; display: inline-block; vertical-align: -.125em; fill: currentColor; }
.star-ic.on { fill: var(--gold); }
.star-slot.lit .star-ic { filter: drop-shadow(0 0 10px rgba(255,201,60,.8)) drop-shadow(0 3px 7px rgba(0,0,0,.35)); }
.star-slot {
  font-size: clamp(40px, 9vw, 60px); line-height: 1;
  color: rgba(255,255,255,.22);                 /* empty slot */
  transform: scale(.7); transition: none;
}
.star-slot.lit {
  color: var(--gold);
  text-shadow: 0 0 12px rgba(255,201,60,.85), 0 3px 8px rgba(0,0,0,.35);
}
/* pop each earned star in turn when the panel (re)renders */
.star-result.anim .star-slot.lit {
  animation: star-pop .42s cubic-bezier(.2,1.5,.4,1) both;
  animation-delay: calc(var(--si, 1) * .16s);
}
@keyframes star-pop {
  0%   { transform: scale(.2) rotate(-25deg); opacity: 0; }
  70%  { transform: scale(1.25) rotate(8deg); opacity: 1; }
  100% { transform: scale(1) rotate(0); opacity: 1; }
}
.star-best {
  font-family: "Cause", sans-serif; font-size: 13px; font-weight: 700;
  color: #fff; opacity: .9; text-shadow: var(--txt-sh); letter-spacing: 1px;
}
/* ---------- shared game result / "done" card (opt-in via .game-done) ----------
   One reusable layout for a game's done panel (#<slug>-done). The pieces are
   produced by different code paths and so arrive in a JUMBLED dom order — the
   .ribbon + .score-pill come from the template, App.renderStars() PREPENDS the
   .star-result, quiz.finish() PREPENDS the "Naik tahap" .level-up-note, and
   quiz.renderReplay() injects the levels button after #<slug>-again.
   This card flex-stacks them and uses `order` to fix the VISUAL order no matter
   the dom order: title -> level-up -> stars -> score -> actions. It also caps
   its height and scrolls internally as a last resort, so the buttons can never
   be clipped by the no-scroll stage on a short/landscape screen (the old bug).
   To adopt: add class="game-done" to the panel, wrap its buttons in
   .done-actions, and delete any per-game #<slug>-done spacing (this owns it). */
.game-done {
  margin: auto; width: min(480px, 100%); max-height: 100%;
  display: flex; flex-direction: column; align-items: center;
  gap: clamp(8px, 2.2vh, 18px); padding: 4px 0; text-align: center;
  overflow-y: auto; -webkit-overflow-scrolling: touch; scrollbar-width: none;
}
.game-done::-webkit-scrollbar { width: 0; height: 0; }
.game-done > .ribbon        { order: 1; }
/* inside the done card the title is a HEADING, not a tappable-looking plate —
   strip the button chrome so the eye lands on the stars, then the actions */
.game-done > .ribbon {
  background: none; border: none; box-shadow: none; border-radius: 0;
  padding: 0; font-size: clamp(30px, 6vh, 40px);
  text-shadow: 0 3px 0 rgba(28,29,38,.35);
}
.game-done > .level-up-note { order: 2; margin: 0; }
.game-done > .star-result   { order: 3; margin: 0; }
.game-done > .score-pill    { order: 4; }
.game-done > .done-actions  { order: 5; }
/* score chip: a small glass pill so it reads as a stat, not body text */
.game-done > .score-pill {
  display: inline-flex; align-items: center; gap: 8px; font-size: 20px;
  padding: 4px 16px; border-radius: 999px;
  background: rgba(0,0,0,.24); border: 1.5px solid rgba(255,255,255,.4);
}

/* action cluster: a single centred column of full-width result buttons. Every
   button (primary "next" plate, the glass replay/levels pills, green "back")
   spans the full width and stacks vertically, so the cluster reads as one tidy
   block at ANY button count. The old 2-col grid stranded a lone pill in the
   left column (half-width, left-aligned) whenever a game had only one pill
   (e.g. just "Pilih tahap") — this never strands. `order` keeps the read order
   primary -> replay -> levels -> back no matter the injected dom order. */
.done-actions {
  display: flex; flex-direction: column; align-items: stretch;
  gap: clamp(8px, 1.6vw, 14px); width: 100%; margin: 0;
}
.done-actions > .btn         { order: 1; margin: 0; min-width: 0; width: 100%; }
.done-actions > .quiz-replay { order: 2; }
.done-actions > .quiz-levels { order: 3; }
.done-actions > .trial-buy   { order: 3; }
.done-actions > .btn.green   { order: 4; }
/* not-purchased: the buy CTA takes the middle slot, so the "Pilih tahap" pill
   yields to it (a locked picker would just bounce to the same paywall).
   !important beats the inline display that renderReplay's .show() sets. */
.done-actions:has(> .trial-buy) > .quiz-levels { display: none !important; }

/* short landscape (phone held sideways): the vertical stack is too tall for the
   no-scroll stage, so lay the card out side-by-side — reward column on the left,
   action buttons on the right — and shrink the trims. Keeps the whole result on
   one screen at ~375px tall. */
@media (max-height: 480px) {
  .game-done {
    width: min(760px, 100%); padding: 0;
    display: grid; grid-template-columns: 1fr 1fr;
    /* reward items each get their own row in the left column; the action column
       spans them all. min-content rows + align-content:center keep the reward
       items grouped as one centred block (they used to stretch apart, dropping
       the score pill to the bottom-left). */
    grid-template-rows: repeat(4, min-content);
    grid-template-areas:
      "ribbon actions"
      "note   actions"
      "stars  actions"
      "score  actions";
    align-content: center; justify-items: center; align-items: center;
    column-gap: clamp(16px, 4vw, 44px); row-gap: clamp(4px, 1.4vh, 10px);
  }
  .game-done > .ribbon        { grid-area: ribbon; font-size: 24px; padding: 0; }
  .game-done > .level-up-note { grid-area: note;   font-size: 15px; }
  .game-done > .star-result   { grid-area: stars;  gap: 2px; }
  .game-done > .star-result .star-slot { font-size: clamp(24px, 6vh, 38px); }
  .game-done > .score-pill    { grid-area: score;  font-size: 17px; padding: 3px 14px; }
  .game-done > .done-actions  { grid-area: actions; align-self: center; width: 100%; gap: 8px; }
  /* keep each button to ONE line so the plate doesn't grow tall in the half-width
     column — that 2-line growth is what pushed the result past a short screen */
  .done-actions > .btn { white-space: nowrap; font-size: 15px; }
}

/* ---------- locked / unlocked level look (course-home + level pickers) ----------
   A level tile shows its earned stars; not-yet-unlocked levels are dimmed with
   a lock and can't be tapped. Generic so any level-grid markup can opt in. */
.lvl-tile {
  position: relative; display: flex; flex-direction: column; align-items: center;
  justify-content: center; gap: 4px; min-width: 56px; padding: 8px 10px;
  border-radius: 14px; font-family: "Cause", sans-serif; font-weight: 700;
  color: var(--ink); cursor: pointer;  /* was #fff = invisible on the flat-white tile */
  background: var(--glass); border: var(--bw) solid var(--ink);
  box-shadow: var(--glass-sh); transition: transform .12s, filter .12s;
}
.lvl-tile:hover { transform: translateY(-2px); }
.lvl-tile .lvl-n { font-size: 18px; line-height: 1; }
/* empty pips = faint ink on the white tile; earned pips force gold via .star-ic.on */
.lvl-tile .lvl-stars { font-size: 12px; color: rgba(28,29,38,.22); letter-spacing: 2px; display: inline-flex; gap: 2px; }
.lvl-tile.locked {
  cursor: not-allowed; filter: grayscale(.6) brightness(.7); opacity: .65;
}
.lvl-tile.locked::before,
.lvl-tile.locked::after {
  content: ""; position: absolute; right: 7px; pointer-events: none;
}
.lvl-tile.locked::before {
  top: 4px; width: 10px; height: 8px; border: 2px solid rgba(28,29,38,.7);
  border-bottom: 0; border-radius: 8px 8px 0 0;
}
.lvl-tile.locked::after {
  top: 12px; width: 16px; height: 11px; border-radius: 4px;
  background: rgba(28,29,38,.7); box-shadow: 0 2px 5px rgba(8,20,14,.2);
}
.lvl-tile.cleared { border-color: rgba(255,201,60,.6); }
/* trial-locked: a level beyond the free-trial ceiling. Unlike a progress lock
   (dead/greyed), this tile is tappable and deep-links to the paywall, so it
   reads as "premium — buy to open": gold tint, full opacity, pointer cursor.
   It keeps the .locked padlock glyph to signal it's still shut. */
.lvl-tile.trial-locked {
  cursor: pointer; filter: none; opacity: 1;
  border-color: rgba(255,201,60,.75);
  background: linear-gradient(180deg, rgba(255,201,60,.18), var(--glass));
}
.lvl-tile.trial-locked:hover { transform: translateY(-2px); }
/* free-trial buy CTA dropped into a game's done panel (App.finishGame) */
.btn.trial-buy { background: var(--gold); color: var(--ink); border-color: var(--ink); }

/* ---------- level-select menu (App.levelSelect, shown after picking a game) ----------
   A stage-covering overlay of .lvl-tile buttons; the top bar stays reachable
   behind it. Tapping an unlocked tile sets the level and starts that run. */
.level-select {
  position: absolute; inset: 0; z-index: 60;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: clamp(12px, 3vh, 24px); padding: clamp(16px, 4vh, 40px) 16px;
  background: rgba(8, 16, 12, .82);
  animation: ls-fade .2s ease both;
}
@keyframes ls-fade { from { opacity: 0; } to { opacity: 1; } }
.level-select .ls-title {
  font-family: var(--font-display); font-weight: 800;
  font-size: clamp(20px, 4.5vw, 30px); color: #fff; text-shadow: var(--txt-sh);
}
.level-select .lvl-grid {
  display: grid; gap: clamp(8px, 1.6vw, 14px); width: min(560px, 92%);
  grid-template-columns: repeat(5, minmax(0, 1fr));
}
.level-select .lvl-tile { width: 100%; }
@media (orientation: portrait) {
  .level-select .lvl-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); width: min(360px, 92%); }
}

/* ---------- tangkap (falling-fruit catch) ---------- */
/* fills the stage, no scroll: prompt pill on top, the rest is the orchard sky
   where glyph-carrying fruit fall; drag the basket along the bottom to catch */
.tg-game { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
.tg-game .progress-dots { flex: 0 0 auto; display: flex; gap: 8px; justify-content: center; margin: 2px 0 10px; }
.tg-game .progress-dots .dot { width: 14px; height: 14px; border-radius: 50%; background: rgba(255,255,255,.3); }
.tg-game .progress-dots .dot.done { background: var(--green); box-shadow: 0 0 10px var(--green); }
.tg-game .progress-dots .dot.current { background: var(--gold); box-shadow: 0 0 12px var(--gold); }

.tg-stage { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; gap: clamp(8px, 1.6vh, 16px); padding: 0 clamp(8px, 2vw, 32px); }
.tg-prompt {
  flex: 0 0 auto; align-self: center; display: inline-flex; align-items: center; gap: 14px;
  padding: 8px clamp(18px, 2.6vw, 30px); border-radius: 999px;
  background: var(--glass);
  border: var(--bw) solid var(--ink); box-shadow: var(--glass-sh);
}
.tg-ask { font-size: clamp(18px, 2.4vw, 24px); font-weight: 800; color: var(--ink); text-shadow: var(--txt-sh); }
.tg-target { font-size: clamp(30px, 4.4vw, 46px); font-weight: 800; line-height: 1; color: var(--gold); vertical-align: middle; padding: 0 4px; }
.tg-listen {
  display: grid; place-items: center; width: clamp(44px, 5vw, 52px); height: clamp(44px, 5vw, 52px); cursor: pointer;
  border-radius: 999px; border: var(--bw) solid var(--ink); color: #fff;
  background: var(--c-orange);
  box-shadow: 0 5px 0 var(--c-orange-d), 0 10px 16px rgba(8,20,14,.4);
  transition: transform .1s, filter .15s, box-shadow .1s;
}
.tg-listen:hover { filter: brightness(1.07); }
.tg-listen:active { transform: translateY(3px); box-shadow: 0 2px 0 var(--c-orange-d), 0 4px 8px rgba(8,20,14,.4); }
.tg-spk { width: 60%; height: 60%; }

.tg-sky { position: relative; flex: 1 1 auto; min-height: 0; overflow: hidden; touch-action: none; cursor: grab; }
.tg-sky:active { cursor: grabbing; }

/* fruit: a real .opt button so quiz.js disable/hint keep working, but the
   basket does the catching — pointers fall through to the sky */
.tg-fruit, .tg-fruit.correct, .tg-fruit.wrong {
  position: absolute; left: 0; top: 0; pointer-events: none;
  background: none; border: none; box-shadow: none; padding: 0;
  display: grid; place-items: center; user-select: none; -webkit-user-select: none;
}
.tg-fruit-ic { grid-area: 1 / 1; font-size: clamp(56px, 8vw, 86px); line-height: 1.15; }
.tg-fruit-glyph { grid-area: 1 / 1; z-index: 1; font-size: clamp(22px, 3.2vw, 34px); font-weight: 800; color: #fff;
  text-shadow: 0 0 6px rgba(0,0,0,.85), 0 2px 6px rgba(0,0,0,.7); transform: translateY(8%); }
/* .opt.hint animates transform/box-shadow — fruits are JS-positioned, so they
   glow on the inner emoji instead of scaling the button */
.tg-fruit.hint { animation: none; box-shadow: none; }
.tg-fruit.hint .tg-fruit-ic { animation: tg-glow 1s ease-in-out infinite; }
@keyframes tg-glow { 0%, 100% { filter: drop-shadow(0 8px 12px rgba(0,0,0,.35)); } 50% { filter: drop-shadow(0 0 18px var(--gold)); transform: scale(1.12); } }
.tg-fruit.tg-caught .tg-fruit-ic { animation: tg-pop .35s cubic-bezier(.2,.9,.3,1.4) both; filter: drop-shadow(0 0 16px var(--green)); }
@keyframes tg-pop { 0% { transform: scale(1.25); } 60% { transform: scale(.85); } 100% { transform: scale(1); } }
.tg-fruit.tg-splat .tg-fruit-ic { animation: tg-shake .5s ease both; filter: drop-shadow(0 0 14px #ff5252) grayscale(.4); }
@keyframes tg-shake { 0%, 100% { transform: rotate(0); } 25% { transform: rotate(-14deg); } 50% { transform: rotate(12deg); } 75% { transform: rotate(-8deg); } }

.tg-basket {
  position: absolute; left: 50%; bottom: clamp(4px, 1.4vh, 16px); transform: translateX(-50%);
  pointer-events: none; z-index: 4; display: grid; place-items: center;
}
.tg-basket-ic { font-size: clamp(68px, 10vw, 110px); line-height: 1; }
.tg-basket.tg-idle .tg-basket-ic { animation: tg-rock 1.8s ease-in-out infinite; }
@keyframes tg-rock { 0%, 100% { transform: rotate(-5deg); } 50% { transform: rotate(5deg); } }

/* one-time gesture cue: drifts left-right above the basket until first drag */
.tg-cue {
  position: absolute; left: 50%; bottom: clamp(96px, 16vh, 160px); transform: translateX(-50%);
  display: flex; align-items: center; gap: 10px; pointer-events: none; z-index: 5;
  font-family: "Cause", sans-serif; font-weight: 800; font-size: clamp(15px, 1.8vw, 19px);
  color: #fff; text-shadow: var(--txt-sh); white-space: nowrap;
  animation: tg-cue-drift 1.6s ease-in-out infinite;
}
.tg-cue-arr { width: clamp(13px, 1.7vw, 19px); height: clamp(13px, 1.7vw, 19px); flex: 0 0 auto;
  border-left: 5px solid #fff; border-bottom: 5px solid #fff; border-radius: 3px; }
.tg-arr-l { transform: rotate(45deg); }
.tg-arr-r { transform: rotate(225deg); }
@keyframes tg-cue-drift { 0%, 100% { transform: translateX(calc(-50% - 26px)); } 50% { transform: translateX(calc(-50% + 26px)); } }


@media (max-width: 760px) {
  .tg-prompt { gap: 10px; padding: 6px 16px; }
  .tg-fruit-ic { font-size: clamp(48px, 11vw, 64px); }
  .tg-basket-ic { font-size: clamp(58px, 13vw, 84px); }
}

/* ---------- pasar (market stall: shelf above, counter below) ---------- */
/* fills the stage, no scroll: dots row + shelf (drag sources) + counter band */
.ps-game { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
.ps-game .progress-dots { flex: 0 0 auto; display: flex; gap: 8px; justify-content: center; margin: 2px 0 10px; }
.ps-game .progress-dots .dot { width: 14px; height: 14px; border-radius: 50%; background: rgba(255,255,255,.3); }
.ps-game .progress-dots .dot.done { background: var(--green); box-shadow: 0 0 10px var(--green); }
.ps-game .progress-dots .dot.current { background: var(--gold); box-shadow: 0 0 12px var(--gold); }

.ps-stage {
  position: relative; flex: 1 1 auto; min-height: 0;
  display: flex; flex-direction: column; gap: clamp(10px, 1.8vh, 18px);
  padding: 0 clamp(8px, 2vw, 28px) clamp(8px, 1.6vh, 16px);
}

/* shelf: frosted card with an awning scallop, goods laid out like a stall */
.ps-shelf {
  position: relative; flex: 1 1 auto; min-height: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: clamp(10px, 2vh, 20px); text-align: center;
  padding: clamp(22px, 3.6vh, 34px) clamp(14px, 2vw, 28px) clamp(14px, 2.4vh, 24px);
  border-radius: var(--radius); background: var(--glass);
  border: var(--bw) solid var(--ink); box-shadow: var(--glass-sh);
  overflow: hidden;
}
.ps-shelf::before { /* awning valance along the stall's top edge */
  content: ""; position: absolute; top: 0; left: 0; right: 0; height: 16px;
  background:
    radial-gradient(circle 11px at 11px 0, #e8533f 96%, transparent) 0 0 / 44px 16px repeat-x,
    radial-gradient(circle 11px at 11px 0, #fff4ea 96%, transparent) 22px 0 / 44px 16px repeat-x;
}
.ps-ask { font-size: clamp(17px, 2.2vw, 22px); font-weight: 800; color: var(--ink); text-shadow: var(--txt-sh); }
.ps-goods { display: flex; flex-wrap: wrap; justify-content: center; gap: clamp(10px, 1.8vw, 20px); }
.ps-item {
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  min-width: clamp(96px, 12vw, 136px); padding: clamp(10px, 1.8vh, 16px) clamp(14px, 1.8vw, 24px) clamp(8px, 1.4vh, 12px);
  border-radius: 20px; touch-action: none; user-select: none; -webkit-user-select: none;
}
.ps-item .ps-pic { font-size: clamp(36px, 6.4vh, 56px); line-height: 1; }
.ps-item .ps-label { font-size: clamp(20px, 3.4vh, 30px); line-height: 1.5; direction: rtl; }
.ps-item.drag-src { opacity: .35; }
.ps-ghost {
  position: fixed; z-index: 120; transform: translate(-50%, -50%); pointer-events: none;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px;
  border-radius: 20px; background: var(--panel); border: 3px solid var(--gold);
  box-shadow: 0 8px 0 var(--cream-d), 0 12px 18px rgba(28,29,38,.2); color: var(--ink);
  font-family: "Cause", sans-serif; font-weight: 700;
}
.ps-ghost .ps-pic { font-size: clamp(36px, 6.4vh, 56px); line-height: 1; }
.ps-ghost .ps-label { font-size: clamp(20px, 3.4vh, 30px); line-height: 1.5; direction: rtl; }

/* counter band: generated stall-counter art; syllable sign + basket on top */
.ps-counter {
  flex: 0 0 auto; min-height: clamp(112px, 20vh, 168px);
  display: flex; align-items: center; justify-content: space-evenly; gap: clamp(10px, 2vw, 24px);
  padding: clamp(8px, 1.6vh, 14px) clamp(12px, 2vw, 28px);
  border-radius: var(--radius); border: 3px solid rgba(255,255,255,.4);
  background: var(--ps-counter-img) center bottom / cover no-repeat, var(--c-orange-d);
  box-shadow: var(--glass-sh);
}

/* hanging price-tag sign: replays the syllable to buy */
.ps-tag {
  display: inline-flex; align-items: center; gap: 12px; cursor: pointer;
  padding: clamp(8px, 1.6vh, 14px) clamp(18px, 2.4vw, 30px); border-radius: 18px;
  border: 3px solid var(--gold); background: var(--surface);
  transform: rotate(-2deg);
  box-shadow: 0 6px 0 var(--cream-d), 0 12px 20px rgba(8,20,14,.45);
  transition: transform .1s, filter .15s, box-shadow .1s;
}
.ps-tag:hover { filter: brightness(1.04); }
.ps-tag:active { transform: rotate(-2deg) translateY(4px); box-shadow: 0 2px 0 var(--cream-d), 0 5px 10px rgba(8,20,14,.4); }
.ps-tag .ps-tag-syl { font-size: clamp(34px, 6.4vh, 54px); line-height: 1.2; direction: rtl; color: #d4541f; }
.ps-spk { width: clamp(22px, 3.6vh, 30px); height: clamp(22px, 3.6vh, 30px); flex: 0 0 auto; color: #b9743c; }

/* basket drop spot on the counter */
.ps-basket {
  position: relative; width: clamp(124px, 17vw, 190px); min-height: clamp(78px, 14vh, 118px);
  border-radius: 22px; background: rgba(20,40,30,.28); border: 3px dashed rgba(255,255,255,.65);
  display: grid; place-items: center;
  transition: transform .12s, background .15s, border-color .15s;
}
.ps-basket:not(.filled) { animation: ps-breathe 1.8s ease-in-out infinite; }
@keyframes ps-breathe { 0%, 100% { border-color: rgba(255,255,255,.55); } 50% { border-color: var(--gold); } }
.ps-basket .ps-basket-ic { font-size: clamp(42px, 8vh, 64px); line-height: 1; }
.ps-basket.drop-hi { transform: scale(1.07); border-color: var(--gold); background: rgba(255,201,60,.28); }
.ps-basket.filled { border-style: solid; border-color: var(--gold); background: var(--panel); box-shadow: 0 6px 0 var(--cream-d); animation: ps-pop .28s cubic-bezier(.2,.9,.3,1.4) both; }
@keyframes ps-pop { 0% { transform: scale(.6); } 60% { transform: scale(1.12); } 100% { transform: scale(1); } }
.ps-basket.filled .ps-basket-ic { display: none; }
.ps-bought { display: flex; flex-direction: column; align-items: center; gap: 2px; padding: 6px 10px; }
.ps-bought .ps-bought-pic { font-size: clamp(34px, 6.4vh, 52px); line-height: 1; }
.ps-bought .ps-bought-word { font-size: clamp(22px, 4vh, 34px); line-height: 1.4; direction: rtl; color: #d4541f; }

/* one-time gesture cue: drifts down toward the counter until the first buy */
.ps-cue {
  position: absolute; left: 50%; bottom: clamp(118px, 22vh, 178px); transform: translateX(-50%);
  display: flex; align-items: center; gap: 8px; pointer-events: none; z-index: 5;
  font-family: "Cause", sans-serif; font-weight: 800; font-size: clamp(15px, 1.8vw, 19px);
  color: #fff; text-shadow: var(--txt-sh); white-space: nowrap;
  animation: ps-cue 1.4s ease-in-out infinite;
}
.ps-cue-arrow { width: clamp(15px, 1.9vw, 22px); height: clamp(15px, 1.9vw, 22px); flex: 0 0 auto;
  border-left: 5px solid #fff; border-bottom: 5px solid #fff; border-radius: 3px;
  transform: rotate(-45deg); }
@keyframes ps-cue { 0%, 100% { transform: translate(-50%, 0); } 50% { transform: translate(-50%, 14px); } }


@media (max-width: 760px) {
  .ps-item { min-width: clamp(84px, 14vw, 110px); }
  .ps-item .ps-pic, .ps-ghost .ps-pic { font-size: clamp(30px, 5.6vh, 44px); }
  .ps-item .ps-label, .ps-ghost .ps-label { font-size: clamp(17px, 3vh, 24px); }
  .ps-counter { min-height: clamp(92px, 18vh, 130px); }
  .ps-tag .ps-tag-syl { font-size: clamp(28px, 5.6vh, 42px); }
  .ps-basket { width: clamp(104px, 20vw, 150px); min-height: clamp(64px, 12vh, 96px); }
  .ps-cue { bottom: clamp(98px, 20vh, 140px); } /* still points at the counter */
}

/* short viewports (landscape phones): shared chrome eats ~200px, so the ask
   line gives way (the cue + syllable tag still carry the task) and the
   counter compresses to keep the goods labels fully visible */
@media (max-height: 520px) {
  .ps-stage { gap: 6px; padding-bottom: 6px; }
  .ps-shelf { padding: 8px 10px 4px; gap: 6px; }
  .ps-shelf::before { height: 10px;
    background:
      radial-gradient(circle 7px at 7px 0, #e8533f 96%, transparent) 0 0 / 28px 10px repeat-x,
      radial-gradient(circle 7px at 7px 0, #fff4ea 96%, transparent) 14px 0 / 28px 10px repeat-x; }
  .ps-ask { display: none; }
  .ps-item { padding: 4px 10px 3px; min-width: 84px; }
  .ps-item .ps-pic, .ps-ghost .ps-pic { font-size: 26px; }
  .ps-item .ps-label, .ps-ghost .ps-label { font-size: 16px; line-height: 1.5; }
  .ps-counter { min-height: 70px; padding: 6px 12px; }
  .ps-tag { padding: 5px 14px; }
  .ps-tag .ps-tag-syl { font-size: 26px; }
  .ps-spk { width: 18px; height: 18px; }
  .ps-basket { width: 96px; min-height: 54px; }
  .ps-basket .ps-basket-ic { font-size: 32px; }
  .ps-bought { padding: 2px 6px; }
  .ps-bought .ps-bought-pic { font-size: 24px; }
  .ps-bought .ps-bought-word { font-size: 18px; }
  .ps-cue { bottom: 84px; font-size: 14px; }
  .ps-cue-arrow { width: 12px; height: 12px; border-width: 4px; }
}

/* ---------- roda (syllable wheel) ---------- */
/* fills the stage, no scroll: dots row + two zones — fairground wheel left
   (flick or tap to spin), answer stall right (waits, then shows the words) */
.rd-game { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
.rd-game .progress-dots { flex: 0 0 auto; display: flex; gap: 8px; justify-content: center; margin: 2px 0 10px; }
.rd-game .progress-dots .dot { width: 14px; height: 14px; border-radius: 50%; background: rgba(255,255,255,.3); }
.rd-game .progress-dots .dot.done { background: var(--green); box-shadow: 0 0 10px var(--green); }
.rd-game .progress-dots .dot.current { background: var(--gold); box-shadow: 0 0 12px var(--gold); }

.rd-stage {
  --rd-size: clamp(220px, min(40vw, 54vh), 430px);
  flex: 1 1 auto; min-height: 0;
  display: grid; grid-template-columns: auto 1fr;
  gap: clamp(18px, 4vw, 56px); align-items: center;
  padding: 0 clamp(10px, 2.5vw, 40px);
}

/* the wheel — a real fairground attraction, spun by hand */
.rd-wheelzone { display: grid; place-items: center; }
.rd-wheelwrap {
  position: relative; width: var(--rd-size); height: var(--rd-size);
  touch-action: none; user-select: none; -webkit-user-select: none;
  cursor: grab;
}
.rd-wheelwrap.spinning { cursor: default; }
.rd-pointer {
  position: absolute; top: calc(var(--rd-size) * -0.05); left: 50%; transform: translateX(-50%);
  width: calc(var(--rd-size) * 0.15); z-index: 3;
}
.rd-pointer svg { display: block; width: 100%; height: auto; }
.rd-wheel {
  width: 100%; height: 100%; border-radius: 50%; position: relative;
  border: calc(var(--rd-size) * 0.028) solid rgba(255,255,255,.9);
  box-shadow: 0 8px 0 var(--cream-d), 0 18px 40px rgba(8,20,14,.5);
  transition: transform 3s cubic-bezier(.12,.8,.22,1);
}
.rd-wheelwrap.dragging .rd-wheel { transition: none; }
.rd-wheel .rd-label {
  position: absolute; left: 50%; top: 50%;
  font-size: calc(var(--rd-size) * 0.105); font-weight: 800; line-height: 1;
  color: #fff; text-shadow: 0 2px 4px rgba(0,0,0,.45); pointer-events: none;
}
.rd-hub {
  position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
  width: calc(var(--rd-size) * 0.2); height: calc(var(--rd-size) * 0.2);
  border-radius: 50%; z-index: 2;
  background: var(--c-orange);
  border: calc(var(--rd-size) * 0.014) solid rgba(255,255,255,.9);
  box-shadow: 0 4px 0 var(--c-orange-d);
}

/* one-time spin cue: circular arrow wiggling around the hub until first spin */
.rd-spincue {
  position: absolute; inset: 0; z-index: 4; pointer-events: none;
  display: grid; place-items: center;
}
.rd-cue-orbit {
  position: absolute; width: 56%; height: 56%; color: #fff;
  animation: rd-orbit 1.8s ease-in-out infinite;
}
@keyframes rd-orbit { 0%, 100% { transform: rotate(-12deg); } 50% { transform: rotate(34deg); } }
.rd-cue-lbl {
  position: absolute; bottom: calc(var(--rd-size) * 0.06); left: 50%; transform: translateX(-50%);
  font-family: "Cause", sans-serif; font-weight: 800; font-size: clamp(16px, 2vw, 21px);
  color: #fff; text-shadow: var(--txt-sh); white-space: nowrap;
}

/* the answer stall — frosted so the scene doesn't bleed through the word cards */
.rd-stall {
  justify-self: center; width: 100%; max-width: 560px; min-height: calc(var(--rd-size) * 0.8);
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: clamp(12px, 2.4vh, 22px); text-align: center;
  padding: clamp(18px, 3vh, 32px) clamp(18px, 2.6vw, 34px);
  border-radius: var(--radius); background: var(--glass);
  border: var(--bw) solid var(--ink); box-shadow: var(--glass-sh);
}
.rd-ask { font-size: clamp(17px, 2.2vw, 23px); font-weight: 800; color: var(--ink); text-shadow: var(--txt-sh); }
.rd-wait .rd-ask { animation: rd-wait-pulse 1.8s ease-in-out infinite; }
@keyframes rd-wait-pulse { 0%, 100% { opacity: .65; } 50% { opacity: 1; } }
.rd-pick { display: flex; flex-direction: column; align-items: center; gap: clamp(10px, 2vh, 18px); width: 100%; }
.rd-pick[hidden] { display: none; }

/* landed syllable: big tappable chip that replays its sound */
.rd-syl-chip {
  display: inline-flex; align-items: center; gap: 12px; cursor: pointer;
  padding: 4px 26px; border-radius: 22px;
  background: var(--surface); border: 4px solid var(--gold);
  box-shadow: 0 8px 0 var(--cream-d), 0 12px 18px rgba(28,29,38,.2);
  animation: rd-pop .32s cubic-bezier(.2,.9,.3,1.4) both;
}
@keyframes rd-pop { 0% { transform: scale(.5); } 60% { transform: scale(1.12); } 100% { transform: scale(1); } }
.rd-syl-chip #rd-syl { font-size: clamp(38px, 6vh, 58px); line-height: 1.6; color: #d4541f; direction: rtl; }
.rd-spk { width: clamp(22px, 3.4vh, 30px); height: clamp(22px, 3.4vh, 30px); flex: 0 0 auto; color: #ff8a3d; }

.rd-options {
  display: grid; gap: clamp(10px, 1.6vw, 16px); width: 100%;
  grid-template-columns: repeat(auto-fit, minmax(clamp(96px, 10vw, 132px), 1fr));
  justify-items: stretch;
}
.rd-options .rd-word {
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  padding: clamp(8px, 1.6vh, 14px) clamp(10px, 1.4vw, 16px);
  min-height: 64px;
}
.rd-options .rd-word .pic { font-size: clamp(30px, 5.6vh, 44px); line-height: 1.15; }
.rd-options .rd-word .label { font-size: clamp(18px, 3.4vh, 26px); line-height: 1.6; direction: rtl; }

@media (max-width: 760px) {
  .rd-stage { --rd-size: clamp(190px, min(38vw, 48vh), 320px); gap: 14px; padding: 0 8px; }
  .rd-stall { min-height: 0; padding: 14px 12px; }
}
/* short viewports (landscape phones): chrome eats ~180px — the syllable chip
   and word cards carry the task, so the ask lines give way first */
@media (max-height: 520px) {
  .rd-stage { --rd-size: clamp(180px, min(36vw, 52vh), 280px); }
  .rd-pick .rd-ask { display: none; }
  .rd-syl-chip { padding: 2px 18px; border-width: 3px; }
  .rd-options .rd-word .pic { font-size: 26px; }
  .rd-options .rd-word .label { font-size: 17px; }
  .rd-cue-lbl { font-size: 14px; }
}

/* ---------- shared chrome diet ---------- */
/* progress dots + level chip read as ambient markers, not a UI row; placed last
   so these win the cascade over the per-game .X-game .progress-dots rules */
.stage .progress-dots { gap: 6px; margin: 0 0 6px; }
.stage .progress-dots .dot { width: 9px; height: 9px; }
.stage .progress-dots .dot.done { box-shadow: 0 0 6px var(--green); }
.stage .progress-dots .dot.current { box-shadow: 0 0 8px var(--gold); }
.stage .level-chip { font-size: 11px; padding: 1px 9px; border-width: 1.5px;
  background: rgba(0,0,0,.22); border-color: rgba(255,255,255,.35); color: rgba(255,255,255,.85); }

/* ---------- bentuk ---------- */
#bt-game { max-width: 640px; }
.bt-ear { font-size: clamp(40px, 9vh, 64px); line-height: 1; margin: 8px 0; }
#bt-options .bt-tile { width: clamp(92px, 18vh, 130px); aspect-ratio: 1;
  display: grid; place-items: center; padding: 0; }
/* CSS-drawn shapes; size from --s, color from --c (set per round in JS) */
.bt-shape { display: block; --s: clamp(60px, 13vh, 90px); background: var(--c); }
.bt-shape.bulatan { width: var(--s); height: var(--s); border-radius: 50%; }
.bt-shape.segiempatsama { width: calc(var(--s) * .93); height: calc(var(--s) * .93);
  border-radius: 10px; }
.bt-shape.segiempattepat { width: calc(var(--s) * 1.16); height: calc(var(--s) * .71);
  border-radius: 10px; }
.bt-shape.segitiga { width: 0; height: 0; background: none;
  border-left: calc(var(--s) * .58) solid transparent;
  border-right: calc(var(--s) * .58) solid transparent;
  border-bottom: var(--s) solid var(--c); }
/* phone landscape: prompt column left, shape tiles right (same recipe as
   #dg-question/#tk-question above) */
@media (max-height: 480px) {
  .bt-ear { font-size: 38px; margin: 2px 0; }
  #bt-question { display: grid; grid-template-columns: auto 1fr; column-gap: 20px;
    align-items: center; justify-items: center;
    grid-template-areas: "ear opts" "ask opts"; }
  #bt-question .bt-ear { grid-area: ear; }
  #bt-question .ask { grid-area: ask; max-width: 240px; }
  #bt-question .options { grid-area: opts; align-content: center; }
  #bt-options .bt-tile { width: clamp(72px, 26vh, 110px); }
  .bt-shape { --s: clamp(48px, 18vh, 76px); }
}

/* ---------- add-to-home-screen nudge (see static/js/a2hs.js) ---------- */
.a2hs {
  position: fixed; left: 50%; bottom: calc(14px + env(safe-area-inset-bottom));
  transform: translate(-50%, 150%); opacity: 0;
  width: min(420px, calc(100vw - 24px)); z-index: 120;
  display: flex; align-items: center; gap: 12px; padding: 12px 38px 12px 14px;
  background: var(--panel); color: var(--ink); border-radius: 20px;
  box-shadow: 0 8px 0 var(--cream-d), 0 12px 18px rgba(28,29,38,.2);
  transition: transform .32s cubic-bezier(.2,.8,.2,1), opacity .32s ease;
}
.a2hs.show { transform: translate(-50%, 0); opacity: 1; }
.a2hs-ic { flex: 0 0 auto; width: 46px; height: 46px; border-radius: 11px;
  box-shadow: 0 3px 0 var(--cream-d); }
.a2hs-body { flex: 1 1 auto; min-width: 0; }
.a2hs-title { font-family: "Cause", sans-serif; font-weight: 600; font-size: 15px; line-height: 1.2; }
.a2hs-steps { margin-top: 3px; font-size: 12.5px; font-weight: 600; color: var(--muted); line-height: 1.4; }
.a2hs-steps b { color: var(--ink); font-weight: 800; }
.a2hs-g { width: 1.05em; height: 1.05em; vertical-align: -.18em; fill: var(--brand); }
.a2hs-install { flex: 0 0 auto; border: 0; cursor: pointer; color: #fff;
  font-family: "Cause", sans-serif; font-weight: 700; font-size: 14px;
  padding: 9px 16px; border-radius: 13px;
  background: var(--c-orange); border: var(--bw) solid var(--ink);
  box-shadow: 0 5px 0 var(--c-orange-d); }
.a2hs-x { position: absolute; top: 6px; right: 8px; border: 0; background: transparent;
  font-size: 22px; line-height: 1; color: var(--muted); cursor: pointer; padding: 2px 7px; }

/* ---------- portrait phones: parent-facing pages (auth, dashboard, billing) ----------
   The kid flow (profiles → home → course → game) is landscape and shows the
   rotate-note in portrait. Auth/dashboard/billing opt into portrait via
   body.allow-portrait; these rules tune *that* portrait experience only and are
   inert in landscape / on desktop. */
@media (orientation: portrait) and (pointer: coarse) {
  /* clear the status bar / camera notch: the top bar (dashboard, billing) and
     the header-less auth pages both start flush at y=0 otherwise, sliding
     content under the notch. env(safe-area-inset-top) is 0 in landscape, so
     this is a pure portrait gain. */
  .game-top { padding-top: calc(12px + env(safe-area-inset-top)); }
  .auth-wrap { padding-top: calc(clamp(16px, 2.6vw, 32px) + env(safe-area-inset-top)); }
  /* flash toasts (e.g. login errors) are absolute at top:10px inside .stage. On
     header-less pages (auth — no .game-top) that's under the status bar / notch.
     :has() scopes the offset to those pages; header pages already clear it. */
  body:not(:has(.game-top)) .flash-stack { top: calc(10px + env(safe-area-inset-top)); }
}

@media (max-width: 560px) {
  /* dashboard stat tiles: auto-fit minmax(140px) drops to a single tall column
     around 360px wide (Galaxy / iPhone mini), making the card a long stack.
     Pin to a 2×2 grid so streak/stars/week/accuracy stay glanceable. */
  .dash-tiles { grid-template-columns: 1fr 1fr; }
  /* reclaim the horizontal room the 24px card frame eats on a ~360px screen */
  .dash-child { padding: 16px; }
  .dash-head { gap: 10px; }
  .dash-head .avatar { width: 52px; height: 52px; font-size: 26px; }
  .dash-head .avatar .avatar-img { width: 44px; height: 44px; }

  /* auth (login / register / claim): the panel frame (40px) + the 9-slice
     button plate's 64px side-caps + 22px type leave only ~150px for the label
     inside a 360px-wide card, so "Create Account" / "Continue with Google" wrap
     to two lines and the plate stretches. Slim the card frame (more room for
     everything) and the plate caps + type. border-image-width must track
     border-width or the caps overflow the text. Width-based so it also catches
     big phones (430px wide, >860px tall) the max-height:860 block misses. */
  .auth-card { padding: 26px 30px; }
  .auth-card .btn.lg { font-size: 17px; }
  /* google button is a flat 2px plate, not 9-slice — keep it flat, just trim
     padding + type so "Continue with Google" stays on one line */
  .auth-card .btn.lg.google-btn { border-width: 2px; border-image-width: 2px; padding: 12px; font-size: 15px; }
  .google-g { width: 19px; height: 19px; }
}

/* ---------- portrait: browse pages (home / course picker / profiles) ----------
   Opted into portrait in the landscape→portrait migration (harness/architecture/ARCHITECTURE.md).
   Their existing compaction keys off HEIGHT (built for landscape); portrait needs
   its own reflow. These win over the height-tier blocks (later in source). */
@media (orientation: portrait) {
  /* home hero: stack greeting over mascot instead of side-by-side */
  .home-pick .hero { flex-direction: column; text-align: center; gap: 10px;
    min-height: 0; padding: 20px 22px; }
  .home-pick .hero .hi { width: 100%; }
  .home-pick .hero h1 { font-size: clamp(26px, 6.4vw, 38px); }
  .home-pick .hero .tag { font-size: 16px; }
  .home-pick .hero .stats { justify-content: center; }
  .home-pick .hero .mascot { height: clamp(120px, 26vh, 190px); }
  .home-pick .hero .glow { right: 50%; transform: translate(50%, -50%); }

  /* course game grid: 2-up in portrait so a 20-game course isn't one tall column */
  .course-home .module-grid { grid-template-columns: 1fr 1fr; gap: 10px; }
}

/* ============================================================
   Arcade wave (10 new games): per-card gradients + page CSS.
   Gradients live with the .module.<slug> block above; play-area
   rules for some games ship inside their templates' scoped <style>.
   ============================================================ */

/* ---- per-game card gradient (course-home tile) — add with the other .module lines (~app.css:454-492) ---- */

/* ---------- ketuk ---------- */
/* whack-a-mole: target card floats top, kampung burrows tile the field. glyph
   "tikus" pop UP from each burrow on JS timers (.opt buttons, called letter
   .is-answer); tap the called letter while up. CSS-only burrow scene — no image
   asset. Every selector scoped to .kt-game so it can't leak to other pages. */
.kt-game { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
.kt-game .progress-dots { flex: 0 0 auto; display: flex; gap: 8px; justify-content: center; margin: 2px 0 10px; }
.kt-game .progress-dots .dot { width: 14px; height: 14px; border-radius: 50%; background: rgba(255,255,255,.3); }
.kt-game .progress-dots .dot.done { background: var(--green); box-shadow: 0 0 10px var(--green); }
.kt-game .progress-dots .dot.current { background: var(--gold); box-shadow: 0 0 12px var(--gold); }

.kt-stage { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; gap: clamp(10px, 2vh, 18px); }

/* target card: what to bonk, always visible while tikus pop */
.kt-target-card {
  flex: 0 0 auto; align-self: center;
  display: flex; align-items: center; gap: clamp(8px, 1.4vw, 14px);
  padding: clamp(8px, 1.4vh, 14px) clamp(14px, 2vw, 22px);
  border-radius: 20px; background: var(--glass);
  border: var(--bw) solid var(--ink); box-shadow: var(--glass-sh);
}
.kt-target-lbl { font-family: "Cause", sans-serif; font-weight: 700; font-size: clamp(13px, 1.7vw, 17px); color: #fff; text-shadow: var(--txt-sh); }
.kt-target-glyph {
  min-width: clamp(56px, 8vh, 84px); padding: 4px 14px; text-align: center;
  font-size: clamp(34px, 6.5vh, 58px); line-height: 1.25; color: #d4541f;
  background: var(--surface); border-radius: 18px;
  border: 3px solid var(--ink); box-shadow: 0 6px 0 var(--cream-d);
}
.kt-listen {
  position: relative; width: clamp(48px, 7vh, 58px); height: clamp(48px, 7vh, 58px); border-radius: 50%;
  display: grid; place-items: center; cursor: pointer; color: #fff;
  border: var(--bw) solid var(--ink);
  background: var(--c-orange);
  box-shadow: 0 5px 0 var(--c-orange-d), 0 10px 16px rgba(8,20,14,.45);
  transition: transform .1s, filter .15s, box-shadow .1s;
}
.kt-listen::before { content: ""; position: absolute; inset: -8px; border-radius: 50%; } /* keeps tap target >=64px */
.kt-listen:hover { filter: brightness(1.07); }
.kt-listen:active { transform: translateY(3px); box-shadow: 0 2px 0 var(--c-orange-d), 0 5px 8px rgba(8,20,14,.4); }
.kt-spk { width: 55%; height: 55%; }

/* burrow field: grassy kampung ground, burrows auto-fit columns to width.
   min-height:0 + overflow:hidden so the field owns leftover height, no scroll */
.kt-field {
  flex: 1 1 auto; min-height: 0; overflow: hidden; border-radius: var(--radius);
  display: grid; gap: clamp(10px, 2.4vh, 26px); place-content: center; place-items: center;
  grid-template-columns: repeat(auto-fit, minmax(clamp(86px, 18vw, 156px), 1fr));
  padding: clamp(12px, 2.4vh, 30px);
  /* tidy grass: soft top light + gentle top-to-bottom green so the field reads
     as a sunlit meadow rather than a flat golf rectangle. Kept simple on purpose. */
  background:
    radial-gradient(130% 72% at 50% -10%, rgba(255,255,255,.20), rgba(255,255,255,0) 56%),
    linear-gradient(180deg, #6BC95F 0%, #57B956 50%, #49A546 100%);
  border: var(--bw) solid var(--ink);
  box-shadow: var(--glass-sh), inset 0 3px 0 rgba(255,255,255,.18);
}

/* one burrow = a dug hole the tikus climbs out of. overflow:visible so the risen
   mouse's head/ears stand above the hole (the ducked mouse is hidden by opacity:0,
   no clip needed). NOTE: do NOT use clip-path here — a negative-inset clip expands
   the hole's HIT box upward into the row above, misrouting clicks. The whole cell
   is the click target (ketuk.js binds the tap on .kt-hole, not the small sprite),
   so hits stay responsive at every level regardless of how small the mouse gets. */
.kt-hole {
  position: relative; width: 100%; max-width: clamp(116px, 21vw, 178px);
  aspect-ratio: 6 / 5; overflow: visible; isolation: isolate;
  /* iOS Safari zeroes aspect-ratio items in auto grid tracks (see menulis bug),
     which would collapse these contentless burrows to 0px. This floor (a touch
     below the aspect height so it never fights the ratio elsewhere) keeps the
     holes visible there; harmless on Chromium/Android. */
  min-height: clamp(92px, 16.5vw, 144px);
}
/* the dug burrow: a dark mouth sunk into the grass, ringed by a raised soil lip.
   ::before paints the soil rim (wider, behind the mouth's dark fill); the mouth
   sits at z:3 over the tikus (z:1) so the tikus reads as climbing out of it. */
.kt-mound {
  position: absolute; left: 50%; bottom: 7%; transform: translateX(-50%);
  width: 58%; height: 17%; border-radius: 50%; z-index: 3;
  background: radial-gradient(112% 150% at 50% 14%, #150b03 0%, #2b1707 55%, #43260f 100%);
  box-shadow:
    inset 0 6px 10px rgba(0,0,0,.62),
    inset 0 -3px 6px rgba(255,222,170,.12),
    0 2px 7px rgba(0,0,0,.3);
}
.kt-mound::before {
  content: ""; position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
  width: 138%; height: 168%; border-radius: 50%; z-index: -1;
  background: radial-gradient(closest-side, rgba(0,0,0,0) 56%, #7a5026 62%, #976a35 75%, rgba(120,150,60,0) 92%);
}
.kt-hole.kt-miss .kt-mound { animation: kt-miss-pulse .5s ease; }
@keyframes kt-miss-pulse {
  0%,100% { box-shadow: inset 0 9px 14px rgba(0,0,0,.66), inset 0 -3px 6px rgba(255,222,170,.12), 0 3px 9px rgba(0,0,0,.32); }
  50%     { box-shadow: inset 0 9px 14px rgba(0,0,0,.66), 0 0 22px var(--gold); }
}

/* the tikus: starts ducked under the mound, slides UP when raised. It's an .opt
   button so quiz.js disable/hint/celebrate work; lives behind the mound (z:1)
   so it appears to climb out of the hole. padding-bottom nudges the glyph up to
   the optical centre (Arabic letters hang low in the em box). */
/* the tikus is a bespoke flat-vector mouse SPRITE (whole head + body) holding a
   blank card; the called LETTER is overlaid on that card via .kt-glyph, so one
   sprite serves every letter. The art replaces the old CSS face — no more
   ear/eye/nose pseudo-elements. background-image is set per-page in ketuk.html
   so it resolves to the CDN (or local static) sprite url. */
.kt-tikus {
  position: absolute; left: 50%; bottom: 0; z-index: 1;
  width: clamp(86px, 18.5vw, 138px); height: clamp(86px, 18.5vw, 138px);
  min-width: 64px; min-height: 64px; cursor: pointer;
  background-color: transparent; background-repeat: no-repeat;
  background-position: center bottom; background-size: contain;
  border: 0; border-radius: 0; box-shadow: none; padding: 0;
  transform: translate(-50%, 120%); opacity: 0;
  /* opacity fades faster than the slide so a ducking mouse vanishes before it
     dips into the grass gap (no clip-path — that would expand the hit box). */
  transition: transform .2s cubic-bezier(.2,.9,.3,1.35), opacity .1s;
  touch-action: manipulation; user-select: none; -webkit-user-select: none;
  pointer-events: none; /* only tappable while UP */
}
/* the called letter, overlaid on the mouse's blank card (the card sits low-centre
   on the sprite). top/width/height are tuned to the card's empty panel. */
.kt-glyph {
  position: absolute; left: 49%; top: 62%; transform: translate(-50%, -50%);
  width: 46%; height: 24%; display: grid; place-items: center;
  font-family: inherit; font-size: clamp(22px, 4.9vw, 40px); line-height: 1;
  color: #2c1f14; pointer-events: none;
}
.kt-tikus.jawi .kt-glyph { font-family: "Amiri", serif; }
.kt-tikus.up { transform: translate(-50%, -34%); opacity: 1; pointer-events: auto; }
.kt-tikus.ducking { transform: translate(-50%, 120%); opacity: 0; pointer-events: none; }
/* HOVER FIX: the shared .opt:hover{transform:translateY(-3px)} is more specific
   than .kt-tikus.up, so without this it wipes the centring translateX(-50%) and
   shoots the tikus out beside the hole (letter no longer over the mouth). Only
   the UP tikus is pointer-active, so only it can be hovered — keep it centred &
   lifted, add a small pop for feedback. .kt-game raises specificity past .opt. */
.kt-game .kt-tikus.up:hover { transform: translate(-50%, -38%) scale(1.05); }
.kt-game .kt-tikus.up:active { transform: translate(-50%, -30%) scale(.97); }
.kt-tikus.correct, .kt-tikus.bonk { animation: kt-bonk .42s ease; }
@keyframes kt-bonk { 0% { transform: translate(-50%, -34%) scale(1); } 40% { transform: translate(-50%, -6%) scale(.78); } 100% { transform: translate(-50%, 120%) scale(.7); opacity: 0; } }
.kt-tikus.wrong { animation: kt-shake .4s ease; filter: drop-shadow(0 0 7px #ff5252); }
@keyframes kt-shake { 0%,100% { transform: translate(-50%, -34%); } 25% { transform: translate(-58%, -34%); } 75% { transform: translate(-42%, -34%); } }
/* quiz.js hint pulses .opt.hint with a bare scale() — that wipes the centering
   translate, so ketuk overrides with a full transform that keeps the tikus up */
.kt-tikus.hint { animation: kt-hint 1s ease-in-out infinite; border-color: var(--gold); }
@keyframes kt-hint {
  0%,100% { transform: translate(-50%, -34%) scale(1); box-shadow: 0 0 0 rgba(244,193,46,0); }
  50% { transform: translate(-50%, -40%) scale(1.1); box-shadow: 0 0 26px var(--gold); }
}

/* phone landscape: compact card to a row (label adds nothing), denser burrows */
@media (max-width: 760px), (max-height: 480px) {
  .kt-target-lbl { display: none; }
  .kt-target-glyph { font-size: clamp(28px, 8vw, 42px); min-width: 52px; }
  .kt-field { gap: clamp(6px, 1.4vh, 14px); padding: clamp(8px, 1.4vh, 16px);
    grid-template-columns: repeat(auto-fit, minmax(clamp(72px, 16vw, 120px), 1fr)); }
  .kt-tikus { width: clamp(50px, 14vw, 84px); height: clamp(50px, 14vw, 84px); }
  .kt-glyph { font-size: clamp(22px, 6.4vw, 34px); }
}

/* ===== course-home tile gradient (add with the other .module.<slug> lines, ~app.css:454-492) ===== */

/* ===== page CSS (add as a new namespaced block with the other game blocks in app.css) =====
   Tarik Tali: tug-of-war letter race. All selectors scoped to .tt-game / #tt-*
   (the tk- namespace is owned by titik, so this game uses tt-). Reuses shared
   vars only. */
/* ---------- tarik (Tarik Tali, tug-of-war) ---------- */
.tt-game { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
.tt-game .progress-dots { flex: 0 0 auto; display: flex; gap: 8px; justify-content: center; margin: 2px 0 10px; }
.tt-game .progress-dots .dot { width: 14px; height: 14px; border-radius: 50%; background: rgba(255,255,255,.3); }
.tt-game .progress-dots .dot.done { background: var(--green); box-shadow: 0 0 10px var(--green); }
.tt-game .progress-dots .dot.current { background: var(--gold); box-shadow: 0 0 12px var(--gold); }

.tt-stage { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column;
  align-items: center; justify-content: space-evenly; gap: clamp(12px, 2.4vh, 28px); }

/* target card: what to pull for, always in view + a listen button */
.tt-target { display: flex; align-items: center; gap: clamp(10px, 1.6vw, 18px);
  padding: clamp(10px, 1.8vh, 16px) clamp(14px, 2vw, 22px); border-radius: 22px;
  background: var(--glass);
  border: var(--bw) solid var(--ink); box-shadow: var(--glass-sh); }
.tt-target-lbl { font-family: "Cause", sans-serif; font-weight: 700;
  font-size: clamp(13px, 1.7vw, 17px); color: var(--muted); text-shadow: var(--txt-sh); }
.tt-target-glyph { min-width: clamp(64px, 10vh, 96px); padding: 4px 14px; text-align: center;
  font-size: clamp(40px, 8vh, 68px); line-height: 1.25; color: #d4541f;
  background: var(--surface); border-radius: 18px;
  border: 3px solid var(--ink); box-shadow: 0 6px 0 var(--cream-d); }
.tt-target-glyph.jawi { font-family: "Amiri", serif; }
.tt-name { font-family: "Cause", sans-serif; font-weight: 800;
  font-size: clamp(15px, 2vw, 20px); color: var(--ink); text-shadow: var(--txt-sh); }
.tt-listen { position: relative; width: clamp(48px, 7vh, 58px); height: clamp(48px, 7vh, 58px);
  border-radius: 50%; display: grid; place-items: center; cursor: pointer; color: #fff;
  border: var(--bw) solid var(--ink);
  background: var(--c-orange);
  box-shadow: 0 5px 0 var(--c-orange-d), 0 10px 16px rgba(8,20,14,.45);
  transition: transform .1s, filter .15s, box-shadow .1s; }
.tt-listen::before { content: ""; position: absolute; inset: -8px; border-radius: 50%; }
.tt-listen:active { transform: translateY(3px);
  box-shadow: 0 2px 0 var(--c-orange-d), 0 5px 8px rgba(8,20,14,.4); }
.tt-spk { width: 55%; height: 55%; }

/* the rope: opponent (left) vs child (right), a peg marker rides the line */
.tt-rope { width: min(92%, 640px); display: flex; align-items: center; gap: clamp(6px, 1.4vw, 14px); }
.tt-foe, .tt-child { font-size: clamp(34px, 7vh, 58px); line-height: 1; flex: 0 0 auto;
  transition: transform .2s ease; }
.tt-rope.tt-toward-child .tt-child { transform: scale(1.12); }
.tt-rope.tt-toward-foe   .tt-foe   { transform: scale(1.12); }
.tt-line { position: relative; flex: 1 1 auto; min-width: 0; height: clamp(16px, 2.6vh, 26px);
  border-radius: 999px; background: var(--c-orange-d);
  border: 2px solid rgba(255,255,255,.5); }
.tt-mid { position: absolute; left: 50%; top: -6px; bottom: -6px; width: 4px;
  transform: translateX(-50%); background: rgba(255,255,255,.8); border-radius: 3px; }
.tt-flag { position: absolute; top: 50%; width: 4px; height: 150%; transform: translateY(-50%);
  background: rgba(255,255,255,.45); border-radius: 3px; }
.tt-flag-foe { left: 0; } .tt-flag-child { right: 0; }
.tt-peg { position: absolute; top: 50%; left: 50%; width: clamp(26px, 4vh, 38px); height: clamp(26px, 4vh, 38px);
  border-radius: 50%; transform: translate(-50%, -50%);
  background: var(--gold);
  border: 3px solid #fff; box-shadow: 0 4px 0 var(--c-yellow-d);
  transition: left .28s cubic-bezier(.2,.8,.3,1.2); }

/* option row: glyph buttons (.opt/.glyph-opt) — quiz.js handles disable/hint/celebrate */
.tt-opts { display: flex; flex-wrap: wrap; justify-content: center; align-items: center;
  gap: clamp(10px, 2vw, 18px); }
.tt-opts .opt.jawi { font-family: "Amiri", serif; }

/* phone landscape: tighter rhythm so the rope + options fit a ~314px stage */
@media (max-height: 480px) {
  .tt-stage { gap: 8px; justify-content: center; }
  .tt-target { gap: 10px; padding: 8px 12px; }
  .tt-target-lbl { display: none; }
  .tt-target-glyph { font-size: clamp(30px, 9vw, 44px); min-width: 56px; }
  .tt-name { font-size: 15px; }
  .tt-foe, .tt-child { font-size: clamp(28px, 8vh, 42px); }
  .tt-opts .opt { min-height: 48px; }
}

/* ----- per-game card gradient (course-home tile): deep dragon purple -> ember ----- */

/* ---------- naga (boss-spell) ---------- */
/* Full-stage, zero-scroll flex column. Spelling IS the attack: a picture +
   ordered empty slots (RTL) above a glyph tray; the dragon creeps in on a
   timer. All sizes fluid clamp() so it fills phone->tablet without scrolling.
   Every selector scoped to #ng-game / .ng-* (app.css is shared by all pages).
   NOTE: the full block below is ALSO inlined in the template <style> (loads
   after app.css) so the page renders standalone; keep them in sync. */
#ng-game.ng-game { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
.ng-game .progress-dots { flex: 0 0 auto; display: flex; gap: 8px; justify-content: center; margin: 2px 0 8px; }
.ng-game .progress-dots .dot { width: 14px; height: 14px; border-radius: 50%; background: rgba(255,255,255,.3); }
.ng-game .progress-dots .dot.done { background: var(--green); box-shadow: 0 0 10px var(--green); }
.ng-game .progress-dots .dot.current { background: var(--gold); box-shadow: 0 0 12px var(--gold); }

.ng-stage {
  position: relative; flex: 1 1 auto; min-height: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: space-between;
  gap: clamp(6px, 1.4vh, 14px); padding: clamp(6px, 1.4vh, 14px) clamp(8px, 2vw, 28px);
}

.ng-field {
  position: relative; flex: 0 0 auto; width: 100%;
  height: clamp(96px, 20vh, 200px);
  border-radius: var(--radius);
  background: var(--c-slate);
  border: var(--bw) solid var(--ink); box-shadow: var(--glass-sh); overflow: hidden;
}
.ng-hero, .ng-dragon {
  position: absolute; bottom: clamp(8px, 2vh, 18px); line-height: 1;
  font-size: clamp(40px, 11vh, 96px);
}
.ng-hero { left: clamp(8px, 2vw, 22px); }
.ng-dragon {
  right: clamp(6px, 1.6vw, 18px);
  transform: translateX(calc(var(--ng-march, 0%) * -2.2));
  transition: transform .5s ease-in;
}
.ng-dragon.recoil { animation: ng-recoil .45s ease-out; }
@keyframes ng-recoil { 0% { } 30% { transform: translateX(calc(var(--ng-march,0%)*-2.2 + 26px)) rotate(8deg); } 100% { } }
.ng-dragon.hurt { animation: ng-hurt .3s ease-in-out; }
@keyframes ng-hurt { 0%,100% { filter: drop-shadow(0 6px 10px rgba(8,20,14,.45)); } 50% { filter: drop-shadow(0 0 14px #ff5a4d) brightness(1.4); } }
.ng-stage.ng-lose .ng-field { animation: ng-shakefield .4s; box-shadow: 0 0 22px #ff5a4d; }
@keyframes ng-shakefield { 0%,100% { transform: translateX(0); } 25% { transform: translateX(-7px); } 75% { transform: translateX(7px); } }

.ng-hpbar {
  position: absolute; left: 50%; bottom: 8px; transform: translateX(-50%);
  width: 78%; height: clamp(8px, 1.4vh, 12px); border-radius: 999px;
  background: rgba(8,20,14,.45); border: 1px solid rgba(255,255,255,.4); overflow: hidden;
}
.ng-hp-fill { height: 100%; width: 0%; border-radius: 999px;
  background: var(--c-red);
  transition: width .5s ease-in; }

.ng-meaning { flex: 0 0 auto; display: flex; align-items: center; justify-content: center; gap: clamp(10px, 2vw, 20px); }
.ng-pic { font-size: clamp(34px, 7vh, 58px); line-height: 1; }
.ng-rumi { font-size: clamp(16px, 2.4vh, 24px); font-weight: 800; color: rgba(255,255,255,.95); text-shadow: var(--txt-sh); min-height: 1.1em; }
.ng-listen {
  display: inline-flex; align-items: center; justify-content: center; cursor: pointer;
  width: clamp(46px, 6vh, 56px); height: clamp(46px, 6vh, 56px); border-radius: 999px;
  border: 2px solid var(--ink);
  background: var(--c-orange);
  box-shadow: 0 4px 0 var(--c-orange-d), 0 8px 14px rgba(8,20,14,.4);
  transition: transform .1s, filter .15s;
}
.ng-listen:hover { filter: brightness(1.07); }
.ng-listen:active { transform: translateY(3px); }
.ng-spk { width: 1.4em; height: 1.4em; color: #fff; }

.ng-spellcard {
  flex: 0 1 auto; max-width: 100%;
  padding: clamp(10px, 2vh, 20px) clamp(16px, 3.4vw, 38px);
  border-radius: var(--radius); background: var(--glass);
  border: var(--bw) solid var(--ink); box-shadow: var(--glass-sh);
}
.ng-slots { display: flex; align-items: center; justify-content: center; flex-wrap: wrap; gap: clamp(6px, 1vw, 12px); }
.ng-slots.jawi { direction: rtl; }
.ng-slot {
  display: grid; place-items: center; width: clamp(46px, 8vh, 80px); height: clamp(56px, 10vh, 92px);
  border-radius: 16px; background: rgba(20,40,30,.28); border: 3px dashed rgba(255,255,255,.6);
  transition: transform .12s, background .15s, border-color .15s;
}
.ng-slot:not(.filled) { animation: ng-breathe 1.8s ease-in-out infinite; }
@keyframes ng-breathe { 0%,100% { border-color: rgba(255,255,255,.55); } 50% { border-color: var(--gold); } }
.ng-slot-glyph { font-size: clamp(32px, 6.5vh, 60px); line-height: 1; color: #fff; }
.ng-slot.filled { background: var(--panel); border-style: solid; border-color: var(--gold);
  box-shadow: 0 6px 0 var(--cream-d); animation: ng-pop .28s cubic-bezier(.2,.9,.3,1.4) both; }
.ng-slot.filled .ng-slot-glyph { color: #d4541f; }
@keyframes ng-pop { 0% { transform: scale(.6); } 60% { transform: scale(1.12); } 100% { transform: scale(1); } }
.ng-slot.shake { animation: ng-slotshake .4s; border-color: #ff5a4d; }
@keyframes ng-slotshake { 0%,100% { transform: translateX(0); } 20% { transform: translateX(-8px); } 60% { transform: translateX(8px); } }

.ng-tray { flex: 0 0 auto; display: flex; flex-wrap: wrap; align-items: center; justify-content: center;
  gap: clamp(8px, 1.6vw, 16px); max-width: 96%; }
.ng-tile {
  display: grid; place-items: center; min-width: clamp(48px, 8vh, 84px); height: clamp(56px, 10vh, 92px);
  padding: 0 clamp(6px, 1vw, 14px); font-size: clamp(34px, 6.5vh, 60px); line-height: 1; color: #d4541f;
  background: var(--surface); border-radius: 16px;
  border: 3px solid var(--ink); box-shadow: 0 6px 0 var(--cream-d);
  touch-action: manipulation; user-select: none; -webkit-user-select: none;
  transition: transform .1s, opacity .2s, box-shadow .1s;
}
.ng-tile:active { transform: translateY(3px); }
.ng-tile.used { opacity: .35; }
.ng-tile:disabled { cursor: default; }

.ng-projectile {
  position: fixed; z-index: 130; transform: translate(-50%,-50%); pointer-events: none;
  font-size: clamp(28px, 5vh, 46px); color: var(--gold);
  filter: drop-shadow(0 0 8px var(--gold));
  transition: left .45s cubic-bezier(.4,0,.9,.6), top .45s cubic-bezier(.4,0,.9,.6), transform .45s, opacity .45s;
}


@media (max-height: 480px) {
  .ng-field { height: clamp(70px, 26vh, 120px); }
  .ng-hero, .ng-dragon { font-size: clamp(34px, 22vh, 70px); }
  .ng-pic { font-size: clamp(28px, 9vh, 44px); }
  .ng-slot, .ng-tile { height: clamp(46px, 18vh, 70px); }
  .ng-slot { width: clamp(40px, 11vh, 60px); }
  .ng-slot-glyph, .ng-tile { font-size: clamp(26px, 11vh, 46px); }
}
@media (orientation: portrait) {
  .ng-stage { justify-content: space-around; }
  .ng-field { height: clamp(110px, 18vh, 180px); }
  .ng-slot { width: clamp(40px, 11vw, 64px); height: clamp(50px, 13vw, 78px); min-width: 44px; }
  .ng-slot-glyph { font-size: clamp(28px, 8vw, 48px); }
  .ng-tile { min-width: 48px; min-height: 48px; height: clamp(50px, 13vw, 78px);
    font-size: clamp(28px, 8vw, 48px); }
  .ng-tray { gap: 8px; }
}

/* Per-card gradient for the course-home tile (add with the other .module.<slug>
   lines in app.css, ~454-492). Sky-blue → azure, distinct from cari/tangkap/dengar. */

/* NOTE: all play-area CSS is already shipped INSIDE the template's scoped <style>
   block (every selector namespaced .tb-game / #tb-* / .tb-*), because this build
   task forbade editing app.css. If you later want it in app.css for consistency
   with the other 32 game blocks, lift the <style> block verbatim under a
   `---------- terbang (Burung Jawi — flappy bird) ----------` header — it is
   already fully scoped and will not leak. No new hex one-offs beyond the gradient
   above; it reuses --glass/--gold/--green/--radius/--txt-sh vars. */

/* card gradient (course-home tile) — add to the .module.<slug> block in app.css */

/* NOTE: NO per-game page CSS block is needed in app.css for lancar. All of its
   play-area CSS lives self-contained in the template's <style> block
   (templates/games/lancar.html), every selector scoped to .lc-game / .lc-* /
   #lc-* so it cannot leak to other pages. This was deliberate: the build task
   forbade editing app.css, so the base layout (stage grid, build card, slots,
   tiles, banks, ghost) + the rocket launch track + fuel gauge + landscape/
   portrait tiers all sit in the template. If the integrator prefers the
   house convention (page CSS in app.css under a `---------- lancar ----------`
   header), move the template's base rules there verbatim — they are already
   namespaced and will work unchanged. */

/* gradient for the course-home tile (place in the .module.<slug> block, app.css ~454-492) */

/* ---------- ingat (Lampu Isyarat — Simon-says letter memory) ---------- */
/* fills the stage, no scroll: a status line on top, then a row of lit
   traffic-light "lamp" pads. A growing sequence flashes + sounds out; the
   child taps the pads back in order. OWN LOOP (no quiz dots), own done panel.
   Subject-agnostic: glyph/order/name from the bank, .ig-rtl picks jawi faces.
   Every selector scoped to .ig-* / #ig-* so nothing leaks into other pages. */
.ig-game { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; gap: clamp(6px, 1.4vh, 12px); }
.ig-game .progress-dots { flex: 0 0 auto; display: flex; gap: 8px; justify-content: center; margin: 2px 0 6px; }
.ig-game .progress-dots .dot { width: 14px; height: 14px; border-radius: 50%; background: rgba(255,255,255,.3); }
.ig-game .progress-dots .dot.done { background: var(--green); box-shadow: 0 0 10px var(--green); }
.ig-game .progress-dots .dot.current { background: var(--gold); box-shadow: 0 0 12px var(--gold); }

.ig-head { flex: 0 0 auto; display: flex; align-items: center; justify-content: center; gap: 10px; }
.ig-status { font-family: "Cause", sans-serif; font-weight: 700; font-size: clamp(14px, 2.2vh, 20px);
  color: #fff; text-shadow: var(--txt-sh); background: rgba(0,0,0,.28);
  border: 2px solid rgba(255,255,255,.5); border-radius: 999px; padding: 3px 16px; }

/* lamp row: --n pads set per run from JS; pads grow to fill leftover height */
/* Pad board: a centred grid, not one long row. Wrap at --ig-cols columns so the
   lamps read as a compact ~square block (3×3 holds the 4–8-pad ladder; bump
   --ig-cols to 4 for a 4×4 feel). max-width pins the wrap point to exactly
   --ig-cols pads; flex-wrap + justify-content keep every row — including a ragged
   last row — centred. --ig-pad drives both pad size and the wrap width so they
   never drift (landscape shrinks --ig-pad below). */
.ig-board { flex: 1 1 auto; min-height: 0;
  --ig-pad: clamp(56px, 16vmin, 132px);
  --ig-gap: clamp(8px, 2.2vw, 22px);
  --ig-cols: 3;
  display: flex; flex-wrap: wrap; align-content: center; justify-content: center;
  gap: var(--ig-gap);
  max-width: calc(var(--ig-cols) * var(--ig-pad) + (var(--ig-cols) - 1) * var(--ig-gap));
  margin-inline: auto; }
.ig-rtl .ig-board { direction: rtl; } /* jawi fills right → left */

/* each pad is a dimmed "lamp"; --h gives it its own hue so the row reads as a
   set of distinct lights. .lit blooms it bright (the flash / tap feedback). */
.ig-pad { position: relative; flex: 0 0 auto; box-sizing: border-box; width: var(--ig-pad, clamp(56px, 16vmin, 132px));
  aspect-ratio: 1 / 1; min-width: 44px; min-height: 44px; border: 0; cursor: pointer;
  border-radius: clamp(14px, 3vh, 26px); -webkit-user-select: none; user-select: none;
  display: flex; align-items: center; justify-content: center;
  background: hsl(var(--h, 200) 60% 42%);
  border: 3px solid rgba(255,255,255,.22);
  box-shadow: 0 5px 0 rgba(0,0,0,.35), 0 10px 16px rgba(8,20,14,.3);
  transition: transform .12s, box-shadow .15s, background .15s, filter .15s; }
.ig-pad:active { transform: translateY(2px); box-shadow: 0 3px 0 rgba(0,0,0,.35); }
.ig-pad.lit { background: hsl(var(--h, 200) 90% 60%);
  border-color: rgba(255,255,255,.85); filter: brightness(1.15);
  box-shadow: inset 0 0 12px rgba(255,255,255,.6),
    0 0 22px hsl(var(--h, 200) 90% 60%), 0 5px 0 rgba(0,0,0,.35); }
.ig-pad.wrong { background: #c0392b; border-color: #fff;
  animation: ig-shake .4s ease; }
@keyframes ig-shake { 0%,100% { transform: translateX(0); }
  25% { transform: translateX(-7px); } 75% { transform: translateX(7px); } }

.ig-glyph { font-size: clamp(26px, 7vmin, 64px); line-height: 1; color: #fff;
  font-weight: 700; text-shadow: 0 2px 4px rgba(0,0,0,.45); pointer-events: none; }

/* done panel: own-loop games show a single longest-run score, not score/total */
.ig-best { font-family: "Cause", sans-serif; font-size: 14px; color: var(--muted);
  margin-top: 6px; }

/* phone landscape: shorter pads so the grid never overflows the height (shrink
   the var so the wrap-width max-width tracks it) */
@media (max-height: 480px) {
  .ig-board { --ig-pad: clamp(48px, 13vmin, 96px); }
  .ig-pad { border-radius: clamp(10px, 2vh, 18px); }
  .ig-glyph { font-size: clamp(22px, 6vmin, 48px); }
  .ig-status { font-size: clamp(12px, 4.4vh, 16px); padding: 2px 12px; }
}

/* card gradient — distinct teal->violet pair (not used by any other slug) */

/* NOTE: all PAGE-SCOPED play-area CSS for ular ships INSIDE the template's own
   <style> block (templates/games/ular.html), the same self-contained pattern
   cari/menulis use for their portrait/layout overrides — every selector is
   scoped to .ul-* / #ul-* so nothing leaks. The integrator only needs to add
   the .module.ular gradient line above to app.css (with the other slug
   gradients, ~lines 454-492). No other app.css edit is required. */

/* per-game course-home tile gradient — distinct navy->violet "piano keys" pair,
   not used by any existing .module.<slug> (verified against app.css:454-491) */

/* NOTE: ALL piano play-area CSS already lives page-scoped in the template's
   <style> block (templates/games/piano.html), which loads AFTER app.css — the
   same pattern tangkap.html uses for its portrait overrides. No app.css game
   block is required. If you prefer to relocate it into app.css under a
   `---------- piano ----------` header, move the .pn-game / #pn-* rules
   verbatim from the template; they are all already scoped to .pn-game. */

/* ---------- potong card gradient (course-home tile) ---------- */
/* add this ONE line with the other .module.<slug> rows in app.css (the ~454-492
   block). Distinct watermelon-slash crimson, unused elsewhere. */

/* ---------- potong (fruit-ninja letter slice) ---------- */
/* fills the stage, no scroll: prompt pill on top, the rest is the toss field
   where glyph-carrying logs rise UP; swipe a finger through them to slice the
   called letter. All selectors page-scoped to .tk-game / #tk-*. */
.tk-game { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
.tk-game .progress-dots { flex: 0 0 auto; display: flex; gap: 8px; justify-content: center; margin: 2px 0 10px; }
.tk-game .progress-dots .dot { width: 14px; height: 14px; border-radius: 50%; background: rgba(255,255,255,.3); }
.tk-game .progress-dots .dot.done { background: var(--green); box-shadow: 0 0 10px var(--green); }
.tk-game .progress-dots .dot.current { background: var(--gold); box-shadow: 0 0 12px var(--gold); }

.tk-stage { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; gap: clamp(8px, 1.6vh, 16px); padding: 0 clamp(8px, 2vw, 32px); }
.tk-prompt {
  flex: 0 0 auto; align-self: center; display: inline-flex; align-items: center; gap: 14px;
  padding: 8px clamp(18px, 2.6vw, 30px); border-radius: 999px;
  background: var(--glass);
  border: var(--bw) solid var(--ink); box-shadow: var(--glass-sh);
}
.tk-ask { font-size: clamp(18px, 2.4vw, 24px); font-weight: 800; color: var(--ink); text-shadow: var(--txt-sh); }
.tk-target { font-size: clamp(30px, 4.4vw, 46px); font-weight: 800; line-height: 1; color: var(--gold); vertical-align: middle; padding: 0 4px; }
.tk-listen {
  display: grid; place-items: center; width: clamp(44px, 5vw, 52px); height: clamp(44px, 5vw, 52px); cursor: pointer;
  border-radius: 999px; border: var(--bw) solid var(--ink); color: #fff;
  background: var(--c-orange);
  box-shadow: 0 5px 0 var(--c-orange-d), 0 10px 16px rgba(8,20,14,.4);
  transition: transform .1s, filter .15s, box-shadow .1s;
}
.tk-listen:hover { filter: brightness(1.07); }
.tk-listen:active { transform: translateY(3px); box-shadow: 0 2px 0 var(--c-orange-d), 0 4px 8px rgba(8,20,14,.4); }
.tk-spk { width: 60%; height: 60%; }

/* score-attack HUD: level chip · countdown bar · live score, above the field */
.tk-hud { position: relative; display: flex; align-items: center; gap: clamp(8px, 2vw, 16px);
  padding: clamp(4px, 1.2vh, 10px) clamp(8px, 2.5vw, 20px); }
.tk-head { flex: 0 0 auto; }
.tk-timewrap { position: relative; flex: 1 1 auto; height: clamp(18px, 3.2vh, 26px);
  border-radius: 999px; overflow: hidden; background: rgba(8,20,14,.28);
  box-shadow: inset 0 1px 3px rgba(0,0,0,.35); }
.tk-timebar { position: absolute; inset: 0 auto 0 0; width: 100%;
  background: var(--c-green);
  transition: width .1s linear, background .3s; }
.tk-timewrap.low .tk-timebar { background: var(--c-red); animation: tk-low 1s ease-in-out infinite; }
@keyframes tk-low { 0%, 100% { filter: brightness(1); } 50% { filter: brightness(1.25); } }
.tk-timewrap.hit { animation: tk-hit .35s ease; }
@keyframes tk-hit { 0%, 100% { transform: translateX(0); } 25% { transform: translateX(-4px); } 75% { transform: translateX(4px); } }
.tk-time { position: absolute; inset: 0; display: grid; place-items: center;
  font-weight: 900; font-size: clamp(12px, 2vh, 16px); color: #fff;
  text-shadow: 0 1px 3px rgba(0,0,0,.7); }
.tk-scorebox { flex: 0 0 auto; font-weight: 900; color: #fff; font-size: clamp(15px, 2.4vh, 20px);
  text-shadow: 0 2px 4px rgba(0,0,0,.55); white-space: nowrap; }
.tk-score-lbl { opacity: .85; font-weight: 700; font-size: .8em; margin-right: 2px; }
/* floating "-2s" tag that drifts up when a decoy is cut */
.tk-pen { position: absolute; right: clamp(8px, 2.5vw, 20px); top: 50%;
  color: #ff5252; font-weight: 900; font-size: clamp(16px, 3vh, 22px);
  text-shadow: 0 2px 5px rgba(0,0,0,.6); pointer-events: none;
  animation: tk-pen .7s ease-out both; }
@keyframes tk-pen { 0% { transform: translateY(0); opacity: 0; } 20% { opacity: 1; } 100% { transform: translateY(-26px); opacity: 0; } }

/* the toss field: full-bleed swipe surface; fruit fly in from every edge */
.tk-field { position: relative; flex: 1 1 auto; min-height: 0; overflow: hidden; touch-action: none; cursor: crosshair; }

/* log: a real .opt button so quiz.js disable/hint keep working, but the swipe
   does the slicing — pointers fall through to the field */
.tk-log, .tk-log.correct, .tk-log.wrong {
  position: absolute; left: 0; top: 0; pointer-events: none;
  background: none; border: none; box-shadow: none; padding: 0;
  display: grid; place-items: center; user-select: none; -webkit-user-select: none;
}
.tk-log-ic { grid-area: 1 / 1; font-size: clamp(56px, 8vw, 86px); line-height: 1.15; }
.tk-log-glyph { grid-area: 1 / 1; z-index: 1; font-size: clamp(22px, 3.2vw, 34px); font-weight: 800; color: #fff;
  text-shadow: 0 0 6px rgba(0,0,0,.85), 0 2px 6px rgba(0,0,0,.7); transform: translateY(8%); }
/* .opt.hint animates transform/box-shadow — logs are JS-positioned, so they
   glow on the inner emoji instead of scaling the button */
.tk-log.hint { animation: none; box-shadow: none; }
.tk-log.hint .tk-log-ic { animation: tk-glow 1s ease-in-out infinite; }
@keyframes tk-glow { 0%, 100% { filter: drop-shadow(0 8px 12px rgba(0,0,0,.35)); } 50% { filter: drop-shadow(0 0 18px var(--gold)); transform: scale(1.12); } }
.tk-log.tk-sliced .tk-log-ic { animation: tk-pop .4s cubic-bezier(.2,.9,.3,1.4) both; filter: drop-shadow(0 0 16px var(--green)); }
@keyframes tk-pop { 0% { transform: scale(1.25) rotate(0); } 50% { transform: scale(.9) rotate(-12deg); } 100% { transform: scale(0) rotate(-20deg); opacity: 0; } }
.tk-log.tk-splat .tk-log-ic { animation: tk-shake .5s ease both; filter: drop-shadow(0 0 14px #ff5252) grayscale(.4); }
@keyframes tk-shake { 0%, 100% { transform: rotate(0); } 25% { transform: rotate(-14deg); } 50% { transform: rotate(12deg); } 75% { transform: rotate(-8deg); } }

/* blade: glowing trail canvas that follows the finger (drawn in JS). Sits above
   the logs so the slash reads over the fruit; never eats pointer events. */
.tk-blade { position: absolute; inset: 0; z-index: 4; pointer-events: none; }
/* juice: splash ring + coloured droplets burst at the slice point */
.tk-splash, .tk-juice { position: absolute; pointer-events: none; z-index: 3; border-radius: 50%; }
.tk-splash { width: 16px; height: 16px; margin: -8px 0 0 -8px;
  background: radial-gradient(circle, rgba(255,255,255,.95), rgba(255,255,255,0) 70%);
  animation: tk-splash .5s ease-out both; }
.tk-splash.bad { background: radial-gradient(circle, rgba(255,90,90,.9), rgba(255,90,90,0) 70%); }
@keyframes tk-splash { 0% { transform: scale(.3); opacity: .9; } 100% { transform: scale(6); opacity: 0; } }
.tk-juice { width: 14px; height: 14px; margin: -7px 0 0 -7px; --jx: 0px; --jy: 0px;
  box-shadow: 0 0 8px rgba(0,0,0,.25); animation: tk-juice .6s cubic-bezier(.2,.7,.3,1) both; }
@keyframes tk-juice {
  0% { transform: translate(0,0) scale(1); opacity: 1; }
  100% { transform: translate(var(--jx), var(--jy)) scale(.3); opacity: 0; }
}

/* combo badge: pops in the top-right corner on clean correct streaks */
.tk-combo {
  position: absolute; top: clamp(6px, 1.4vh, 14px); right: clamp(8px, 2vw, 18px);
  pointer-events: none; z-index: 6; opacity: 0; transform: scale(.7);
  font-family: "Cause", sans-serif; font-weight: 800; font-size: clamp(18px, 2.6vw, 28px);
  color: var(--gold); text-shadow: 0 0 10px rgba(255,201,60,.6), var(--txt-sh);
  transition: opacity .18s ease, transform .18s ease;
}
.tk-combo.show { opacity: 1; transform: scale(1); }
.tk-combo.pulse { animation: tk-combo-pop .32s cubic-bezier(.2,.9,.3,1.4) both; }
@keyframes tk-combo-pop { 0% { transform: scale(1.5); } 60% { transform: scale(.9); } 100% { transform: scale(1); } }

/* one-time gesture cue: a slashing blade hint until the first swipe */
.tk-cue {
  position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
  display: flex; align-items: center; gap: 12px; pointer-events: none; z-index: 5;
  font-family: "Cause", sans-serif; font-weight: 800; font-size: clamp(15px, 1.8vw, 19px);
  color: #fff; text-shadow: var(--txt-sh); white-space: nowrap;
  animation: tk-cue-slash 1.5s ease-in-out infinite;
}
.tk-cue-blade { width: clamp(22px, 3vw, 32px); height: 5px; flex: 0 0 auto; border-radius: 3px;
  background: linear-gradient(90deg, transparent, #fff); transform: rotate(-25deg); }
@keyframes tk-cue-slash { 0%, 100% { transform: translate(calc(-50% - 22px), -50%); } 50% { transform: translate(calc(-50% + 22px), -50%); } }


@media (max-width: 760px) {
  .tk-prompt { gap: 10px; padding: 6px 16px; }
  .tk-log-ic { font-size: clamp(48px, 11vw, 64px); }
}

/* card gradient — fresh two-stop, distinct from menulis(#2fe0a0,#16b9c8)/cari/tangkap */

/* NOTE: ALL page-scoped lakar play-area CSS lives in the template's own <style>
   block (templates/games/lakar.html), every selector scoped to .lk-game / #lk-*,
   so nothing here can leak to other pages. The block loads after app.css and
   covers: .lk-game flex column + relative, #lk-done absolute overlay, canvas,
   progress-dots (lakar isn't a .quiz page so dots are styled in full there),
   portrait + (max-height:480px) landscape grid. If the integrator prefers the
   play-area rules in app.css instead, move that block under a `---------- lakar ----------`
   header; it is already fully .lk-* scoped. The one line that MUST be in app.css
   is the .module.lakar gradient above (course-home tile color). */

/* ---------- runner wave card gradients (course-home tiles) ---------- */
/* lari/elak/kutip are terbang-style auto-runners. ALL their play-area CSS lives
   page-scoped in each template's <style> block; only the .module.<slug> tile
   gradient belongs here, alongside the other .module rows. */

/* lari (Lari Jawi — auto-runner): per-card gradient. Warm kampung dusk —
   sunset sky over earth, matching the runner-on-a-ground-line mechanic. */

/* Elak Lorong card gradient (.module.elak) — scoped to the course-home tile.
   Add to learn/static/css/app.css alongside the other .module.<slug> rules.
   Rushing-track teal→indigo to read as a fast lane-dodger, distinct from
   terbang's sky and piano's keys. */

/* ----- per-game card gradient (course-home tile): meadow green -> sky azure (runner) ----- */

/* gol (Penalti Jawi — penalty shootout): per-card gradient. Football-pitch green
   under a sky band, reading as a goalmouth, distinct from kutip's blue-green. */

/* ---------- arcade wave 3 card gradients (course-home tiles) ---------- */
/* Each new mechanic gets ONE distinct-hue .module.<slug> tile gradient here;
   all play-area CSS lives page-scoped in each game's template <style> block. */

/* ---------- course home v2 preview ----------
   Full-width course bands + repeated mission cards. Scoped to the preview route
   so the current course home remains untouched while the redesign is evaluated. */
.course-home-v2 {
  max-width: none; padding: 0 0 calc(30px + env(safe-area-inset-bottom));
  color: #fff;
}
.course-home-v2 a { text-decoration: none; }
.v2-hero, .v2-featured, .v2-section {
  padding-left: clamp(16px, 3vw, 40px);
  padding-right: clamp(16px, 3vw, 40px);
}
.v2-kicker {
  margin: 0 0 6px; font-family: "Cause", sans-serif; font-size: 12px;
  font-weight: 800; letter-spacing: 1.8px; text-transform: uppercase;
  color: #ffcf5c; text-shadow: 0 2px 7px rgba(8,20,14,.5);
}
.v2-hero {
  position: relative; overflow: hidden;
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(160px, 260px);
  gap: clamp(18px, 4vw, 46px); align-items: center;
  min-height: clamp(210px, 28vh, 330px);
  padding-top: clamp(22px, 4vw, 46px); padding-bottom: clamp(22px, 4vw, 46px);
  background: var(--c-green-d);
  border-bottom: 1px solid rgba(255,255,255,.18);
  isolation: isolate;
}
.v2-hero::before {
  content: ""; position: absolute; inset: 0; z-index: -1; opacity: .42;
  background:
    linear-gradient(135deg, transparent 0 22%, rgba(255,255,255,.12) 22% 23%, transparent 23% 47%, rgba(255,255,255,.1) 47% 48%, transparent 48% 100%),
    repeating-linear-gradient(90deg, rgba(255,255,255,.06) 0 1px, transparent 1px 44px);
}
.v2-hero h1 {
  margin: 0; max-width: 760px; font-family: "Cause", sans-serif;
  font-size: clamp(38px, 7vw, 82px); line-height: .92; letter-spacing: 0;
  color: #fff; text-shadow: 0 5px 18px rgba(8,20,14,.42);
}
.v2-tagline {
  max-width: 650px; margin: 14px 0 0;
  font-size: clamp(16px, 2vw, 21px); line-height: 1.28; font-weight: 700;
  color: rgba(255,255,255,.9); text-shadow: 0 2px 9px rgba(8,20,14,.45);
}
.v2-stat-row {
  display: flex; flex-wrap: wrap; gap: 8px; margin-top: clamp(18px, 2.6vw, 28px);
}
.v2-stat-row span {
  display: inline-flex; align-items: baseline; gap: 7px; min-height: 38px;
  padding: 7px 11px; border-radius: 8px;
  background: rgba(255,255,255,.13); border: 1px solid rgba(255,255,255,.24);
}
.v2-stat-row strong { font-family: "Cause", sans-serif; font-size: 20px; line-height: 1; }
.v2-stat-row small { font-size: 12px; font-weight: 800; color: rgba(255,255,255,.78); }
.v2-script-card {
  justify-self: end; width: min(100%, 248px); aspect-ratio: 1 / 1;
  display: grid; align-content: center; justify-items: center; gap: 12px;
  border-left: 1px solid rgba(255,255,255,.26);
  background: rgba(255,255,255,.06);
}
.v2-script-card .jawi {
  font-size: clamp(66px, 12vw, 118px); line-height: 1; color: #fff6d6;
  text-shadow: 0 8px 18px rgba(8,20,14,.46);
}
.v2-script-card i {
  display: block; width: 64%; height: 7px; border-radius: 8px;
  background: var(--c-yellow);
  box-shadow: 0 4px 0 var(--c-yellow-d);
}
.v2-script-card i:nth-child(3) { width: 48%; background: var(--c-orange); box-shadow: 0 4px 0 var(--c-orange-d); }
.v2-script-card i:nth-child(4) { width: 32%; background: var(--c-teal); box-shadow: 0 4px 0 var(--c-teal-d); }

.v2-featured {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center; gap: clamp(14px, 2.6vw, 30px);
  padding-top: clamp(16px, 2.2vw, 26px); padding-bottom: clamp(16px, 2.2vw, 26px);
  background: rgba(8,20,14,.24); border-top: 1px solid rgba(255,255,255,.14);
  border-bottom: 1px solid rgba(255,255,255,.16);
}
.v2-featured-art {
  width: clamp(82px, 12vw, 136px); aspect-ratio: 1; display: grid; place-items: center;
}
.v2-featured-art img {
  width: 100%; height: 100%; object-fit: contain;
}
.v2-featured-copy h2 {
  margin: 0; font-family: "Cause", sans-serif; font-size: clamp(26px, 4vw, 46px);
  line-height: 1; color: #fff; text-shadow: var(--txt-sh);
}
.v2-featured-copy p:last-child {
  margin: 7px 0 0; max-width: 560px; color: rgba(255,255,255,.82);
  font-weight: 700; font-size: clamp(14px, 1.6vw, 18px); line-height: 1.25;
}
.v2-featured-btn { white-space: nowrap; }

.v2-section { margin-top: clamp(24px, 3.2vw, 42px); }
.v2-section-head {
  display: flex; align-items: end; justify-content: space-between; gap: 14px;
  margin-bottom: 12px;
}
.v2-section-head h2 {
  margin: 0; font-family: "Cause", sans-serif; font-size: clamp(24px, 3.4vw, 38px);
  line-height: 1; color: #fff; text-shadow: var(--txt-sh);
}
.v2-section-head > span {
  flex: 0 0 auto; margin-bottom: 3px; padding: 6px 10px; border-radius: 8px;
  font-family: "Cause", sans-serif; font-size: 13px; font-weight: 800;
  color: #fff; background: rgba(255,255,255,.12); border: 1px solid rgba(255,255,255,.24);
}
.v2-game-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: 12px;
}
.v2-latih-grid { grid-template-columns: repeat(auto-fit, minmax(176px, 1fr)); }
.v2-tile {
  --v2-accent: #ffcf5c;
  position: relative; min-height: 198px; overflow: hidden; isolation: isolate;
  display: grid; grid-template-rows: auto 1fr auto auto; align-items: center;
  padding: 12px; border-radius: 8px; color: #fff;
  background: rgba(255,255,255,.12);
  border: 1px solid rgba(255,255,255,.22);
  box-shadow: 0 8px 0 rgba(8,20,14,.26), 0 12px 18px rgba(8,20,14,.2);
  transition: transform .14s ease, border-color .14s ease, background .14s ease;
}
.v2-tile:nth-child(6n+1) { --v2-accent: #ffcf5c; }
.v2-tile:nth-child(6n+2) { --v2-accent: #5fd0c0; }
.v2-tile:nth-child(6n+3) { --v2-accent: #ff7a3d; }
.v2-tile:nth-child(6n+4) { --v2-accent: #9f7aea; }
.v2-tile:nth-child(6n+5) { --v2-accent: #43d17a; }
.v2-tile:nth-child(6n+6) { --v2-accent: #4aa3ff; }
.v2-tile::before {
  content: ""; position: absolute; inset: 0 0 auto; height: 5px;
  background: var(--v2-accent);
}
@media (hover: hover) and (pointer: fine) {
  .v2-tile:hover {
    transform: translateY(-5px); border-color: rgba(255,255,255,.4);
    background: rgba(255,255,255,.18);
  }
  .v2-tile:hover .v2-art img { transform: translateY(-4px) scale(1.05); }
}
.v2-tile-num {
  justify-self: start; padding: 3px 8px; border-radius: 8px;
  font-family: "Cause", sans-serif; font-size: 12px; font-weight: 800;
  color: #10271e; background: var(--v2-accent);
}
.v2-stars {
  position: absolute; top: 12px; right: 12px; display: inline-flex; align-items: center; gap: 4px;
  padding: 3px 7px; border-radius: 8px; font-family: "Cause", sans-serif;
  font-size: 12px; font-weight: 800; color: #fff;
  background: rgba(0,0,0,.24); border: 1px solid rgba(255,255,255,.2);
}
.v2-stars img { width: 16px; height: 16px; object-fit: contain; }
.v2-art {
  align-self: end; justify-self: center; width: min(74%, 132px); aspect-ratio: 1;
  display: grid; place-items: center; margin-top: 6px;
}
.v2-art img {
  width: 100%; height: 100%; object-fit: contain;
  transition: transform .2s ease;
}
.v2-copy { min-width: 0; display: grid; gap: 4px; text-align: center; margin-top: 8px; }
.v2-copy strong {
  font-family: "Cause", sans-serif; font-size: clamp(19px, 2vw, 25px);
  line-height: 1.05; color: #fff; text-shadow: var(--txt-sh);
}
.v2-copy span {
  min-height: 2.4em; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; color: rgba(255,255,255,.76); font-size: 13px; font-weight: 700; line-height: 1.2;
}
.v2-action {
  justify-self: center; margin-top: 10px; padding: 5px 10px; border-radius: 8px;
  font-family: "Cause", sans-serif; font-size: 12px; font-weight: 800;
  color: #10271e; background: #fff7d8; border: 1px solid rgba(255,255,255,.35);
}
.v2-tile.is-locked { opacity: .72; }
.v2-tile.is-locked .v2-art img { filter: grayscale(.85); }
.v2-tile.is-locked .v2-action { color: #fff; background: rgba(0,0,0,.22); }
.v2-lock-mark {
  position: absolute; top: 42px; right: 14px; width: 28px; height: 25px; opacity: .9;
}
.v2-lock-mark::before {
  content: ""; position: absolute; left: 6px; top: 0; width: 16px; height: 14px;
  border: 3px solid #fff7d8; border-bottom: 0; border-radius: 10px 10px 0 0;
}
.v2-lock-mark::after {
  content: ""; position: absolute; left: 2px; right: 2px; bottom: 0; height: 16px;
  border-radius: 5px; background: #fff7d8;
}

@media (max-height: 560px) and (orientation: landscape) {
  .v2-hero {
    grid-template-columns: minmax(0, 1fr) 140px; min-height: 154px;
    padding-top: 14px; padding-bottom: 14px;
  }
  .v2-hero h1 { font-size: clamp(31px, 7vh, 48px); }
  .v2-tagline { display: none; }
  .v2-stat-row { margin-top: 10px; }
  .v2-script-card { width: 132px; }
  .v2-script-card .jawi { font-size: 58px; }
  .v2-featured { grid-template-columns: 76px minmax(0, 1fr) auto; padding-top: 10px; padding-bottom: 10px; }
  .v2-featured-art { width: 72px; }
  .v2-featured-copy h2 { font-size: 24px; }
  .v2-featured-copy p:last-child { display: none; }
  .v2-section { margin-top: 20px; }
  .v2-tile { min-height: 154px; }
  .v2-art { width: min(62%, 88px); }
  .v2-copy span { display: none; }
}

@media (orientation: portrait) {
  .course-home-v2 { padding-bottom: calc(18px + env(safe-area-inset-bottom)); }
  .v2-hero {
    grid-template-columns: 1fr; min-height: 0;
    padding-top: 20px; padding-bottom: 20px;
  }
  .v2-hero h1 { font-size: clamp(37px, 11vw, 58px); }
  .v2-tagline { font-size: 16px; }
  .v2-script-card {
    justify-self: stretch; width: 100%; aspect-ratio: auto; min-height: 96px;
    grid-template-columns: auto 1fr; align-content: center; justify-items: start;
    border-left: 0; border-top: 1px solid rgba(255,255,255,.18);
    padding-top: 12px;
  }
  .v2-script-card .jawi { grid-row: 1 / 4; font-size: 58px; }
  .v2-script-card i { width: 100%; }
  .v2-script-card i:nth-child(3) { width: 72%; }
  .v2-script-card i:nth-child(4) { width: 48%; }
  .v2-featured { grid-template-columns: 78px minmax(0, 1fr); }
  .v2-featured-btn { grid-column: 1 / -1; width: fit-content; justify-self: start; }
  .v2-section-head { align-items: start; }
  .v2-game-grid, .v2-latih-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
  .v2-tile { min-height: 172px; padding: 10px; }
  .v2-art { width: min(72%, 104px); }
  .v2-copy strong { font-size: clamp(17px, 4.8vw, 22px); }
  .v2-copy span { display: none; }
  .v2-stars { top: 10px; right: 10px; font-size: 11px; }
}

@media (max-width: 360px) and (orientation: portrait) {
  .v2-game-grid, .v2-latih-grid { grid-template-columns: 1fr; }
  .v2-tile { min-height: 154px; grid-template-columns: 84px minmax(0, 1fr); grid-template-rows: auto 1fr auto; column-gap: 10px; }
  .v2-tile-num { grid-column: 1 / 2; }
  .v2-art { grid-column: 1 / 2; grid-row: 2 / 4; width: 82px; margin: 0; }
  .v2-copy { grid-column: 2 / 3; grid-row: 2 / 3; align-self: center; text-align: left; margin: 0; }
  .v2-action { grid-column: 2 / 3; grid-row: 3 / 4; justify-self: start; }
}

/* ============================================================================
   SHARED GAME-SCENE JUICE (sj-*) — harness/architecture/GAMES.md quality bar.
   Generalised from sarang-huruf's .sh-* so EVERY PI game can render its answer
   options as depicted scene objects over a bespoke backdrop with one shared
   feedback juice. Hooks the classes quiz.js already toggles (.correct / .wrong /
   .is-answer.hint / .reteach + the disabled state) so a card-quiz game gets the
   juice for free by tagging its options .opt.sj-obj; own-loop games (sarang-
   huruf / tolak / pilih-baik / gali / teman) call App.juice.* directly.
   ========================================================================== */
/* bespoke backdrop PNG over the stage's flat-colour fallback */
.sj-backdrop { position: absolute; inset: 0; width: 100%; height: 100%;
  display: block; object-fit: cover; object-position: center;
  pointer-events: none; z-index: 0; }

/* turn any quiz.js .opt into a chrome-free scene object — only its art shows */
.opt.sj-obj, .opt.sj-obj:disabled,
.opt.sj-obj.correct, .opt.sj-obj.wrong {
  background: none; border: 0; padding: 0; margin: 0; min-width: 0;
  box-shadow: none; outline: none; font: inherit; color: inherit;
  -webkit-appearance: none; appearance: none; }
.opt.sj-obj { cursor: pointer; transition: filter .2s ease, transform .12s ease; }

/* correct: gold glow + a small lift (filter works on <img>/<svg>/div alike) */
.sj-correct, .opt.sj-obj.correct {
  filter: drop-shadow(0 0 14px #F4C12E); animation: sj-pop-ok .42s ease; }
@keyframes sj-pop-ok { 0%{transform:scale(1);} 42%{transform:scale(1.12);} 100%{transform:scale(1);} }

/* wrong: a soft shake + bounce, NEVER a shatter / red-X (a miss costs no star) */
.sj-wrong, .opt.sj-obj.wrong { animation: sj-shake .44s ease; }
@keyframes sj-shake {
  0%,100% { transform: translate3d(0,0,0) rotate(0); }
  18% { transform: translate3d(-4px,0,0) rotate(-3deg); }
  42% { transform: translate3d(4px,0,0) rotate(3deg); }
  68% { transform: translate3d(-3px,0,0) rotate(-2deg); }
  86% { transform: translate3d(2px,0,0) rotate(1deg); }
}

/* after 2 misses quiz.js adds .hint to .is-answer → pulse the correct option */
.sj-hint, .opt.sj-obj.is-answer.hint { animation: sj-pulse 1s ease infinite; }
@keyframes sj-pulse {
  0%,100% { filter: drop-shadow(0 0 0 rgba(244,193,46,0)); }
  50% { filter: drop-shadow(0 0 13px rgba(244,193,46,.92)); }
}
/* first-miss gentle one-shot reteach glow (quiz.js .reteach) */
.opt.sj-obj.is-answer.reteach { animation: sj-pulse 1.4s ease 1; }

/* on-brand cream prompt bubble — replaces plain off-white question boxes */
.sj-bubble { display: inline-flex; align-items: center; justify-content: center;
  gap: 10px; background: var(--cream, #ECE2CC); color: var(--ink, #1C1D26);
  border: 3px solid var(--ink, #1C1D26); border-radius: 18px;
  padding: 8px 18px; box-shadow: 0 4px 0 var(--cream-d, #D8C9A8);
  font-family: var(--font-text), system-ui; font-weight: 700; }

/* floating "Cuba lagi!" note on a wrong attempt (soft orange, never punitive) */
.sj-cuba { position: absolute; left: 50%; top: 14%; transform: translate(-50%, 0);
  font-family: var(--font-display), system-ui; font-weight: 800;
  font-size: clamp(20px, 6vw, 30px); color: #fff;
  background: var(--c-orange, #F2902F); border: 3px solid var(--ink, #1C1D26);
  border-radius: 16px; padding: 6px 18px;
  box-shadow: 0 4px 0 var(--c-orange-d, #D9701C);
  z-index: 30; pointer-events: none; animation: sj-cuba 1.15s ease forwards; }
@keyframes sj-cuba {
  0% { opacity: 0; transform: translate(-50%, 10px) scale(.8); }
  18% { opacity: 1; transform: translate(-50%, 0) scale(1); }
  78% { opacity: 1; }
  100% { opacity: 0; transform: translate(-50%, -8px) scale(1); }
}

/* sparkle burst on a correct answer (App.juice.sparkle sets --sx/--sy/left/top) */
.sj-spark { position: absolute; width: 12px; height: 12px; pointer-events: none;
  z-index: 28; transform: translate(-50%, -50%);
  background: radial-gradient(circle, #FFF3C9 0 34%, #F4C12E 36% 60%, transparent 62%);
  animation: sj-spark .62s ease-out forwards; }
@keyframes sj-spark {
  0% { opacity: 0; transform: translate(-50%, -50%) scale(.3); }
  30% { opacity: 1; }
  100% { opacity: 0;
    transform: translate(calc(-50% + var(--sx, 0px)), calc(-50% + var(--sy, 0px))) scale(1.15); }
}
