/* Self-hosted. No third-party font request from any page — see build.py HEAD. */
@font-face{font-family:"Baloo 2";src:url(/fonts/baloo2-800.woff2) format("woff2");
  font-weight:500 800;font-style:normal;font-display:swap}
@font-face{font-family:"Nunito Sans";src:url(/fonts/nunitosans-400.woff2) format("woff2");
  font-weight:400 700;font-style:normal;font-display:swap}

/* Happen Labs — happenlabs.com
   Design system v2 (A2 "toybox"). Tokens mirror the published system;
   change them there first, then here. */

:root{
  /* colour */
  --paper:#FFFDF7; --raised:#FFFFFF;
  --ink:#2A2438; --muted:#6B6480; --rule:#E4DFD2; --focus:#3A6EA8;
  --game-ink:#2A2438;
  /* Game colours are sampled from each title's app icon, so the site matches
     the store. Each game declares its own text colour: a saturated brand colour
     cannot be assumed to take dark text. CI asserts each pair clears 4.5:1. */
  --moodoku:#009CFC;  --moodoku-ink:#2A2438;   /* 5.1:1 */
  --jigsawed:#CC009C; --jigsawed-ink:#FFFFFF;  /* 5.2:1 */
  --held-a:#A98EE0;   --held-a-ink:#2A2438;    /* 5.5:1 */
  --held-b:#86CC57;   --held-b-ink:#2A2438;    /* 7.9:1 */
  --vpd:#7DAEFF;      --vpd-ink:#2A2438;       /* 6.7:1 */
  --deuces:#FF9EC4;   --deuces-ink:#2A2438;    /* 7.8:1 */
  /* spacing */
  --s1:4px; --s2:8px; --s3:16px; --s4:24px; --s5:40px; --s6:64px;
  /* shape */
  --r-card:22px; --r-control:11px; --r-pill:999px;
  /* type */
  --display:"Baloo 2", system-ui, -apple-system, sans-serif;
  --body:"Nunito Sans", system-ui, -apple-system, "Segoe UI", sans-serif;

  box-sizing:border-box;
  padding-top:env(safe-area-inset-top,0px);
  padding-bottom:env(safe-area-inset-bottom,0px);
}
@media (prefers-color-scheme: dark){
  :root:not([data-theme="light"]){
    --paper:#211D2B; --raised:#2B2638; --ink:#F6F2EA;
    --muted:#A79FBC; --rule:#3A3348; --focus:#9CC0EA;
  }
}
:root[data-theme="dark"]{
  --paper:#211D2B; --raised:#2B2638; --ink:#F6F2EA;
  --muted:#A79FBC; --rule:#3A3348; --focus:#9CC0EA;
}

*,*::before,*::after{box-sizing:inherit}
html{scroll-padding-top:env(safe-area-inset-top,0px); -webkit-text-size-adjust:100%}
body{margin:0; background:var(--paper); color:var(--ink);
  font-family:var(--body); font-size:16px; line-height:26px}
a{color:inherit}
a:focus-visible,summary:focus-visible{outline:3px solid var(--focus); outline-offset:3px}
img{max-width:100%; height:auto}
.wrap{max-width:1180px; margin:0 auto; padding:0 var(--s5)}

/* ---------- header ---------- */
.top{display:flex; align-items:center; justify-content:space-between;
  gap:var(--s4); padding:var(--s4) 0 var(--s5); flex-wrap:wrap}
.mark{font-family:var(--display); font-weight:800; font-size:26px;
  letter-spacing:-0.01em; text-decoration:none}
nav{display:flex; align-items:center; gap:var(--s4); font-weight:600; font-size:15px}
nav > a{text-decoration:none; color:var(--muted)}
nav > a:hover, nav > a[aria-current]{color:var(--ink)}
details.menu{position:relative}
details.menu summary{list-style:none; cursor:pointer; color:var(--ink); font-weight:600}
details.menu summary::-webkit-details-marker{display:none}
details.menu summary::after{content:" \25BE"}
details.menu[open] summary{color:var(--ink)}
details.menu ul{position:absolute; right:0; top:calc(100% + 10px); margin:0; padding:var(--s2);
  list-style:none; background:var(--raised); border:3px solid var(--ink);
  border-radius:var(--r-card); box-shadow:5px 5px 0 var(--ink); min-width:240px; z-index:20}
