/* Shared by every game. Only declarations that were byte-identical in all
   eleven files live here; anything a game does differently stays in that
   game's own <style>, which is loaded after this file and therefore wins.
   Change something here and it changes in all eleven games at once.

   Deliberately not versioned in the filename: the host serves everything with
   "max-age=0, must-revalidate" plus an ETag, so the browser checks this file on
   every load anyway. A version in the name would mean touching all eleven games
   again for every change here, which is the thing this file exists to avoid. */

:root {
  --redact: #b8382a;
  --olive: #6b7a4f;
  --mono: ui-monospace, "SFMono-Regular", "Courier New", Courier, monospace;
  --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}

/* --on-bg-muted carries real body text, not just decoration: .eyebrow here, the
   secondary buttons in fourteen games, and 15px lines like .boom-sub and
   .ready-body. So it has to clear 4.5:1 against its own --bg, the threshold for
   normal-size text.
   Measured on 11 August, the four light variants sat at 3.18 (ocean), 3.31
   (forest), 3.67 (paper) and 3.77 (wine) -- readable on a desk, not in a dim room
   on a phone, which is where these games are played. They were darkened to land
   between 4.65 and 4.68 with the hue kept. The dark variants were already at 8.4
   to 9.1 and are untouched.
   Aimed above 4.5 and not exactly on it, because rounding differs per engine: the
   first attempt computed 4.51 and the browser read 4.49.
   Lighten these again and the games lose that margin without anything looking
   broken, so check the ratio before you touch them. shared-index.css carries its
   own set for the nine pages outside the games, held to the same 4.5. */
html[data-theme="dark"] {
  --bg: #0d0c0b;
  --on-bg-text: #f5f0e6;
  --on-bg-muted: #b8ae9a;
  --on-bg-border: #35312a;
  --surface: #171613;
  --surface-2: #201e1a;
  --surface-border: #35312a;
  --surface-input-bg: #0a0908;
  --surface-text: #f5f0e6;
  --surface-muted: #c9bfab;
  --icon-btn-bg: rgba(255,255,255,0.08);
  --icon-btn-border: rgba(255,255,255,0.2);
}

html[data-theme="light"] {
  --bg: #ede6d6;
  --on-bg-text: #1c1b19;
  --on-bg-muted: #6b6558;
  --on-bg-border: #c9bfa5;
  --surface: #faf7ef;
  --surface-2: #f1ebdd;
  --surface-border: #d8cdb2;
  --surface-input-bg: #ffffff;
  --surface-text: #1c1b19;
  --surface-muted: #6a6455;
  --icon-btn-bg: rgba(28,27,25,0.06);
  --icon-btn-border: rgba(28,27,25,0.18);
}

/* ---------- palettes ---------- */
/* A palette recolours the surface, not the game. Each one is a pair, a dark and
   a light variant, because every game has a sun/moon button that must keep
   meaning something -- and because those eighteen buttons each write their own
   storage key, which a shared palette must not fight. So light/dark stays on
   data-theme and owned by the game; the palette rides along on data-palette,
   written by shared.js under one key for all eighteen.

   Two attribute selectors beat the one above, so only the variables that differ
   are listed here and the rest falls back to the block for that theme.
   Accents are deliberately untouched: --gold lives in nine game files and eight
   games carry their own trio (--bus, --ring, --deal, --indigo, --orange,
   --umber, --blue), so a palette that promised to recolour those would be a
   promise kept in one game out of three.
   The default palette has no block at all -- it is the two above. */
[data-palette="ocean"][data-theme="dark"] {
  --bg: #0a1016;
  --on-bg-text: #e6f0f5;
  --on-bg-muted: #9db2c2;
  --on-bg-border: #24384a;
  --surface: #101a24;
  --surface-2: #16242f;
  --surface-border: #24384a;
  --surface-input-bg: #060c11;
  --surface-text: #e6f0f5;
  --surface-muted: #a9bece;
}

