/* The site's two fonts, in one place: every page gets them through this file, so a page
   links only site.css (CLAUDE.md rule 4). A game never links Google Fonts itself. */
@import url("https://fonts.googleapis.com/css2?family=Fredoka:wght@400;500;600;700&family=Nunito:wght@400;600;700;800&display=swap");
/* Shared styles for the games hub (matches the in-game design system). */
:root{
  --sky-top:#A8E0FF; --sky-bot:#D9F6E4; --ink:#33236B; --ink-soft:#6a5aa0;
  --grape:#6C4AB6; --grape-dark:#553a95; --sun:#FFCF43; --sun-dark:#e6a800;
  --coral:#FF6B7A; --coral-dark:#e24a5c; --leaf:#2FB37D; --leaf-dark:#218a60;
  --sky:#3FA7E0; --sky-dark:#2b86ba; --card:#FFFFFF; --key-line:#E4DCF5;
  --paper:#FBF3E4; --paper-line:#EAD9BC;
  /* the board games' shared board (see the GAME BOARD section at the end) */
  --board-frame:#ECE4FB; --board-frame-lo:#DFD4F6; --board-back:#F6F2FF; --board-line:#D3C6F0;
  --board-rim:0 0 0 3px var(--key-line), 0 7px 0 3px #D9D0EF;
  --shadow:0 18px 40px rgba(72,45,130,.18); --radius:26px;
  /* The three page widths. A game picks a TIER (.app{--app-w:var(--app-wide)}), never a
     pixel of its own — that is what kept 36 games at 10 different widths. */
  --app-narrow:520px; --app:600px; --app-wide:720px;
}
*{box-sizing:border-box;} html,body{margin:0;padding:0;}
/* Mobile-first baseline. text-size-adjust stops iOS inflating text in landscape; the sizing
   tokens below are the ONLY page widths a game may pick from (CLAUDE.md "Standard layout"). */