details.menu li a{display:block; padding:var(--s2) var(--s3);
  border-radius:var(--r-control); color:var(--ink); text-decoration:none}
details.menu li a:hover{background:var(--gold)}
details.menu .grouplabel{padding:var(--s2) var(--s3) var(--s1); font-size:11px; font-weight:700;
  letter-spacing:.07em; text-transform:uppercase; color:var(--muted)}
details.menu hr{border:none; border-top:2px solid var(--rule); margin:var(--s2) var(--s3)}

/* ---------- headings & prose ---------- */
h1{font-family:var(--display); font-weight:800; font-size:clamp(36px,5.2vw,52px);
  line-height:1.08; margin:0 0 var(--s4); letter-spacing:-0.015em}
h2{font-family:var(--display); font-weight:700; font-size:24px; margin:0 0 var(--s4)}
h3{font-family:var(--display); font-weight:700; font-size:19px; margin:var(--s4) 0 var(--s1)}
.hero{max-width:54ch; padding-bottom:var(--s6)}
.hero p{margin:0 0 var(--s3); font-size:19px; line-height:30px}
.hero p:last-child{margin-bottom:0; color:var(--muted)}
.body{max-width:64ch}
.body p{margin:0 0 var(--s3)}
.body p:last-child{margin-bottom:0}
.lede{font-size:19px; line-height:30px; font-weight:600}
.note{font-size:13px; color:var(--muted); font-style:italic}
main section{padding-bottom:var(--s6)}
ul.plain{max-width:64ch; padding-left:var(--s4); margin:0 0 var(--s3)}
ul.plain li{margin-bottom:var(--s2)}
dl{margin:0; max-width:64ch}
dt{font-family:var(--display); font-weight:700; font-size:19px; margin:var(--s4) 0 var(--s1)}
dt:first-of-type{margin-top:0}
dd{margin:0; color:var(--muted)}

/* ---------- cards ---------- */
.games{display:grid; grid-template-columns:1fr 1fr; gap:var(--s4);
  list-style:none; margin:0 0 var(--s6); padding:0}
.card{display:flex; flex-direction:column; background:var(--raised);
  border:3px solid var(--ink); border-radius:var(--r-card);
  box-shadow:6px 6px 0 var(--ink); overflow:hidden; position:relative;
  transition:transform 110ms ease, box-shadow 110ms ease}
.card:hover{transform:translate(-2px,-2px); box-shadow:9px 9px 0 var(--ink)}
.card.held{border-color:var(--rule); box-shadow:6px 6px 0 var(--rule)}
.card.held:hover{transform:none; box-shadow:6px 6px 0 var(--rule)}
/* odd count -> first card spans both columns */
.card:first-child:nth-last-child(odd){grid-column:span 2}
/* aspect-ratio, not a fixed height: with a fixed height the card art
   stretched to 4.83:1 in single-column at the 820px breakpoint while
   being 2.86:1 on desktop, so art could not be sourced to one ratio. */
.art{aspect-ratio:5/2; border-bottom:3px solid var(--ink); position:relative;
  display:flex; align-items:center; justify-content:center}
.art img{width:132px; height:132px; border-radius:26px; position:relative;
  box-shadow:0 3px 0 rgba(42,36,56,.28)}