[data-palette="ocean"][data-theme="light"] {
  --bg: #dfe9f0;
  --on-bg-text: #16242f;
  --on-bg-muted: #546977;
  --on-bg-border: #b8cddb;
  --surface: #f4f9fc;
  --surface-2: #e6eff5;
  --surface-border: #b8cddb;
  --surface-input-bg: #ffffff;
  --surface-text: #16242f;
  --surface-muted: #5a7183;
}

[data-palette="forest"][data-theme="dark"] {
  --bg: #0a0f0b;
  --on-bg-text: #e9f2ea;
  --on-bg-muted: #a1b7a7;
  --on-bg-border: #2c3d31;
  --surface: #121a14;
  --surface-2: #1a251d;
  --surface-border: #2c3d31;
  --surface-input-bg: #060a07;
  --surface-text: #e9f2ea;
  --surface-muted: #adc3b3;
}

[data-palette="forest"][data-theme="light"] {
  --bg: #e2ebe2;
  --on-bg-text: #17231a;
  --on-bg-muted: #586b5e;
  --on-bg-border: #bcd2bf;
  --surface: #f5faf5;
  --surface-2: #e8f1e9;
  --surface-border: #bcd2bf;
  --surface-input-bg: #ffffff;
  --surface-text: #17231a;
  --surface-muted: #5d7263;
}

[data-palette="wine"][data-theme="dark"] {
  --bg: #0e0a14;
  --on-bg-text: #efe9f5;
  --on-bg-muted: #b3a4c1;
  --on-bg-border: #382a44;
  --surface: #17111f;
  --surface-2: #211829;
  --surface-border: #382a44;
  --surface-input-bg: #090610;
  --surface-text: #efe9f5;
  --surface-muted: #bfb0cd;
}

[data-palette="wine"][data-theme="light"] {
  --bg: #e8e2ef;
  --on-bg-text: #1f172a;
  --on-bg-muted: #6c5f79;
  --on-bg-border: #cdbedb;
  --surface: #f9f5fc;
  --surface-2: #efe8f4;
  --surface-border: #cdbedb;
  --surface-input-bg: #ffffff;
  --surface-text: #1f172a;
  --surface-muted: #6b5d7a;
}

* {
  box-sizing: border-box;
  -webkit-tap-highlight-color: transparent;
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
}

html, body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--on-bg-text);
  font-family: var(--sans);
  overscroll-behavior: none;
}

#app {
  min-height: 100vh;
  min-height: 100dvh;
  background: var(--bg);
  display: flex;
  flex-direction: column;
  align-items: stretch;
}

.topbar {
  display: flex;
  align-items: center;
  width: 100%;
  flex-shrink: 0;
}

.lang-select {
  width: 100%;
  font-family: var(--mono);
  font-size: 14px;
  letter-spacing: 0.5px;
  background: var(--surface-input-bg);
  color: var(--surface-text);
  border: 1px solid var(--surface-border);
  border-radius: 8px;
  padding: 10px 12px;
  -webkit-appearance: none;
  appearance: none;
}

.icon-group {
  display: flex;
  gap: 6px;
}