html{-webkit-text-size-adjust:100%; text-size-adjust:100%;}
body{font-family:'Nunito',system-ui,sans-serif; color:var(--ink); min-height:100vh; min-height:100dvh;
  background:linear-gradient(180deg,var(--sky-top) 0%,#C6ECEC 45%,var(--sky-bot) 100%); background-attachment:fixed;
  padding:26px 16px 70px;}
body::before,body::after{content:""; position:fixed; z-index:0; border-radius:50%; background:rgba(255,255,255,.55); filter:blur(1px);
  box-shadow:60px 10px 0 -6px rgba(255,255,255,.55), 120px 4px 0 -10px rgba(255,255,255,.5); pointer-events:none;}
body::before{width:90px;height:34px;top:6%;left:6%;} body::after{width:70px;height:26px;top:12%;right:10%;}

.wrap{position:relative; z-index:1; max-width:1080px; margin:0 auto;}
.masthead{display:flex; align-items:center; gap:16px; justify-content:center; text-align:center; flex-direction:column; margin-bottom:6px;}
.brandline{display:flex; align-items:center; gap:14px; justify-content:center;}
.hub-owl{width:64px; height:64px; flex:0 0 auto;}
.sitetitle{font-family:'Fredoka',sans-serif; font-weight:700; font-size:clamp(30px,6vw,52px); line-height:1; margin:0;}
.sitetitle .c1{color:var(--grape);} .sitetitle .c2{color:var(--coral);} .sitetitle .c3{color:var(--leaf);} .sitetitle .c4{color:var(--sun-dark);} .sitetitle .c5{color:var(--sky);}
.tagline{color:var(--ink-soft); font-weight:700; font-size:clamp(15px,2.6vw,19px); margin:10px 0 0;}

.toolbar{display:flex; gap:10px; align-items:center; justify-content:center; flex-wrap:wrap; margin:22px 0 6px;}
.search{font-family:'Nunito',sans-serif; font-weight:700; font-size:16px; color:var(--ink);
  border:3px solid var(--key-line); background:#fff; border-radius:16px; padding:12px 18px; width:min(420px,90vw); outline:none;}
.search:focus{border-color:var(--grape);}
.count{font-family:'Fredoka',sans-serif; font-weight:600; color:var(--ink-soft); font-size:14px;}

.age-filter{display:flex; gap:8px; align-items:center; justify-content:center; flex-wrap:wrap; margin:0 0 18px;}
.age-chip{font-family:'Fredoka',sans-serif; font-weight:600; font-size:13.5px; color:var(--ink-soft);
  background:#fff; border:2px solid var(--key-line); border-radius:999px; padding:7px 16px; cursor:pointer;
  transition:background .12s ease,color .12s ease,border-color .12s ease;}
.age-chip:hover{border-color:var(--grape);}
.age-chip[aria-pressed="true"]{background:var(--grape); color:#fff; border-color:var(--grape);}
.age-chip:focus-visible{outline:3px solid var(--grape); outline-offset:2px;}

.grid{display:grid; grid-template-columns:repeat(auto-fill,minmax(240px,1fr)); gap:20px; margin-top:22px;}
.game-card{position:relative; display:flex; flex-direction:column; gap:10px; text-decoration:none; color:var(--ink);
  background:var(--card); border-radius:22px; padding:22px 20px 20px; box-shadow:var(--shadow);
  border:3px solid #fff; transition:transform .14s ease, box-shadow .14s ease, border-color .14s ease; overflow:hidden;}
.game-card::before{content:""; position:absolute; inset:0 0 auto 0; height:8px; background:var(--acc,var(--grape));}
.game-card:hover,.game-card:focus-visible{transform:translateY(-5px); box-shadow:0 26px 50px rgba(72,45,130,.24); border-color:var(--acc,var(--grape)); outline:none;}
.game-emoji{font-size:46px; line-height:1; margin-top:6px;}
.game-title{font-family:'Fredoka',sans-serif; font-weight:600; font-size:22px; line-height:1.1;
  display:flex; align-items:center; gap:8px; flex-wrap:wrap;}
.game-tag{color:var(--ink-soft); font-weight:700; font-size:14.5px; line-height:1.4; flex:1;}
.skills{display:flex; flex-wrap:wrap; gap:6px; margin-top:2px;}
.skill{font-family:'Fredoka',sans-serif; font-weight:600; font-size:11.5px; color:var(--grape); background:#F1EDFA;
  padding:4px 10px; border-radius:999px;}
.play{align-self:flex-start; margin-top:4px; font-family:'Fredoka',sans-serif; font-weight:600; font-size:15px;
  color:#fff; background:var(--acc,var(--grape)); border-radius:12px; padding:9px 18px;}
.badge{font-family:'Fredoka',sans-serif; font-weight:700; font-size:11px; color:#fff; line-height:1;
  background:var(--coral); padding:5px 10px; border-radius:999px; box-shadow:0 4px 10px rgba(226,74,92,.35);}
.age-badge{position:absolute; top:14px; right:14px; font-family:'Fredoka',sans-serif; font-weight:700; font-size:11px;
  color:var(--ink-soft); background:#fff; padding:4px 10px; border-radius:999px; box-shadow:0 2px 6px rgba(51,35,107,.12);}

.acc-grape{--acc:var(--grape);} .acc-coral{--acc:var(--coral);} .acc-leaf{--acc:var(--leaf);} .acc-sun{--acc:var(--sun-dark);} .acc-sky{--acc:var(--sky);}

.empty{text-align:center; color:var(--ink-soft); font-weight:700; padding:40px 10px;}
.foot{text-align:center; color:var(--ink-soft); font-weight:700; font-size:13.5px; margin-top:40px;}

@media (prefers-reduced-motion: reduce){*{animation:none !important; transition:none !important;}}

/* ============================================================
   SHARED STYLES FOR INDIVIDUAL GAME PAGES
   Linked by every games/<slug>/index.html:
     <link rel="stylesheet" href="../../assets/site.css" />
   <body class="game"> opts into the game-page layout below.
   Each game's own inline <style> keeps only what's actually
   unique to it: extra CSS vars for its own theme colors,
   .app{max-width}, .title{font-size}, .topbar{margin-bottom},
   and any game-specific components (e.g. a clock face, a pizza
   slice picker). Everything here is the shared look-and-feel and
   the required control scheme (topbar / tlink / level-switch /
   actions / serve-row) — see CLAUDE.md's "Standard layout &
   controls" section.
   ============================================================ */
/* touch-action:manipulation keeps pinch-zoom (an accessibility need) but drops DOUBLE-TAP
   zoom, which is what turned a fast or mistimed tap into a zoom — and it also removes the
   ~300ms delay the browser spent waiting for that second tap. Never user-scalable=no: that
   fails WCAG 1.4.4 and iOS has ignored it since iOS 10. */
.game{display:flex; align-items:flex-start; justify-content:center;
  padding:clamp(16px,2.6vw,22px) clamp(10px,2.6vw,14px) 60px;
  touch-action:manipulation;}
.game::before{top:8%; left:6%;} .game::after{top:16%; right:10%;}

/* Mobile-first: width:100% is what applies on a phone — max-width never binds below the
   tier, so the card always fills the screen. A game overrides only --app-w, never max-width. */
.app{position:relative; z-index:1; width:100%; max-width:var(--app-w,var(--app)); margin:0 auto;}
/* The card is the sizing context for everything inside it: a game sizes its board and type in
   cqw/cqi (the card's content box), never vw (the viewport), which is what used to overflow on
   a phone — the viewport includes page and card padding the content never gets. */
.card{container:card / inline-size;
  background:var(--card); border-radius:var(--radius); box-shadow:var(--shadow);
  padding:clamp(14px,3.4vw,26px);}
.brand{display:flex; align-items:center; gap:14px; justify-content:center; margin-bottom:4px;}
.title{font-family:'Fredoka',sans-serif; font-weight:700; line-height:1; margin:0; text-align:center;
  font-size:clamp(24px,8cqi,42px);}  /* scales with the CARD, so a wide game gets a bigger title */
.title .c1{color:var(--grape);} .title .c2{color:var(--coral);} .title .c3{color:var(--leaf);} .title .c4{color:var(--sun-dark);}
.subtitle{text-align:center; color:var(--ink-soft); font-weight:700; margin:2px 0 18px; font-size:15px;}
/* the game's card emoji, filled in from /games.js by site.js's applyGameText() — a page never
   writes the glyph itself, so the hub card and the game can't drift apart */
.gemoji{font-style:normal;}

.home-top{display:flex; margin-bottom:4px;}
.hub-link{font-family:'Fredoka',sans-serif; font-weight:600; font-size:14px; color:var(--grape); text-decoration:none; opacity:.8;}
.hub-link:hover{opacity:1;}

/* owl mascot (mood states: idle / think / worried / happy / win) */
.owl{width:60px; height:60px; flex:0 0 auto;}
.owl .pupil{transition:transform .2s ease;}
.owl .brow{transition:transform .2s ease; transform-origin:center;}
.owl.think .pupil{transform:translateY(2px);}
.owl.worried .brow-l{transform:rotate(14deg) translateY(-1px);}
.owl.worried .brow-r{transform:rotate(-14deg) translateY(-1px);}
.owl.happy .beak,.owl.win .beak{transform:translateY(1px) scale(1.05);}

/* monster mascot (color-match, math-monsters): a dynamically-recolored SVG
   character with mood states idle / sad / chomp. Each game still supplies its
   own monsterSVG() markup and svg.monster{width} sizing. */
.stagewrap{display:flex; flex-direction:column; align-items:center; margin-bottom:4px;}
.monster-name{font-family:'Fredoka',sans-serif; font-weight:600; color:var(--ink-soft); font-size:13px; margin-top:2px;}
svg.monster.mini{width:100%; height:auto; display:block;}
.monster .pupil{transition:transform .18s ease;}
.monster.sad .pupil{transform:translateY(-2px);}
.monster.chomp .pupil{transform:scale(1.12);}
.monster.chomp{animation:chompbounce .4s ease;}
.monster.sad{animation:sadwiggle .5s ease;}
@keyframes chompbounce{0%{transform:scale(1)}35%{transform:scale(1.14,.9)}70%{transform:scale(.94,1.08)}100%{transform:scale(1)}}
@keyframes sadwiggle{0%,100%{transform:rotate(0)}25%{transform:rotate(-6deg)}75%{transform:rotate(6deg)}}
.belly-fill{transition:transform .5s cubic-bezier(.2,1.1,.4,1);}
.diff .mini-slot{display:block; margin:0 auto 6px;}
.diff .mini-slot svg{width:100%; height:100%; display:block;}

/* buttons */
.btn{font-family:'Fredoka',sans-serif; font-weight:600; font-size:17px; border:none; cursor:pointer;
  color:#fff; border-radius:16px; padding:13px 22px; position:relative;
  transition:transform .06s ease, filter .15s ease; box-shadow:0 5px 0 var(--btn-shadow,#0003); user-select:none;}
.btn:active:not(:disabled){transform:translateY(4px); box-shadow:0 1px 0 var(--btn-shadow,#0003);}
.btn:focus-visible{outline:none;}
.btn-primary{background:var(--grape); --btn-shadow:var(--grape-dark);}
.btn-go{background:var(--leaf); --btn-shadow:var(--leaf-dark);}
.btn-warn{background:var(--coral); --btn-shadow:var(--coral-dark);}
.btn-sun{background:var(--sun); color:var(--ink); --btn-shadow:var(--sun-dark);}
.btn-ghost{background:#F1EDFA; color:var(--grape); --btn-shadow:#d9d0ef;}
.btn:disabled{filter:grayscale(.4) opacity(.5); cursor:not-allowed;}
.btn-lg{font-size:20px; padding:16px 34px;}
.btn-sm{font-size:15px; padding:9px 15px; border-radius:13px;}
.btn.ready{background:var(--sun); color:var(--ink); --btn-shadow:var(--sun-dark); animation:rp .8s ease-in-out infinite;}
@keyframes rp{0%,100%{transform:scale(1)}50%{transform:scale(1.04)}}

/* level grid (choose-a-level cards on the start screen) */
.block-label{font-family:'Fredoka',sans-serif; font-weight:600; color:var(--grape); font-size:15px;
  text-transform:uppercase; letter-spacing:1px; margin:20px 0 10px;}
.diff-grid{display:grid; grid-template-columns:repeat(2,1fr); gap:12px;}
/* A game with THREE levels (the board games against the owl) adds .three, so the row fills
   instead of leaving a fourth, empty column. On a narrow screen the cards stay two-up and the
   third takes the full width below them, which reads as deliberate rather than half-finished. */
.diff-grid.three .diff:last-child{grid-column:span 2;}
/* Asks the CARD how much room it has, not the phone: a narrow game and a wide one used to
   disagree about how many level cards fit, because the query measured the viewport. */
@container card (min-width:460px){
  .diff-grid{grid-template-columns:repeat(4,1fr);}
  .diff-grid.three{grid-template-columns:repeat(3,1fr);}
  .diff-grid.three .diff:last-child{grid-column:auto;}
  /* A game with TWO levels (battleship: one sea, one fleet, two owls) keeps its cards
     half-and-half at every width — four columns would leave two empty. */
  .diff-grid.two{grid-template-columns:repeat(2,1fr);}
}
.diff{border:3px solid #EEE8FB; background:#fff; border-radius:18px; cursor:pointer; padding:14px 10px; text-align:center; transition:all .15s ease;}
.diff:focus-visible{outline:3px solid var(--grape); outline-offset:2px;}
/* Cards in a row always share one height (grid stretch), and their contents stack from the
   TOP — so every card's emoji + name sits on the same line and every hint starts on the same
   line, even when one hint wraps and its neighbours don't. (A button centres its content by
   default, which is what used to knock a wrapped card's name out of line.) */
.diff{container-type:inline-size; display:flex; flex-direction:column; align-items:center; justify-content:flex-start;}
.diff .d-name,.diff .d-range{width:100%;}
/* emoji + name always on ONE line, on every card: the name shrinks a little on a narrow card
   rather than wrapping ("⭐ Medium" is the longest, and wrapped first) */
.diff .d-name{font-family:'Fredoka',sans-serif; font-weight:600; font-size:19px; font-size:min(19px,18cqw); white-space:nowrap;}
.diff .d-range{font-size:12.5px; font-weight:700; color:var(--ink-soft); margin-top:3px;}
.diff[aria-pressed="true"]{border-color:var(--dc,var(--grape)); background:var(--dbg,#F4F0FE); transform:translateY(-2px); box-shadow:var(--shadow);}
.diff.easy{--dc:var(--leaf); --dbg:#E8F8F0;} .diff.medium{--dc:var(--sky); --dbg:#E6F4FC;}
.diff.hard{--dc:var(--sun-dark); --dbg:#FFF6DC;} .diff.expert{--dc:var(--coral); --dbg:#FFEAED;}
.diff.my{grid-column:1 / -1;}

/* ---------- numbered multiple-choice list ----------
   One full-width choice per row, each with a .num badge and .otxt label, going two-up once the
   card is wide enough. calendar-quest, what-am-i and guess-the-capital each carried their own
   byte-identical copy of this. A game resizes it with the four vars and adds nothing else.
   (The compact answer TILES in math-monsters / shape-math / sneak-peek / tally-chart are a
   different component that happens to share the .opt name — they are not .optlist.) */
.optlist{display:grid; grid-template-columns:1fr; gap:12px;}
@container card (min-width:460px){ .optlist{grid-template-columns:1fr 1fr;} }
.optlist .opt{border:3px solid var(--key-line); background:#fff; border-radius:16px; cursor:pointer;
  padding:var(--opt-pad,14px 14px); font-family:'Fredoka',sans-serif; font-weight:600;
  font-size:var(--opt-size,16px); color:var(--ink); text-align:left;
  display:flex; align-items:center; gap:12px; transition:all .12s ease;}
.optlist .opt:hover:not(:disabled){border-color:var(--grape); transform:translateY(-1px);}
.optlist .opt:focus-visible{outline:3px solid var(--grape); outline-offset:2px;}
.optlist .opt:disabled{cursor:default;}
.optlist .opt .num{font-family:'Fredoka',sans-serif; font-weight:600; background:#F1EDFA; color:var(--grape);
  width:var(--opt-num,28px); height:var(--opt-num,28px); border-radius:9px;
  display:flex; align-items:center; justify-content:center;
  font-size:var(--opt-numsize,14px); flex:0 0 auto;}
.optlist .opt .otxt{flex:1; line-height:1.3;}
.optlist .opt.correct{background:var(--leaf); border-color:var(--leaf-dark); color:#fff;}
.optlist .opt.correct .num{background:rgba(255,255,255,.3); color:#fff;}
.optlist .opt.wrong{background:#FFE1E6; border-color:var(--coral); color:var(--coral-dark);}
.optlist .opt.wrong .num{background:var(--coral); color:#fff;}
.optlist .opt.gone{background:#F2EEF9; border-color:#E0D8F0; color:#b7abd6; cursor:not-allowed; text-decoration:line-through;}
.optlist .opt.gone .num{background:#E0D8F0; color:#b7abd6;}
.optlist .opt.wrong-flash{animation:wrongShake .35s ease;}

/* row of small stat chips under the topbar (moves count, par/best, etc.) */
.stats-row{display:flex; gap:8px; justify-content:center; align-items:center; margin-bottom:14px; flex-wrap:wrap; min-height:1px;}

.howto{background:#F4F0FE; border-radius:16px; padding:14px 16px; margin-top:18px; font-weight:700; color:var(--ink-soft); font-size:14px; line-height:1.5;}
.howto b{color:var(--grape);}

/* tile grid (color-match, shape-sorter): a grid of tappable answer tiles.
   Each game still supplies its own .tile base rule (background/padding/
   layout differ) and .tile.found treatment. */
.tilegrid{display:grid; gap:14px; margin:6px 0 6px;}
.tile:hover{border-color:var(--grape);}
.tile:focus-visible{outline:3px solid var(--ink); outline-offset:3px;}
.tile.wrong-flash{animation:shaketile .35s ease;}
.tile.hintglow{animation:pulseglow 1.1s ease;}
/* the "not that one" wobble a wrong answer gets (games: animation:wrongShake .35s ease) */
@keyframes wrongShake{0%,100%{transform:translateX(0)}25%{transform:translateX(-7px)}75%{transform:translateX(7px)}}
@keyframes shaketile{0%,100%{transform:translateX(0)}25%{transform:translateX(-8px)}75%{transform:translateX(8px)}}
@keyframes pulseglow{0%,100%{box-shadow:0 0 0 0 rgba(255,207,67,0)}50%{box-shadow:0 0 0 8px rgba(255,207,67,.65)}}
.center{text-align:center;} .mt{margin-top:22px;}
.row-btns{display:flex; gap:12px; flex-wrap:wrap; justify-content:center;}

/* top bar: Home (left) / owl + level chip (centre) / Skip (right) */
.topbar{display:flex; align-items:center; justify-content:space-between; gap:10px;}
.topbar .tb-center{display:flex; align-items:center; gap:8px; min-width:0;}
.topbar .tb-right{display:flex; align-items:center; gap:8px;}
.chip{font-family:'Fredoka',sans-serif; font-weight:600; font-size:14px; color:#fff; background:var(--grape); padding:6px 14px; border-radius:999px;}
.chip.par{background:var(--sun); color:var(--ink);}  /* "par"/"best" goal chip: coin-counter, robot-instructions */

/* text-link controls: Home / Skip / Next / Reset / Hint */
.tlink{font-family:'Fredoka',sans-serif; font-weight:600; font-size:15px; color:var(--grape); background:none; border:none;
  cursor:pointer; text-decoration:none; padding:11px 10px; min-height:44px; display:inline-flex;
  align-items:center; justify-content:center; gap:5px; opacity:.82;}
.tlink:hover{opacity:1;} .tlink.off{opacity:.32; pointer-events:none;}
.tlink:focus-visible{outline:3px solid var(--grape); outline-offset:2px; border-radius:10px;}
.actions{display:flex; gap:10px; justify-content:center; align-items:center; margin:12px 0 18px; flex-wrap:wrap;}
.serve-row{display:flex; justify-content:center; margin-top:6px;}
.invisible{visibility:hidden !important;}  /* hidden but keeps its space, so the top bar doesn't reflow */
/* ...except in the actions row: a link a level or phase doesn't use takes NO space there, so the
   links that are left sit centred together instead of beside a gap (every game, one rule) */
.actions > .invisible{display:none !important;}

/* level chip switcher (dropdown) */
.level-switch{position:relative; display:inline-block;}
.chip.level{cursor:pointer; user-select:none; border:none; display:inline-flex; align-items:center; gap:5px;}
.chip.level:hover{filter:brightness(1.07);} .chip.level .caret{font-size:10px; opacity:.85;}
.chip.level:focus-visible{outline:3px solid var(--ink); outline-offset:2px;}
.level-menu{position:absolute; top:calc(100% + 8px); left:50%; transform:translateX(-50%); z-index:40;
  background:#fff; border:2px solid var(--key-line); border-radius:14px; box-shadow:var(--shadow); padding:6px; min-width:160px; display:none;}
.level-menu.open{display:block;}
.level-opt{display:flex; align-items:center; justify-content:space-between; gap:10px; width:100%; text-align:left;
  font-family:'Fredoka',sans-serif; font-weight:600; font-size:15px; color:var(--ink); background:none; border:none; cursor:pointer; padding:9px 12px; border-radius:10px; white-space:nowrap;}
.level-opt:hover{background:#F4F0FE;} .level-opt[aria-current="true"]{background:#F1EDFA; color:var(--grape);}
.level-opt .tick{color:var(--leaf-dark); font-weight:700; opacity:0;} .level-opt[aria-current="true"] .tick{opacity:1;}

/* win/result banner + feedback line under the game */
.result{text-align:center; padding:8px 4px 16px;}
.result .rlabel{font-family:'Fredoka',sans-serif; font-weight:700; font-size:clamp(17px,4vw,20px); color:var(--leaf-dark);}
/* the round's star rating (★★☆) — sunshine yellow, with a darker rim so it holds up on white.
   A filled ★ and an empty ☆ differ by SHAPE, not colour, so they can share this one colour. */
.result .stars{font-size:32px; letter-spacing:5px; margin:6px 0; color:var(--sun); -webkit-text-stroke:1px var(--sun-dark);}
.result .rsub{font-weight:700; color:var(--ink-soft); margin-top:6px; font-size:14px;}
.feedback{min-height:22px; text-align:center; font-family:'Nunito',sans-serif; font-weight:700; font-size:14px; margin:14px 0 12px;
  display:flex; align-items:center; justify-content:center;}
.feedback.good{color:var(--leaf-dark);} .feedback.bad{color:var(--coral-dark);} .feedback.hint{color:var(--sun-dark);}

/* ============================================================
   RULES SHEET — a scrollable "how to play" panel for the games
   whose rules need more room than the start screen's .howto
   block (tic-tac-toe, mystery-word,
   matchstick-math). A game links it from its start screen
   (a "📖 Read the full rules" .tlink) and from its .actions row
   during play (a "📖 Rules" .tlink), and supplies ONLY its own
   rules copy — plus any diagram CSS unique to that game.
   Everything here is the shared chrome; the open/close/Escape/
   focus handling is wireRulesSheet() in site.js.
   Named .sheet-ov, not .overlay, so it can't collide with a
   game's own full-screen overlay.
   ============================================================ */
.sheet-ov{position:fixed; inset:0; z-index:70; display:none; align-items:center; justify-content:center;
  padding:16px; background:rgba(51,35,107,.5); backdrop-filter:blur(3px);}
.sheet-ov.show{display:flex;}
.sheet{background:var(--card); border-radius:24px; box-shadow:var(--shadow); width:100%; max-width:480px;
  max-height:88vh; display:flex; flex-direction:column; overflow:hidden; animation:sheetPop .28s ease;}
@keyframes sheetPop{0%{transform:scale(.9); opacity:0}100%{transform:scale(1); opacity:1}}
.sheet-top{display:flex; align-items:center; justify-content:space-between; gap:10px;
  padding:16px 18px 12px; border-bottom:2px solid var(--key-line);}
.sheet-top h2{font-family:'Fredoka',sans-serif; font-weight:700; font-size:21px; margin:0; color:var(--grape);}
.sheet-x{font-family:'Fredoka',sans-serif; font-size:18px; line-height:1; color:var(--ink-soft);
  background:#F4F0FE; border:none; border-radius:50%; width:34px; height:34px; cursor:pointer; flex:0 0 auto;}
.sheet-x:hover{background:#E8E0FA; color:var(--ink);}
.sheet-x:focus-visible{outline:3px solid var(--grape); outline-offset:2px;}
.sheet-body{overflow-y:auto; padding:14px 18px 6px; -webkit-overflow-scrolling:touch;}
.sheet-foot{padding:12px 18px 16px; display:flex; justify-content:center; border-top:2px solid var(--key-line);}
.rule{margin-bottom:16px;}
.rule h3{font-family:'Fredoka',sans-serif; font-weight:600; font-size:16px; margin:0 0 5px; color:var(--ink);}
.rule p{font-weight:700; font-size:14px; line-height:1.5; color:var(--ink-soft); margin:0 0 6px;}
.rule p b{color:var(--grape);}
.rule .cap{display:block; text-align:center; font-size:12px; font-weight:700; color:var(--ink-soft); margin-top:6px;}
.rule .cap b{color:var(--grape);}
/* one lone noughts-and-crosses board, for the games whose rules need to show a
   three-in-a-row (tic-tac-toe). Cells are plain <i> elements:
   <div class="rsolo"><i>✕</i><i class="o">◯</i>...</div> */
.rsolo{display:grid; grid-template-columns:repeat(3,1fr); gap:2px; width:74px; flex:0 0 auto;
  background:#EDE7FA; border-radius:8px; padding:3px;}
.rsolo i{display:flex; align-items:center; justify-content:center; aspect-ratio:1/1; background:#fff;
  border-radius:4px; font-family:'Fredoka',sans-serif; font-style:normal; font-weight:700;
  font-size:17px; line-height:1; color:var(--coral);}
.rsolo i.o{color:var(--sky);}
.rsolo i.fade{border:2px dashed #CFC4EC;}   /* dashed, not just faint - never colour alone */
.rsolo i.fade span{opacity:.3;}

/* a row of little diagrams with arrows between them */
.rrow{display:flex; align-items:center; justify-content:center; gap:10px; flex-wrap:wrap; margin:8px 0 2px;}
.rarrow{font-size:20px; color:var(--grape); font-weight:800;}

/* ============================================================
   GAME BOARD — the board every board game shares, and the
   piece that stands on it. Change a board's look HERE and
   every board game changes with it; a game carries no copy.

   The piece (.piece) is used by all of them, on a holed board
   or a chequered one: the game gives it its box, site.css
   gives it its colours (yours coral, the owl's sun — .bird,
   because .owl is the mascot's class).

   The holed board (.gb-*) is Connect Four's lavender sheet
   with a hole punched for every square, reused by Gomoku and
   whatever comes next. buildBoard() in site.js writes the
   markup and sets --cols/--rows/--cell on the .gb-stage; every
   size below is worked out from those, and a game tunes it
   only through buildBoard's options (hole, inset, edge, head).
   Back to front: the back panel, the pieces, the frame (one
   sheet with the holes cut by a mask, so a falling piece slides
   BEHIND it), the holes' lips, the leaving-piece layer, the
   rim, the win line, then the game's own layer of buttons.
   ============================================================ */
.piece{position:relative; border-radius:50%;}
.piece::before{content:""; position:absolute; inset:23%; border-radius:50%;}
.piece.you{background:var(--coral);
  box-shadow:inset 0 calc(-1 * var(--piece-edge, 4px)) 0 var(--coral-dark), inset 0 3px 0 rgba(255,255,255,.3);}
.piece.you::before{box-shadow:inset 0 2px 0 rgba(120,0,30,.18), 0 1px 0 rgba(255,255,255,.35);}
.piece.bird{background:var(--sun);
  box-shadow:inset 0 calc(-1 * var(--piece-edge, 4px)) 0 var(--sun-dark), inset 0 3px 0 rgba(255,255,255,.4);}
.piece.bird::before{box-shadow:inset 0 2px 0 rgba(140,90,0,.18), 0 1px 0 rgba(255,255,255,.45);}

/* --pad stays under half a cell minus a hole, so no part-hole shows in the frame's border */
.gb-stage{--pad:calc(var(--cell) * .15); --hole:calc(var(--cell) * .42); --piece-inset:10%; --piece-edge:4px;}
.gb-rig{position:relative; margin:var(--head, 0px) auto 0; padding-bottom:8px;
  width:calc(var(--cols) * var(--cell) + 2 * var(--pad));}
.gb-board{position:relative; height:calc(var(--rows) * var(--cell) + 2 * var(--pad));}
.gb-layer{position:absolute; left:var(--pad); top:var(--pad);
  width:calc(var(--cols) * var(--cell)); height:calc(var(--rows) * var(--cell));}
.gb-slot{position:absolute; width:var(--cell); height:var(--cell);}
.gb-back{background:radial-gradient(circle at 50% 50%, #fff calc(var(--hole) * .78), var(--board-back) var(--hole))
  0 0 / var(--cell) var(--cell);}
.gb-frame{position:absolute; inset:0; border-radius:22px; pointer-events:none;
  background:linear-gradient(180deg, var(--board-frame), var(--board-frame-lo));
  -webkit-mask:radial-gradient(circle at 50% 50%, transparent var(--hole), #000 calc(var(--hole) + .5px)) var(--pad) var(--pad) / var(--cell) var(--cell);
          mask:radial-gradient(circle at 50% 50%, transparent var(--hole), #000 calc(var(--hole) + .5px)) var(--pad) var(--pad) / var(--cell) var(--cell);}
/* each hole is ringed in a lavender line */
.gb-lips{pointer-events:none;
  background:radial-gradient(circle at 50% 50%, transparent calc(var(--hole) - .5px), var(--board-line) var(--hole),
    var(--board-line) calc(var(--hole) + 2px), transparent calc(var(--hole) + 2.5px)) 0 0 / var(--cell) var(--cell);}
.gb-rim{position:absolute; inset:0; border-radius:22px; pointer-events:none; box-shadow:var(--board-rim);}
.gb-fx{pointer-events:none; z-index:3;}        /* pieces on their way off the board */

.gb-slot .piece{position:absolute; inset:var(--piece-inset);}
/* the piece just played wears a white dot, so the owl's reply is easy to spot */
.gb-slot.last .piece::after{content:""; position:absolute; inset:36%; border-radius:50%; background:rgba(255,255,255,.85);}
.gb-slot.win{animation:gbWin 1s ease-in-out .7s infinite;}
@keyframes gbWin{50%{transform:scale(1.08);}}
/* dropped down a column (--fall cells, --dur long), slid down after a pop, placed on an empty
   square, or taken off the board */
.gb-slot .piece.drop{animation:gbDrop var(--dur, .55s) both;}
@keyframes gbDrop{
  0%{transform:translateY(calc(-1 * (var(--fall) * var(--cell) + var(--pad) + 4px))); animation-timing-function:cubic-bezier(.55,0,1,.9);}
  70%{transform:translateY(0); animation-timing-function:cubic-bezier(0,0,.4,1);}
  85%{transform:translateY(-10%); animation-timing-function:cubic-bezier(.6,0,1,1);}
  100%{transform:translateY(0);}}
.gb-slot .piece.place{animation:gbPlace .26s cubic-bezier(.2,1.5,.5,1) both;}
@keyframes gbPlace{0%{transform:scale(.2); opacity:.35} 60%{transform:scale(1.13)} 100%{transform:scale(1)}}
.gb-slot .piece.gone{animation:gbGone .45s ease-out both;}
@keyframes gbGone{0%{transform:scale(1)} 35%{transform:scale(1.22)} 100%{transform:scale(.15) rotate(30deg); opacity:0}}

/* the line drawn through a winning row, over the frame (the board's viewBox is
   --cols x --rows, one unit per cell) — drawWinLine() in site.js fills it */
.gb-line{position:absolute; left:var(--pad); top:var(--pad); z-index:4; overflow:visible; pointer-events:none;
  width:calc(var(--cols) * var(--cell)); height:calc(var(--rows) * var(--cell));}
.gb-line line{stroke-linecap:round; stroke-dasharray:1; animation:gbLine .55s ease-out .2s both;}
.gb-line .wl-back{stroke:#fff; stroke-width:.3;}
.gb-line .wl-front{stroke:var(--grape); stroke-width:.16;}
.gb-line circle{fill:#fff; stroke:var(--grape); stroke-width:.06; transform-box:fill-box; transform-origin:center;
  animation:gbDot .25s ease-out .65s both;}
@keyframes gbLine{from{stroke-dashoffset:1;}}
@keyframes gbDot{from{transform:scale(0);}}

canvas#confetti{position:fixed; inset:0; z-index:60; pointer-events:none; display:none;}
.hide{display:none !important;}