.card.held .art{border-bottom-color:var(--rule)}
.art::after{content:""; position:absolute; inset:0; opacity:.16;
  background-image:linear-gradient(to right,#2A2438 1.5px,transparent 1.5px),
                   linear-gradient(to bottom,#2A2438 1.5px,transparent 1.5px);
  background-size:40px 40px}
.cbody{padding:var(--s4); display:flex; flex-direction:column; gap:var(--s2); flex:1}
.chip{align-self:flex-start; background:rgba(42,36,56,.1); color:var(--muted);
  border-radius:var(--r-pill); padding:3px 11px; font-size:11px; font-weight:700;
  letter-spacing:.07em; text-transform:uppercase}
.name{font-family:var(--display); font-weight:800; font-size:30px; line-height:1.1; margin:0}
.name a{text-decoration:none}
.name a:hover{text-decoration:underline; text-underline-offset:4px}
.desc{margin:0; color:var(--muted); flex:1}

/* ---------- badges ---------- */
.badges{display:flex; gap:var(--s2); flex-wrap:wrap; margin-top:var(--s2)}
.badge{display:inline-block; background:var(--ink); color:var(--paper);
  border-radius:var(--r-control); padding:9px 15px; font-size:12px;
  font-weight:700; text-decoration:none; line-height:1.35}
.badge small{display:block; font-size:9px; font-weight:600; opacity:.7; letter-spacing:.04em}
.badge:hover{filter:brightness(1.35)}
.badge.off{background:transparent; color:var(--muted); border:2px dashed var(--rule)}
.badge.lg{padding:12px 22px; font-size:15px}
.badge.lg small{font-size:10px}

/* ---------- game page hero ---------- */
.herocard{color:var(--game-ink); border:3px solid var(--game-ink);
  border-radius:var(--r-card); box-shadow:8px 8px 0 var(--game-ink);
  padding:var(--s6) var(--s5); margin-bottom:var(--s6); position:relative; overflow:hidden}
.herocard::before{content:""; position:absolute; inset:0; opacity:.14; pointer-events:none;
  background-image:linear-gradient(to right,#2A2438 1.5px,transparent 1.5px),
                   linear-gradient(to bottom,#2A2438 1.5px,transparent 1.5px);
  background-size:40px 40px}
.herocard > *{position:relative}
.herocard .chip{display:inline-block; background:rgba(42,36,56,.16);
  color:var(--game-ink); margin-bottom:var(--s3)}
.herocard h1{font-size:clamp(48px,9vw,104px); line-height:.98;
  margin:0 0 var(--s3); letter-spacing:-0.025em}
.herocard .badge{background:#2A2438; color:#FFFDF7}
.tagline{margin:0 0 var(--s5); max-width:32ch; font-size:21px; line-height:31px; font-weight:600}

/* ---------- footer ---------- */
footer{border-top:3px solid var(--rule); padding:var(--s4) 0 var(--s6);
  display:flex; justify-content:space-between; gap:var(--s3); flex-wrap:wrap;
  font-size:14px; color:var(--muted)}
footer a{text-decoration:none; font-weight:600; margin-left:var(--s4)}
footer a:hover{color:var(--ink)}

@media (max-width:820px){
  .wrap{padding:0 var(--s4)}
  .games{grid-template-columns:1fr; max-width:520px; margin-inline:auto}
  .card:first-child:nth-last-child(odd){grid-column:auto}
  .herocard{padding:var(--s5) var(--s4)}
}


/* ---------- legal pages ---------- */
.legal{max-width:44rem}
.legal h2{font-family:var(--display); font-size:20px; margin:var(--s5) 0 var(--s3)}
.legal h2 .num{color:var(--muted); font-weight:500; margin-right:var(--s2)}
.legal h3{font-size:17px; margin:var(--s4) 0 var(--s2)}
.legal p, .legal li{margin:0 0 var(--s3)}
.legal ul, .legal ol{padding-left:var(--s4)}
.legal li{margin-bottom:var(--s2)}
.legal a{font-weight:600}
.stamp{color:var(--muted); font-size:15px; margin:0}
nav.toc{background:var(--raised); border:3px solid var(--ink); border-radius:var(--r-card);
  box-shadow:5px 5px 0 var(--ink); padding:var(--s4); margin:var(--s5) 0}
nav.toc p{font-family:var(--display); font-weight:700; margin:0 0 var(--s2); font-size:17px}
nav.toc ol{margin:0; padding-left:var(--s4); font-size:15px; line-height:1.55}
nav.toc li{margin-bottom:var(--s1)}
.tablewrap{overflow-x:auto; margin:0 0 var(--s4); border:3px solid var(--ink);
  border-radius:var(--r-card)}
.legal table{border-collapse:collapse; width:100%; font-size:15px; min-width:32rem}
.legal th,.legal td{text-align:left; vertical-align:top; padding:var(--s2) var(--s3);
  border-bottom:2px solid var(--rule)}
.legal th{font-weight:700; background:var(--paper)}
.legal tr:last-child td{border-bottom:none}
.callout{border-left:4px solid var(--rule); padding-left:var(--s3); color:var(--muted)}
.notice{border:3px solid var(--ink); border-radius:var(--r-card); background:var(--raised);
  box-shadow:5px 5px 0 var(--ink); padding:var(--s4); margin:0 0 var(--s5)}
.notice p:last-child{margin-bottom:0}
@media print{ .top, nav.toc, footer{display:none} .wrap{max-width:none; padding:0} }

/* Visually hidden, still in the accessibility tree. Used for held cards,
   whose visible name is an em dash and means nothing read aloud. */
.vh{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;
    clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;border:0}

/* Nav separator. Was <li><hr></li>, which is an empty item in the a11y tree. */
.menu ul li.sep{border-top:1px solid var(--rule);margin-top:var(--s2);padding-top:var(--s2)}

/* A pending badge should read as deliberate, not unfinished. */
.badge.off{background:transparent;color:var(--muted);border:2px dashed var(--rule)}

/* Banner cards: the image fills the art box and replaces the colour field,
   the centred icon and the lattice. The lattice at 16% over flat colour is
   texture; over rendered art it reads as dirt. */
.art.banner img.bannerimg{width:100%;height:100%;object-fit:cover;
  border-radius:0;box-shadow:none;display:block}
.art.banner::after{display:none}

/* Card header: app icon beside the chip and title. */
.chead{display:flex; gap:var(--s3); align-items:center}
.cicon{width:76px; height:76px; border-radius:18px; flex:none; display:block;
  box-shadow:0 3px 0 rgba(42,36,56,.34)}
.cmeta{display:flex; flex-direction:column; gap:var(--s1); min-width:0}

/* Whole card is the link target. This is a stretched pseudo-element on the
   existing title link, NOT a wrapping <a>: nesting anchors is invalid HTML
   and the browser would break the store badges out of the outer link.
   One anchor means one focusable element and one thing announced, named by
   the title. Held cards have no <a> in .name, so they get no overlay and
   stay correctly unclickable. */
.name a::after{content:""; position:absolute; inset:0; z-index:1}
/* Store badges carry their own destinations - lift the LINKS above the
   overlay, not the container. Raising the whole .badges row turns the flex
   gaps around the badges into dead zones: not a badge, and blocking the
   card link underneath. */
.badges a{position:relative; z-index:2}

/* ---------- Vaidas mock: wordmark, gradient ground, centred hero ---------- */

/* Wordmark as a themed background rather than two <img> tags: only one file
   downloads, and it follows BOTH prefers-color-scheme and the data-theme
   override, the same way every colour token does. The accessible name comes
   from the visually-hidden span inside the link. */
.mark{display:block; width:150px; height:63px; text-indent:0;
  background:url(/img/wordmark-ink.png) no-repeat center/contain}
@media (prefers-color-scheme:dark){
  :root:not([data-theme="light"]) .mark{background-image:url(/img/wordmark-paper.png)}
}
:root[data-theme="dark"] .mark{background-image:url(/img/wordmark-paper.png)}

/* Gradient ground, resolving into --paper so the rest of the page is unchanged. */
body{background:linear-gradient(180deg,#80B2F9 0%,#B7DAF6 16%,#E6F2E4 36%,
     #F6E9C6 60%,var(--paper) 88%) no-repeat top center/100% 1000px, var(--paper)}
@media (prefers-color-scheme:dark){
  :root:not([data-theme="light"]) body{background:linear-gradient(180deg,#3F4964 0%,
    #4A5162 18%,#4C4E58 40%,#453F49 66%,var(--paper) 92%) no-repeat top center/100% 1000px,
    var(--paper)}
}
:root[data-theme="dark"] body{background:linear-gradient(180deg,#3F4964 0%,
  #4A5162 18%,#4C4E58 40%,#453F49 66%,var(--paper) 92%) no-repeat top center/100% 1000px,
  var(--paper)}

/* Nav as a floating pill. */
/* Nav pill. --raised is DARKER than the gradient at the top of the page, so
   filling with it made the pill recede - measured identical to the ground.
   The mock uses a translucent neutral that lifts AND desaturates (in light
   mode its blue channel drops, which a white overlay cannot do). Values
   solved from the mock: ~rgba(212,212,212,.36) light, ~rgba(138,138,138,.60)
   dark. */
.top nav{display:flex; align-items:center; gap:var(--s4);
  background:rgba(212,212,212,.36);
  border:1px solid rgba(255,255,255,.40); border-radius:var(--r-pill);
  padding:10px var(--s4); backdrop-filter:blur(10px)}
@media (prefers-color-scheme:dark){
  :root:not([data-theme="light"]) .top nav{background:rgba(138,138,138,.42);
    border-color:rgba(255,255,255,.20)}
}
:root[data-theme="dark"] .top nav{background:rgba(138,138,138,.42);
  border-color:rgba(255,255,255,.20)}

/* Theme switch, left of the pill. Shows the theme you would switch TO. */
.top{gap:var(--s3)}
.themetoggle{margin-left:auto; display:grid; place-items:center;
  width:42px; height:42px; flex:none; cursor:pointer; color:var(--ink);
  background:rgba(212,212,212,.36); border:1px solid rgba(255,255,255,.40);
  border-radius:var(--r-pill); backdrop-filter:blur(10px);
  transition:transform 110ms ease}
.themetoggle:hover{transform:translateY(-1px)}
.themetoggle:focus-visible{outline:2px solid var(--focus); outline-offset:2px}
@media (prefers-color-scheme:dark){
  :root:not([data-theme="light"]) .themetoggle{background:rgba(138,138,138,.42);
    border-color:rgba(255,255,255,.20)}
}
:root[data-theme="dark"] .themetoggle{background:rgba(138,138,138,.42);
  border-color:rgba(255,255,255,.20)}
/* light theme -> offer dark (moon); dark theme -> offer light (sun) */
.themetoggle .i-sun{display:none}
.themetoggle .i-moon{display:block}
@media (prefers-color-scheme:dark){
  :root:not([data-theme="light"]) .themetoggle .i-sun{display:block}
  :root:not([data-theme="light"]) .themetoggle .i-moon{display:none}
}
:root[data-theme="dark"] .themetoggle .i-sun{display:block}
:root[data-theme="dark"] .themetoggle .i-moon{display:none}
:root[data-theme="light"] .themetoggle .i-sun{display:none}
:root[data-theme="light"] .themetoggle .i-moon{display:block}

/* Centred hero. Heading centres; body copy stays left-aligned in a centred
   column, because centred paragraphs are harder to read. */
.hero.center{max-width:none; text-align:center}
.hero.center h1{font-size:clamp(34px,5vw,62px); line-height:1.08;
  max-width:19ch; margin:0 auto var(--s5)}
.hero.center p{max-width:58ch; margin-left:auto; margin-right:auto; text-align:left}
h2.center{text-align:center}

/* ---------- Official store badges ----------------------------------------
   Apple and Google both forbid recolouring or redrawing their badges, so
   these are the supplied assets. Files are named by the BADGE's own colour,
   not the theme: the black badge goes on light grounds, the white on dark. */
.badge.store{width:150px; height:44px; padding:0; border:0; border-radius:0;
  background:no-repeat center/contain; box-shadow:none}
.badge.store.lg{width:182px; height:53px}
.badge.store.appstore{background-image:url(/img/badge-appstore-black.webp)}
.badge.store.googleplay{background-image:url(/img/badge-googleplay-black.webp)}
@media (prefers-color-scheme:dark){
  :root:not([data-theme="light"]) .badge.store.appstore{background-image:url(/img/badge-appstore-white.webp)}
  :root:not([data-theme="light"]) .badge.store.googleplay{background-image:url(/img/badge-googleplay-white.webp)}
}
:root[data-theme="dark"] .badge.store.appstore{background-image:url(/img/badge-appstore-white.webp)}
:root[data-theme="dark"] .badge.store.googleplay{background-image:url(/img/badge-googleplay-white.webp)}
/* the herocard forces its own badge colours for the text badges - exempt these */
.herocard .badge.store{background-color:transparent}

/* The hero sits on the gradient, where --muted measures 3.15:1 in dark mode
   and 2.57:1 over the light blue - both below WCAG AA. On flat --paper it was
   6.53:1, so the gradient is what broke it. Hero copy stays at full ink.
   Vaidas's mock has both paragraphs the same weight anyway. */
.hero.center p:last-child{color:var(--ink)}

/* ---------- Game page hero ------------------------------------------------
   Art stands alone; the title block sits below it on the page background.
   Text is never overlaid: measured, these heroes span 0.004-0.970 luminance,
   so overlaid text is ~1:1 at worst and would need a 55% scrim for AA. */
.heroart{aspect-ratio:2200/920; border:3px solid var(--ink);
  border-radius:var(--r-card); overflow:hidden; box-shadow:8px 8px 0 var(--ink);
  margin:0 0 var(--s5)}
.heroart img{width:100%; height:100%; object-fit:cover; display:block}

.gamehead{margin:0 0 var(--s6)}
.gamehead h1{font-family:var(--display); font-weight:800;
  font-size:clamp(34px,4.6vw,56px); line-height:1.02; margin:0}
.gamehead .cicon{width:96px; height:96px; border-radius:22px}
.gamehead .tagline{margin:var(--s4) 0 var(--s5); max-width:44ch}
@media (max-width:820px){
  .gamehead .cicon{width:68px; height:68px; border-radius:16px}
}


/* Inside the hero panel the badge follows the PANEL's polarity, supplied as a
   token next to --game/--game-ink. The page theme is irrelevant here: the
   panel is the game colour either way. */
.herobody .badge.store.appstore{background-image:var(--badge-as)}
.herobody .badge.store.googleplay{background-image:var(--badge-gp)}

/* ---------- Mobile nav: pill collapses to a hamburger ---------------------
   At phone width the pill wrapped under the wordmark and collided with it.
   Below 720px the links move into a panel behind a real <button> with
   aria-expanded; a <noscript> block keeps the nav expanded without JS so the
   button can never become a dead end. */
.hamburger{display:none; place-items:center; width:42px; height:42px; flex:none;
  cursor:pointer; color:var(--ink); background:rgba(212,212,212,.36);
  border:1px solid rgba(255,255,255,.40); border-radius:var(--r-pill);
  backdrop-filter:blur(10px)}
.hamburger:focus-visible{outline:2px solid var(--focus); outline-offset:2px}
@media (prefers-color-scheme:dark){
  :root:not([data-theme="light"]) .hamburger{background:rgba(138,138,138,.42);
    border-color:rgba(255,255,255,.20)}
}
:root[data-theme="dark"] .hamburger{background:rgba(138,138,138,.42);
  border-color:rgba(255,255,255,.20)}

@media (max-width:720px){
  .top{position:relative; flex-wrap:nowrap; gap:var(--s2)}
  .mark{width:118px; height:50px}
  .hamburger{display:grid}
  .themetoggle{margin-left:auto}
  .top nav{display:none; position:absolute; top:calc(100% + 8px); right:0;
    z-index:40; flex-direction:column; align-items:stretch; gap:var(--s2);
    min-width:210px; padding:var(--s3); border-radius:var(--r-control)}
  .top[data-nav="open"] nav{display:flex}
  .top nav a, .top nav details.menu summary{padding:6px 4px}
  details.menu ul{position:static; box-shadow:none; border:0; padding:0;
    background:transparent}
}

/* Header nav links. `nav > a` (line ~63) sets --muted, which is 2.28:1 on the
   lifted pill. Games escaped it only because it sits inside <details>, so it
   is not a direct child - which is why the first fix caught one of three.
   Scoped to .top so the legal-page TOC keeps its deliberate de-emphasis. */
.top nav > a{color:var(--ink)}

/* Multi-column list, used for the video poker variants. */
.cols{columns:2; column-gap:var(--s5); margin:0 0 var(--s4); padding-left:var(--s4)}
.cols li{break-inside:avoid; margin-bottom:var(--s1)}
@media (max-width:560px){ .cols{columns:1} }

/* Breadcrumb trail. Matches the BreadcrumbList markup - visible, not markup-only. */
.crumbs{display:flex; flex-wrap:wrap; align-items:center; gap:var(--s2);
  font-size:14px; color:var(--muted); margin:0 0 var(--s4)}
.crumbs a{color:var(--muted); text-decoration:none}
.crumbs a:hover{color:var(--ink); text-decoration:underline; text-underline-offset:3px}
.crumbs [aria-current]{color:var(--ink); font-weight:600}
.crumbs .sep{opacity:.5}