.icon-btn {
  width: 34px;
  height: 34px;
  border-radius: 8px;
  border: 1px solid var(--icon-btn-border);
  background: var(--icon-btn-bg);
  color: var(--on-bg-text);
  font-size: 16px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.main-column {
  flex: 1;
  display: flex;
  flex-direction: column;
}

.screen {
  display: none;
  flex-direction: column;
  width: 100%;
  margin: 0 auto;
}

.screen.active {
  display: flex;
}

.eyebrow {
  font-family: var(--mono);
  text-transform: uppercase;
  color: var(--on-bg-muted);
  text-align: center;
}

h1.stamp {
  font-family: var(--mono);
  text-align: center;
  color: var(--redact);
  border: 3px solid var(--redact);
  border-radius: 4px;
  margin: 0;
  transform: rotate(-2deg);
  text-transform: uppercase;
  /* iOS Safari can leave a stale sliver of a rotated bordered box behind when
     its .screen toggles display: none -> flex (seen switching screen-board ->
     screen-setup -> screen-board on Scorebord). translateZ(0) forces its own
     compositing layer so the old raster gets invalidated instead of bleeding
     through. */
  -webkit-transform: rotate(-2deg) translateZ(0);
  -webkit-backface-visibility: hidden;
}

.card {
  background: var(--surface);
  color: var(--surface-text);
  border-radius: 10px;
  border: 1px solid var(--surface-border);
  padding: 22px;
  box-shadow: 0 8px 24px rgba(0,0,0,0.35);
  position: relative;
}

.field {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-bottom: 16px;
}

.field:last-child {
  margin-bottom: 0;
}

.field label {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 2px;
  text-transform: uppercase;
  color: var(--surface-muted);
}

/* Namenvelden in alle spellen. iOS verstopt zijn overlay-scrollbalken een paar
   seconden na het scrollen en negeert ::-webkit-scrollbar-styling, dus met
   meer namen dan er passen is er geen blijvend teken dat de lijst doorloopt.
   shared.js tekent daarom een eigen duimpje (.names-scroll-thumb) dat blijft
   staan zolang de inhoud overloopt; de native balk gaat hier uit zodat er
   nooit twee naast elkaar staan. */
textarea.names-input { overflow-y: auto; scrollbar-width: none; }
textarea.names-input::-webkit-scrollbar { display: none; }
.names-scroll-wrap { position: relative; }
.names-scroll-wrap > textarea.names-input { width: 100%; display: block; }
.names-scroll-thumb {
  position: absolute;
  right: 5px;
  width: 6px;
  border-radius: 3px;
  background: var(--surface-muted);
  opacity: 0.8;
  pointer-events: none;
  display: none;
}

/* De koopknop in het uitlegvenster. Alleen de plaatsing staat hier: de kleur
   komt uit button.primary, dat elk spel met zijn eigen accent overschrijft. */
/* De steunkaart op een eindscherm. Erft .card, dus oppervlak, rand en thema
   komen uit het gekozen palet; alleen de afstand en de knoppenkolom staan hier.
   De kaart is een direct kind van .screen, dat een flex-kolom is, dus de breedte
   volgt vanzelf. */
.support-card {
  margin-top: 16px;
  text-align: center;
}

.support-ask {
  margin: 0 0 14px;
  font-size: 14px;
  line-height: 1.55;
}

.support-card .primary {
  width: 100%;
}

/* Bewust geen .secondary: veertien spellen definiëren die zelf en tipsy niet,
   dus een knop die erop leunt ziet er in één spel uit als een kale browserknop.
   Daarom staan de eigenschappen hier voluit, en wel precies die van de
   secondary-knop die die veertien spellen zelf definiëren: omlijnd, mono,
   hoofdletters, radius 8. Zo leest hij als een knop van dit spel.
   Hij blijft lichter dan de vraag erboven -- omlijnd tegenover gevuld, gedempt
   tegenover accent, 14px tegenover 16 -- want wegklikken mag zichtbaar zijn zonder
   even zwaar te wegen als de vraag zelf. Eerder was dit een kale tekstregel van
   12px zonder rand, en die las niet als iets dat je kon aantikken. */
.support-no {
  display: block;
  width: 100%;
  margin-top: 8px;
  padding: 12px;
  background: transparent;
  border: 1px solid var(--surface-border);
  border-radius: 8px;
  color: var(--surface-muted);
  font-family: var(--mono);
  font-size: 14px;
  letter-spacing: 1px;
  text-transform: uppercase;
}

.supporter-buy {
  width: 100%;
  margin-top: 4px;
}

/* ---------- game rules ---------- */
.game-rules {
  margin-top: 28px;
  padding: 20px 2px 0;
  border-top: 1px solid var(--on-bg-border);
  color: var(--on-bg-muted);
  font-size: 14px;
  line-height: 1.65;
  text-align: left;
}
.game-rules h3 {
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--on-bg-text);
  margin: 18px 0 6px;
}
.game-rules h3:first-child { margin-top: 0; }
.game-rules p { margin: 0 0 10px; }
.game-rules ul { margin: 0 0 10px; padding-left: 20px; }
.game-rules li { margin: 0 0 4px; }

