  /* heavy rounded Korean display font for the win/celebration UI (self-hosted for offline/Capacitor) */
  @font-face {
    font-family: 'Jua';
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    /* subset to the glyphs actually used (ASCII + the ~254 Hangul syllables in our copy): 2.0MB TTF
       -> 104KB WOFF. Re-subset from the source TTF (kept in repo, .vercelignored) when adding copy.
       Missing glyphs fall back to system-ui below — graceful, never tofu. */
    src: url('../../assets/fonts/Jua-subset.woff') format('woff');
  }
  :root {
    --sha-tint: ;   /* equipped Sha costume tint (set by main on equip; empty = base/no tint) */
    /* Quiet Canvas — warm light theme (calm + vivid). The vivid jewels are the only saturated color;
       everything else is a calm warm-neutral so the puzzle content pops. */
    --bg: #e7dfce;          /* warm beige page */
    --bg2: #f0eadc;         /* lighter beige (gradient top / raised) */
    --panel: #f4efe2;       /* raised card/panel surface */
    --card: #f7f2e7;        /* lightest surface (pills, tiles) */
    --lead: #d3c9b2;        /* card/modal border (CSS only; board leading is drawn dark in board.js) */
    --line: #d8cfba;        /* subtle hairline border */
    --scrim: rgba(46,39,24,.42); /* modal overlay */
    --glass: #ded4bf;       /* empty (unfilled) cell — warm neutral, reads as empty on beige */
    --text: #262a31;        /* ink */
    --muted: #8a836f;
    --s1: #e8475c;          /* ruby */
    --s2: #f0a01e;          /* amber */
    --s3: #21b083;          /* emerald */
    --s4: #2f86e0;          /* sapphire */
    --s5: #9a55e0;          /* amethyst */
    --sX: #b4a98f;
    --bad: #e23b46;
    /* gold text family — the one source for gold UI text (PLAY, rewards, victory, grade) */
    --gold: #ffe07a;        /* gold text fill */
    --gold-pale: #fff0a5;   /* pale gold (grade title) */
    --gold-deep: #7a4a08;   /* gold text stroke / shadow */
    --good: #1aa377;
    /* ── shared depth kit (2026-06-06): one source for the raised/pressed/sheen look across ALL screens.
       Surfaces & buttons compose these so every panel reads as the same warm, lifted glass. ── */
    --lift-sm: 0 2px 6px rgba(90,76,40,.13);             /* pills, list rows */
    --lift:    0 8px 20px -6px rgba(90,76,40,.28);       /* cards, nodes, raised buttons */
    --lift-lg: 0 18px 44px -14px rgba(90,76,40,.36);     /* modals, hero, elevated panels */
    --sheen:   inset 0 1px 0 rgba(255,255,255,.7);       /* top-edge highlight (light catches the rim) */
    --press:   inset 0 2px 6px rgba(120,98,54,.26);      /* recessed/pressed-in */
    --grad-card: linear-gradient(180deg, var(--card), var(--panel)); /* default raised surface fill */
    --app-h: 100dvh;
  }
  * { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
  html, body {
    margin: 0; height: 100%;
    background: radial-gradient(120% 95% at 50% 8%, var(--bg2) 0%, var(--bg) 50%, #dccfb2 100%);
    background-color: var(--bg);
    color: var(--text);
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", sans-serif;
    overscroll-behavior: none;
  }
  body { min-height: 100%; }
  h1 { margin: 0; font-size: 22px; letter-spacing: .5px; }

  #board {
    position: relative;
    display: grid;
    gap: 0;
    padding: 0;
    background: transparent;      /* void cells show the page → shape stands free, not a rectangle */
    touch-action: none;
    user-select: none; -webkit-user-select: none;
    margin: auto 0 6px;   /* top-auto pairs with .controls margin-top:auto → board centers, controls pin bottom */
    /* a light halo + deeper warm lift float the window OFF the darker-edged canvas (board ≠ background) */
    filter: drop-shadow(0 0 1px rgba(255,252,244,.7)) drop-shadow(0 16px 28px rgba(90,76,40,.28));
    transition: filter .3s;
    animation: glassBreath 8s ease-in-out infinite; /* whole window breathes very subtly */
  }
  @keyframes glassBreath {
    0%, 100% { filter: drop-shadow(0 0 1px rgba(255,252,244,.7)) drop-shadow(0 16px 28px rgba(90,76,40,.28)) brightness(1); }
    50%      { filter: drop-shadow(0 0 2px rgba(255,252,244,.85)) drop-shadow(0 18px 31px rgba(90,76,40,.32)) brightness(1.015); }
  }
  html[data-motion="reduced"] { #board { animation: none; } }
  /* stage entrance — board "assembles" on a new stage. FULL: cells pop in as a wave (per-cell delay set in
     JS via --cin). MINI: a quick whole-board fade for the fast in-loop stages. Never blocks input. */
  #board.stageIn .cell { animation: cellIn .26s cubic-bezier(.34,1.4,.5,1) both; animation-delay: var(--cin, 0ms); }
  @keyframes cellIn { 0% { opacity: 0; transform: scale(.5); } 100% { opacity: 1; transform: scale(1); } }
  #board.stageInMini { animation: boardInMini .18s ease-out both; }
  @keyframes boardInMini { 0% { opacity: .3; transform: scale(.985); } 100% { opacity: 1; transform: scale(1); } }
  html[data-motion="reduced"] { #board.stageIn .cell, #board.stageInMini { animation: none !important; } }
  /* lead "tubes": the leading (outer outline + interior dividers) is drawn as one SVG overlay in
     render() — rounded, glossy black rods with a top-left sheen + bottom-right shade, so each line
     reads as a raised 3D came. Inherits #board's url(#rough) wobble → stays hand-drawn. */
  .boardFrameLayer { position: absolute; left: 0; top: 0; overflow: visible; pointer-events: none; z-index: 1; }
  .leadLayer { position: absolute; left: 0; top: 0; overflow: visible; pointer-events: none; z-index: 3; }
  /* each cell is a shard of glass; the leading between/around shards is the SVG .leadLayer above */
  .cell {
    position: relative;
    background: var(--glass);
    display: flex; align-items: center; justify-content: center;
    font-size: 20px; font-weight: 800;
    color: var(--text);   /* clue on an empty (light) cell reads dark; filled jewel cells go white below */
  }
  /* empty cell = an "off" glass tile: top-left sheen + recessed inner shadow so it reads as glass,
     not flat paper, BEFORE any jewel is painted (fixes the bland first-impression on an empty board) */
  .cell.unfilled {
    background: linear-gradient(157deg, rgba(255,255,255,.55), rgba(255,255,255,0) 46%), var(--glass);
    box-shadow: inset 0 1.5px 0 rgba(255,255,255,.6), inset 0 -4px 7px rgba(120,98,54,.18), inset 0 0 0 1px rgba(120,98,54,.05);
  }
  .cell.void { background: transparent; } /* outside the glass shape → shows the lead field behind */
  /* stained-glass pattern: grayscale fractal-noise (cloudy cathedral glass) overlaid on each FILLED
     shard — empty cells stay clean for the Quiet Canvas look (the texture only reads well over a
     saturated/painted shard, not on light beige). --bx/--by keeps the cloud continuous across a
     region; per-tile vars (set in render) drift each shard independently. */
  .cell.tex { overflow: hidden; }
  .cell.tex::before {
    content: ""; position: absolute; inset: -18px; pointer-events: none;
    background-image: url("../../assets/common/glass_facets.png");
    background-size: 256px 256px; background-repeat: repeat;
    background-position: var(--bx, 0px) var(--by, 0px);
    mix-blend-mode: soft-light;   /* VORONOI cracked-glass mosaic (tileable, tools/gen-glass-facets.py): irregular polygon CELLS with darker crack edges, soft-light onto the shard → same-hue glass-piece facets. NOT a smooth crumple (that read as foil/cloth) and NOT directional bands (a monotonous river). */
    opacity: 1;
    /* static texture: the per-tile drift animation was removed for perf (an infinite per-cell transform
       promoted EVERY filled cell to its own compositor layer → idle jank). The reams still read as glass. */
  }
  .cell.unfilled::before { content: none; }   /* base boards keep empty cells clean; the game board overrides this with a very quiet idle-glass texture */
  @keyframes glassWanderA { 0%,100% { transform: translate(0,0); } 25% { transform: translate(-11px,8px); } 50% { transform: translate(9px,11px); } 75% { transform: translate(11px,-8px); } }
  @keyframes glassWanderB { 0%,100% { transform: translate(0,0); } 33% { transform: translate(10px,9px); } 66% { transform: translate(-9px,10px); } }
  @keyframes glassWanderC { 0%,100% { transform: translate(0,0); } 25% { transform: translate(8px,-11px); } 50% { transform: translate(-11px,-7px); } 75% { transform: translate(-8px,10px); } }
  @keyframes glassWanderD { 0%,100% { transform: translate(0,0); } 30% { transform: translate(-10px,-9px); } 60% { transform: translate(11px,-6px); } }
  html[data-motion="reduced"] { .cell.tex::before { animation: none; } }
  .cell .clue { position: relative; pointer-events: none; text-shadow: 0 1px 2px rgba(0,0,0,.22); }
  /* white reads on saturated jewels; top gloss + bottom depth makes each filled shard read as a gem */
  .cell.s1,.cell.s2,.cell.s3,.cell.s4,.cell.s5,.cell.sX { color: #fff; box-shadow: inset 0 1px 0 rgba(255,255,255,.30), inset 0 -5px 9px rgba(0,0,0,.15); }  /* subtle gem top-light + bottom depth only — keep it light so a multi-cell region reads as ONE flowing pane (region edges are framed by the lead, not per-cell) */
  .cell.s1{background:var(--s1)} .cell.s2{background:var(--s2)} .cell.s3{background:var(--s3)}
  .cell.s4{background:var(--s4)} .cell.s5{background:var(--s5)} .cell.sX{background:var(--sX)}
  .cell.locked { filter: brightness(1.04) saturate(1.42); }  /* richer jewels; brightness stays low so the colour-density variation reads */
  /* a region just SEALED (matched an allowed shape): one-shot gem flash + green glow (in-play reward) */
  .cell.sealflash { animation: sealShine .55s ease-out; }
  @keyframes sealShine {
    0%   { filter: brightness(1.16) saturate(1.2); }
    28%  { filter: brightness(1.95) saturate(1.3) drop-shadow(0 0 9px rgba(120,255,180,.95)); }
    100% { filter: brightness(1.16) saturate(1.2) drop-shadow(0 0 0 rgba(120,255,180,0)); }
  }
  /* the matched-shape palette swatch celebrates the moment a region completes it (replaces num's clue-pop) */
  .swatchPop { animation: swatchPop .95s cubic-bezier(.2,1.5,.4,1); transform-origin: center; }
  @keyframes swatchPop {
    0%   { transform: scale(1); }
    25%  { transform: scale(1.45); filter: brightness(1.6) drop-shadow(0 0 10px rgba(110,255,170,.9)); }
    60%  { transform: scale(1.18); filter: brightness(1.3) drop-shadow(0 0 6px rgba(110,255,170,.55)); }
    100% { transform: scale(1); filter: none; }
  }
  /* a region is closed off but isn't an allowed shape (can't grow → definitively wrong): ruby-tinted
     tile + a gentle breathing pulse so it reads as "fix me" without color alone (also a status line). */
  .cell.err { box-shadow: inset 0 0 0 99px rgba(206,52,62,.30), inset 0 1px 0 rgba(255,255,255,.18), inset 0 -5px 9px rgba(0,0,0,.16); animation: errBreathe 1.5s ease-in-out infinite; }
  @keyframes errBreathe { 0%,100% { filter: brightness(1) } 50% { filter: brightness(1.12) saturate(1.15) } }
  .cell.errflash { animation: errShake .42s ease-out; }
  @keyframes errShake {
    0%   { filter: brightness(1.3) drop-shadow(0 0 8px rgba(226,59,70,.9)); transform: translateX(0); }
    20%  { transform: translateX(-2px); } 45% { transform: translateX(2px); } 70% { transform: translateX(-1px); }
    100% { filter: brightness(1) drop-shadow(0 0 0 rgba(226,59,70,0)); transform: translateX(0); }
  }
  /* hint reveal: the matching palette icon glows gold to connect board outline → palette reference */
  .swatchHint { animation: swatchHint 1.8s ease-out forwards; transform-origin: center; }
  @keyframes swatchHint {
    0%   { filter: none; }
    20%  { filter: drop-shadow(0 0 7px #ffd23a) brightness(1.5); transform: scale(1.15); }
    60%  { filter: drop-shadow(0 0 4px rgba(255,210,58,.5)) brightness(1.2); transform: scale(1.05); }
    100% { filter: none; transform: scale(1); }
  }
  html[data-motion="reduced"] { .cell.sealflash, .swatchPop, .swatchHint, .cell.err, .cell.errflash { animation: none !important; } }