/* ---------- palette picker ---------- */
/* Built by shared.js and opened from the topbar, so all eighteen games get the
   same sheet. Every palette is shown, locked ones included: nobody buys what
   they never knew was there. The swatch is drawn from the palette's own
   variables via inline custom properties, so a new palette in the blocks at the
   top of this file needs no CSS here. */
.palette-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
}

.palette-option {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px;
  border-radius: 8px;
  border: 1px solid var(--surface-border);
  background: transparent;
  color: var(--surface-text);
  font-family: var(--sans);
  font-size: 14px;
  text-align: left;
}

.palette-option.active {
  border-color: var(--surface-text);
  background: var(--surface-2);
}

/* Three bands in the palette's own colours: page, card and text. Enough to
   recognise it without pretending to be a preview of a whole game. */
.palette-swatch {
  flex: 0 0 auto;
  width: 34px;
  height: 34px;
  border-radius: 6px;
  overflow: hidden;
  border: 1px solid var(--surface-border);
  display: flex;
  flex-direction: column;
}

.palette-swatch i {
  display: block;
  flex: 1;
}

/* The swatch carries data-palette and data-theme itself, so the blocks at the
   top of this file resolve inside it and these three bands need no colours of
   their own. That is why those selectors are not anchored to html. */
.palette-swatch i:nth-child(1) { background: var(--bg); }
.palette-swatch i:nth-child(2) { background: var(--surface); }
.palette-swatch i:nth-child(3) { background: var(--on-bg-text); }

/* A card back is one gradient, not three bands, and its colours come from
   CARD_BACKS in shared.js rather than from a variable -- so this one carries an
   inline background and only needs the card's own proportions here. */
.cardback-swatch {
  display: block;
  width: 26px;
  height: 34px;
  border-radius: 4px;
}

/* The heading above the card-back row. Same shape as a .field label so the
   sheet reads as one form and not as two stacked things. */
.palette-section {
  display: block;
  margin-top: 4px;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 2px;
  text-transform: uppercase;
  color: var(--surface-muted);
}

/* ---------- named groups ---------- */
/* Built by shared.js above every name field, so all games get the same bar.
   Everything here uses the surface variables, which each game sets to its own
   theme -- no game needs a line of CSS for this. */
.group-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
}

.group-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  min-width: 0;
}

.group-btn {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 1px;
  text-transform: uppercase;
  padding: 5px 10px;
  border-radius: 6px;
  border: 1px solid var(--surface-border);
  background: transparent;
  color: var(--surface-muted);
  flex-shrink: 0;
}

.group-btn:active { background: var(--surface-2); }

/* Pushed to the far edge so the chips read as a list and the button as the way
   to add to it, however many chips there are. */
.group-save { margin-left: auto; }

.group-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-family: var(--sans);
  font-size: 13px;
  padding: 5px 10px;
  border-radius: 6px;
  border: 1px solid var(--surface-border);
  background: transparent;
  color: var(--surface-text);
  max-width: 100%;
}

.group-chip.active {
  border-color: var(--surface-text);
  background: var(--surface-2);
}

.group-chip-count {
  font-family: var(--mono);
  font-size: 11px;
  color: var(--surface-muted);
}

/* Stretched over the full height of the chip so a thumb has something to aim
   at vertically. Deliberately not widened much further: this deletes a group,
   and a mistap on the name beside it only deselects, which costs nothing. */
.group-chip-x {
  font-size: 13px;
  line-height: 1;
  color: var(--surface-muted);
  /* The negative margins are what give the target the full height of the chip
     -- 25 by 29 measured, against 21 by 17 without them, because align-self
     stops at the chip's own padding. */
  padding: 8px 6px 8px 8px;
  margin: -8px -8px -8px 0;
}

.group-hint {
  margin: 6px 0 0;
  font-family: var(--sans);
  font-size: 12px;
  color: var(--surface-muted);
}

.group-name-input {
  width: 100%;
  font-family: var(--sans);
  font-size: 16px;
  background: var(--surface-input-bg);
  color: var(--surface-text);
  border: 1px solid var(--surface-border);
  border-radius: 8px;
  padding: 12px;
}

.toggle-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

.toggle {
  width: 52px;
  height: 30px;
  border-radius: 20px;
  background: var(--surface-border);
  position: relative;
  flex-shrink: 0;
  transition: background .2s;
}

.toggle.on {
  background: var(--olive);
}

.toggle .knob {
  position: absolute;
  top: 3px;
  left: 3px;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: #f7f3ea;
  transition: transform .2s;
}

.toggle.on .knob {
  transform: translateX(22px);
}

button.primary {
  font-family: var(--mono);
  font-size: 16px;
  letter-spacing: 1px;
  text-transform: uppercase;
  border: none;
  border-radius: 8px;
  width: 100%;
}

.modal-overlay {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.6);
  align-items: flex-end;
  justify-content: center;
  padding: 0;
}

.modal-overlay.active {
  display: flex;
}

.modal-sheet {
  background: var(--surface);
  color: var(--surface-text);
  width: 100%;
  max-width: 460px;
  border-radius: 16px 16px 0 0;
  padding: 24px 20px max(24px, env(safe-area-inset-bottom));
  max-height: 86vh;
  overflow-y: auto;
  box-shadow: 0 -10px 40px rgba(0,0,0,0.4);
}

.modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.modal-header h2 {
  font-family: var(--mono);
  font-size: 18px;
  letter-spacing: 1px;
  margin: 0;
  text-transform: uppercase;
}

.modal-close {
  width: 30px;
  height: 30px;
  border-radius: 50%;
  border: 1px solid var(--surface-border);
  background: transparent;
  color: var(--surface-text);
  font-size: 16px;
}

.modal-sub {
  font-family: var(--sans);
  color: var(--surface-muted);
}

.overview-link {
  display: block;
  margin-top: 18px;
  text-align: center;
  font-size: 13px;
  letter-spacing: 1px;
  color: inherit;
  opacity: .75;
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* ---------- the supporter gate ---------- */
/* Everything a game needs to show that something is locked. One class on
   <body> clears the whole page at once, so no game has to track the state in
   its own JS -- shared.js puts it there.

   Blur is a shop window and not a lock: the figures stay in the DOM and are
   meant to be recognisable as real. Names, dates and labels are never blurred,
   because recognising your own evening is what makes the number beside it
   worth unlocking; a field of grey would only say that something exists. */
.locked-figure {
  filter: blur(6px);
  user-select: none;
}

body.is-supporter .locked-figure {
  filter: none;
  user-select: auto;
}

/* A small line of explanation under whatever is locked. .muted-note is the
   plain version for any aside; add .lock-note when the line only makes sense
   while something is actually locked, and it takes care of itself. */
.muted-note {
  font-family: var(--sans);
  font-size: 13px;
  line-height: 1.5;
  color: var(--surface-muted);
  text-align: center;
}

/* Generic, because more than notes need hiding: a whole block that only makes
   sense once something has been received. No game defines its own .is-hidden. */
.is-hidden,
body.is-supporter .lock-note {
  display: none;
}

/* The way out from under a lock note: a plain underlined line to the supporter
   page, in the same muted type as the note above it so it reads as one thought
   and not as a banner. It carries .lock-note as well, so the rule above hides it
   for a supporter -- and that rule wins on specificity over this display, which
   is why the order here is safe. */
.lock-link {
  display: block;
  margin-top: 6px;
  color: var(--surface-muted);
  text-decoration: underline;
  text-underline-offset: 2px;
}

.lock-actions {
  display: flex;
  gap: 10px;
  margin-top: 4px;
}

/* A padlock on the control that leads to something locked. Show it, never
   hide the control: nobody buys what they never knew was there.

   Drawn with ::after rather than as a child element on purpose. Every game
   rewrites the innerHTML of anything carrying data-t when the language
   changes, which would wipe a child on the first switch. */
.locked-action::after {
  content: " \1F512";
}

body.is-supporter .locked-action::after {
  content: none;
}

/* ---------- home button and its confirmation ---------- */
/* Both are built in shared.js, because the button and the question are the same
   in every game. Yes is red: it leaves the game and whatever is on screen is
   gone. No is green: it puts you back where you were.

   The two colour rules are deliberately more specific than a plain
   "button.primary". Every game overrides that selector with its own accent in
   its own <style>, which is loaded after this file, so at equal specificity the
   game would win and the Yes button would come out in the game's accent colour
   instead of red. The :active pairs need one step more again, because a game's
   "button.primary:active" already carries a pseudo-class. */
.home-ask {
  font-size: 15px;
  line-height: 1.5;
  text-align: center;
  margin: 6px 0 18px;
}

.home-actions {
  display: flex;
  gap: 10px;
}

.home-actions button.home-yes,
.home-actions button.home-no {
  padding: 16px;
}

.home-actions button.home-yes {
  background: var(--redact);
  color: #ede6d6;
}

.home-actions button.home-yes:active {
  background: #8f2c21;
}

.home-actions button.home-no {
  background: var(--olive);
  color: #ede6d6;
}

.home-actions button.home-no:active {
  background: #5a6742;
}

/* The home icon is an inline SVG, not a character: U+2302 has no glyph in the
   iOS system font and the button came out empty on an iPhone. The svg is sized
   to sit level with the gear and the sun next to it. */
#btn-home svg {
  display: block;
  width: 17px;
  height: 17px;
}

/* ---------- footer with a way out ----------
   Sits below #app, which is min-height:100dvh, so it never competes with the
   game for space: you scroll past the game to reach it. That placement is
   deliberate. Fifteen games each style their own screens and several fill the
   viewport with a canvas; putting links inside #app risked breaking layouts
   that have nothing to do with each other.

   The links are real, static hrefs in the served HTML rather than injected by
   script, because their first job is to give a crawler that lands on a game
   page somewhere to go. shared.js only relabels them when the player switches
   language. */
.game-footer {
  border-top: 1px solid rgba(127, 127, 127, 0.28);
  padding: 22px 18px max(28px, env(safe-area-inset-bottom));
  margin-top: 8px;
  background: var(--bg);
  color: var(--on-bg-text);
}

.game-footer-heading {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 2px;
  text-transform: uppercase;
  opacity: 0.55;
  margin-bottom: 12px;
}

.game-footer-links {
  display: flex;
  flex-wrap: wrap;
  gap: 10px 18px;
  max-width: 520px;
}

.game-footer-links a {
  font-family: var(--sans);
  font-size: 14px;
  color: var(--on-bg-text);
  opacity: 0.78;
  text-decoration: none;
  border-bottom: 1px solid rgba(127, 127, 127, 0.4);
  padding-bottom: 2px;
}

.game-footer-links a:active {
  opacity: 1;
}

/* The way back to the overview earns a little more weight than its three
   neighbours: it is the link that always applies, whatever game you are on. */
.game-footer-links a[data-game="home"] {
  font-family: var(--mono);
  font-size: 13px;
  letter-spacing: 1px;
  opacity: 1;
}
