/* Shared by the hub and every game: reset, Telegram safe areas, the slim chrome
   a game wears, and the letterboxed stage that fits any phone or desktop.

   The chrome is pixel art end to end. Two rules make that read: nothing is round
   and nothing is smooth — panels are flat fills with a hard 4px bevel and a
   notched corner, and every glyph comes from one 16px atlas. The game canvases are
   deliberately left out of it; their art is the shipped games' own. */

/* Both faces are self-hosted and 1.6KB each, which is what a Telegram mini app can
   afford. Licences (SIL Open Font License 1.1) sit beside the files. */
@font-face { font-family: Silkscreen; font-weight: 400; src: url(assets/ui/fonts/silkscreen-400.woff2) format("woff2"); font-display: swap }
@font-face { font-family: Silkscreen; font-weight: 700; src: url(assets/ui/fonts/silkscreen-700.woff2) format("woff2"); font-display: swap }
@font-face { font-family: "Press Start 2P"; src: url(assets/ui/fonts/press-start-2p-400.woff2) format("woff2"); font-display: swap }

:root {
  --ui: Silkscreen, monospace;
  --display: "Press Start 2P", Silkscreen, monospace;

  --ink: #3e2d1d;
  --ink-dim: #8a683c;
  --page: #241a10;
  --chrome: #f0d78d;
  --chrome-lit: #ffefc4;
  --chrome-edge: #7d5526;
  --chrome-shadow: #4a3418;
  --field: #fff3c6;
  --field-ink: #5b3f21;
  --field-dim: #93744a;
  --good: #6f9c52;
  --done: #d7ecc0;
  --done-ink: #2f5c25;

  /* Kenney 1-Bit Pack, CC0, baked 3x by scripts/build_pixel_ui.py. The atlas is
     12 tiles wide, so one tile at --px is 1/12 of the sheet. */
  --atlas: url(assets/ui/pixel-icons.png);
  --atlas-tiles: 12;

  --notch: polygon(6px 0, calc(100% - 6px) 0, 100% 6px, 100% calc(100% - 6px),
    calc(100% - 6px) 100%, 6px 100%, 0 calc(100% - 6px), 0 6px);

  --top-safe: max(env(safe-area-inset-top, 0px), var(--tg-safe-top, 0px));
  --bottom-safe: max(env(safe-area-inset-bottom, 0px), var(--tg-safe-bottom, 0px));
  --left-safe: max(env(safe-area-inset-left, 0px), var(--tg-safe-left, 0px));
  --right-safe: max(env(safe-area-inset-right, 0px), var(--tg-safe-right, 0px));
}
* { box-sizing: border-box }
html, body { height: 100% }
body { margin: 0; overflow: hidden; background: var(--page); font-family: var(--ui); font-weight: 400 }
button { font: inherit; cursor: pointer; touch-action: manipulation; min-height: 44px }
button:disabled { cursor: default; filter: grayscale(1) brightness(.8) }
:focus-visible { outline: 3px solid #ef9e18; outline-offset: -3px }
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0 }

/* One atlas tile, sized by --px. The bake is 48px, so 48/24/16 all land on whole
   source pixels and the browser never has to resample the art. */
.px {
  --px: 24px;
  flex: none;
  display: inline-block;
  vertical-align: middle;
  width: var(--px);
  height: var(--px);
  background: var(--atlas) no-repeat;
  background-size: calc(var(--px) * var(--atlas-tiles)) var(--px);
  background-position: calc(var(--n) * var(--px) * -1) 0;
  image-rendering: pixelated;
}
.px-arrow { --n: 0 }
.px-boom { --n: 1 }
.px-check { --n: 2 }
.px-coin { --n: 3 }
.px-cross { --n: 4 }
.px-egg { --n: 5 }
.px-gem { --n: 6 }
.px-heart { --n: 7 }
.px-medal { --n: 8 }
.px-medal-bronze { --n: 9 }
.px-medal-gold { --n: 10 }
.px-medal-silver { --n: 11 }

/* Frame: a slim head bar over a viewport that owns everything else. The single
   minmax(0,1fr) column is what keeps a long row of goal chips from widening the
   page past the phone: an auto column sizes to max-content and the letterboxed
   canvas then grows with it, pushing the right-hand HUD off the screen. */
.frame {
  /* Telegram's reported height can arrive stale after a rotation (the portrait
     value while the window is already landscape), which used to push half the
     stage below the fold. min() with the real dvh keeps the frame honest. */
  height: min(100dvh, var(--tg-viewport-height, 100dvh));
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: auto minmax(0, 1fr);
  gap: 8px;
  padding: calc(8px + var(--top-safe)) calc(10px + var(--right-safe)) calc(10px + var(--bottom-safe)) calc(10px + var(--left-safe));
}
.hud {
  position: relative;
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
  min-height: 52px;
  padding: 6px 10px;
  background: var(--chrome);
  border: 4px solid var(--chrome-edge);
  box-shadow: inset 0 4px var(--chrome-lit), inset 0 -4px #00000030;
}
/* The brand gives room to the credit chip and profile button rather than pushing
   them off a narrow phone screen. Basis 0 keeps it on the first line: a long level
   subtitle would otherwise bump the profile button onto its own row. */
.brand { flex: 1 1 0; min-width: 0; line-height: 1 }
.brand h1 { margin: 0; font-size: 17px; color: var(--ink); text-shadow: 2px 2px #ffffff55; overflow: hidden; text-overflow: ellipsis; white-space: nowrap }
.kicker { display: block; font-size: 11px; letter-spacing: .5px; color: var(--ink-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap }

.goals { flex: 1; min-width: 0; display: flex; align-items: center; justify-content: center; gap: 6px; overflow: hidden }
.goal {
  display: flex;
  align-items: center;
  gap: 5px;
  padding: 4px 7px;
  background: var(--field);
  border: 2px solid var(--chrome-edge);
  clip-path: var(--notch);
  box-shadow: inset 0 2px #ffffff66;
  font-size: 13px;
  color: var(--field-ink);
  white-space: nowrap;
}
/* The chips carry the games' own baked thumbnails, which are 12-24px art: nearest
   neighbour keeps them crisp where the rest of the page is smooth. */
.goal i { width: 24px; height: 24px; flex: none; background-size: contain; background-repeat: no-repeat; background-position: center; image-rendering: pixelated }
.goal b { color: var(--ink) }
.goal span { color: var(--field-dim) }
.goal.done { background: var(--done); border-color: var(--good); color: var(--done-ink) }
.goal .time { font-variant-numeric: tabular-nums }

.purse { flex: none; display: flex; align-items: center; gap: 6px; font-size: 17px; color: var(--ink) }
.purse .px { --px: 24px }

.profile { flex: none; width: 44px; height: 44px; padding: 0; border: 4px solid var(--chrome-edge); background: var(--field); clip-path: var(--notch); overflow: hidden }
.profile .avatar { display: grid; place-items: center; width: 100%; height: 100%; font-size: 14px; color: var(--field-ink); background-size: contain; background-repeat: no-repeat; background-position: center; image-rendering: pixelated }
.menu {
  position: absolute;
  z-index: 12;
  top: calc(100% + 6px);
  right: 8px;
  display: grid;
  gap: 6px;
  width: 220px;
  padding: 10px;
  background: var(--chrome);
  border: 4px solid var(--chrome-edge);
  clip-path: var(--notch);
  box-shadow: inset 0 4px var(--chrome-lit);
}
.menu[hidden] { display: none }
.menu button, .menu-link {
  display: grid;
  place-items: center;
  min-height: 40px;
  padding: 7px;
  background: var(--field);
  border: 2px solid var(--chrome-edge);
  clip-path: var(--notch);
  box-shadow: inset 0 2px #ffffff66;
  color: var(--field-ink);
  font-family: var(--ui);
  font-size: 13px;
  text-align: center;
  text-decoration: none;
}
.menu button:active, .menu-link:active { box-shadow: inset 0 2px #00000030 }

/* The player profile: one sheet, rendered by profile.js into any
   [data-profile-summary] host — the hub panel and the in-game menu both use it. */
.profile-head { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; margin: 0; font-size: 14px }
.profile-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--field-ink) }
.profile-level { flex: none; font-size: 11px; color: var(--ink-dim) }
.profile-bar { position: relative; margin: 8px 0 0; height: 14px; background: var(--field-dim); border: 2px solid var(--chrome-edge); font-size: 0 }
.profile-bar span { display: block; height: 100%; background: #6fb546; box-shadow: inset 0 3px #a6dd7c }
.profile-bar i { position: absolute; inset: 0; display: grid; place-items: center; font-size: 9px; font-style: normal; color: #2c1e0c }
.profile-purse { display: flex; align-items: center; gap: 6px; margin: 8px 0 0; font-size: 13px; color: var(--field-ink) }
.profile-purse .px { --px: 20px }
.profile-purse strong { font-size: 17px; font-variant-numeric: tabular-nums }
.profile-games { list-style: none; margin: 8px 0 0; padding: 8px 0 0; display: grid; gap: 4px; border-top: 2px solid var(--field-dim) }
.profile-games li { display: grid; grid-template-columns: 1fr auto auto; gap: 8px; align-items: baseline; font-size: 11px; color: var(--ink-dim) }
.profile-games b { color: var(--field-ink); font-size: 12px }
.profile-games i { font-style: normal; font-variant-numeric: tabular-nums }

/* Hub header: the purse is worth seeing before a player picks a game. */
.credit-chip { flex: none; display: flex; align-items: center; gap: 6px; margin-left: auto; margin-right: 8px; padding: 6px 10px; background: #241c12; border: 2px solid #6b5a3d; clip-path: var(--notch); font-size: 15px; color: #ffe9b8; font-variant-numeric: tabular-nums }
.credit-chip .px { --px: 20px }

/* Stage: the fixed logical scene scaled to whatever the phone gives us. The canvas
   keeps the browser's smooth upscale on purpose — that art is the shipped game's. */
.stage { display: grid; place-items: center; min-height: 0; position: relative }
.stage canvas { max-width: 100%; max-height: 100%; border: 4px solid var(--chrome-edge); touch-action: none; cursor: crosshair }
/* Anchored to the stage, not a fixed offset from the top of the page: the HUD is
   two rows tall on a phone, and a hard 64px put this right over the goal chips. */
.toast {
  position: absolute;
  left: 50%;
  top: 8px;
  transform: translate(-50%, -8px);
  max-width: min(90%, 520px);
  padding: 8px 12px;
  text-align: center;
  background: var(--field);
  border: 3px solid var(--chrome-edge);
  clip-path: var(--notch);
  color: var(--field-ink);
  font-size: 13px;
  opacity: 0;
  pointer-events: none;
  transition: opacity .2s, transform .2s;
}
.toast.show { opacity: 1; transform: translate(-50%, 0) }

.modal { position: fixed; z-index: 20; inset: 0; display: grid; place-items: center; padding: 16px; background: #31271ca8 }
.modal[hidden] { display: none }
.result { width: min(100%, 345px); background: var(--chrome); border: 4px solid var(--chrome-edge); clip-path: var(--notch); box-shadow: inset 0 4px var(--chrome-lit); text-align: center; padding: 20px }
.result .medal { display: grid; place-items: center; margin: 10px 0 }
.result .medal .px { --px: 48px }
.result h2 { margin: 6px 0 10px; font-family: var(--display); font-size: 13px; line-height: 1.5; color: var(--field-ink) }
.result p { font-size: 13px; line-height: 1.5; color: var(--ink-dim) }
.result-actions { display: flex; gap: 8px }
.result button {
  width: 100%;
  padding: 12px;
  background: #6fb546;
  border: 3px solid #2f5c25;
  clip-path: var(--notch);
  box-shadow: inset 0 3px #a6dd7c;
  color: #10240b;
  font-family: var(--ui);
  font-size: 14px;
}
.result button:active { box-shadow: inset 0 3px #00000040 }
/* Exit is the secondary action on both dialogs, so it reads as a lesser press:
   the same notched plate as a menu row, in the panel's own field colour. */
.result-exit {
  display: block;
  margin-top: 10px;
  padding: 10px;
  background: var(--field);
  border: 2px solid var(--chrome-edge);
  clip-path: var(--notch);
  box-shadow: inset 0 2px #00000030;
  color: var(--field-ink);
  font-family: var(--ui);
  font-size: 13px;
  text-align: center;
  text-decoration: none;
}
.result-exit:active { box-shadow: inset 0 2px #00000060 }

/* A landscape-only scene in a portrait window: cover the game and hold it still
   rather than letterboxing 800x600 into a strip. */
.rotate-gate {
  position: fixed;
  z-index: 40;
  inset: 0;
  display: none;
  place-items: center;
  padding: 24px;
  background: var(--page);
}
.rotate-gate.show { display: grid }
/* A plate, because every word of chrome in this app sits on one: --ink is a dark
   brown on the farm's gold panel and would vanish on the bare page. */
.rotate-card {
  width: min(100%, 300px);
  display: grid;
  gap: 10px;
  justify-items: center;
  text-align: center;
  padding: 20px;
  background: var(--chrome);
  border: 4px solid var(--chrome-edge);
  clip-path: var(--notch);
  box-shadow: inset 0 4px var(--chrome-lit);
  color: var(--field-ink);
}
.rotate-card .px { --px: 48px; rotate: -90deg; animation: swing 1.6s ease-in-out infinite }
.rotate-card b { font-family: var(--display); font-size: 13px; line-height: 1.5 }
.rotate-card span:last-child { font-size: 12px; color: var(--field-dim) }
@keyframes swing { 50% { rotate: -35deg } }

/* The first thing painted on a game page: where the engine bytes are going, then the
   press that starts the run. It lives in the HTML so it is on screen before shell.js
   has even loaded. */
.boot {
  position: fixed;
  z-index: 35;
  inset: 0;
  display: grid;
  place-items: center;
  padding: 20px;
  background: var(--page);
  transition: opacity .22s;
}
.boot.done { opacity: 0; pointer-events: none }
.boot-card {
  width: min(100%, 340px);
  display: grid;
  gap: 12px;
  justify-items: center;
  text-align: center;
  padding: 22px 20px;
  background: var(--chrome);
  border: 4px solid var(--chrome-edge);
  clip-path: var(--notch);
  box-shadow: inset 0 4px var(--chrome-lit);
}
.boot-card h2 { margin: 0; font-family: var(--display); font-size: 15px; line-height: 1.5; color: var(--field-ink) }
/* The game's own shipped desktop icon, 48px of art shown at exactly twice its size so
   every source pixel stays a clean 2x2 block. It is the game saying it is loading. */
.boot-art { width: 96px; height: 96px; display: block; image-rendering: pixelated }
.boot-label { margin: 0; font-size: 12px; letter-spacing: .5px; color: var(--field-dim) }
.boot.failed .boot-label { color: #a32c14 }
.boot-bar {
  position: relative;
  width: 100%;
  height: 18px;
  background: var(--field);
  border: 3px solid var(--chrome-edge);
  clip-path: var(--notch);
  overflow: hidden;
}
.boot-bar span { display: block; height: 100%; width: 0; background: #f4b41b; box-shadow: inset 0 3px #ffe08a; transition: width .18s }
/* No Content-Length from the host: slide a block instead of freezing at zero. */
.boot.unknown .boot-bar span { width: 34%; animation: crawl 1.1s linear infinite }
@keyframes crawl { from { transform: translateX(-120%) } to { transform: translateX(340%) } }
.boot-play {
  padding: 14px 34px;
  font-family: var(--display);
  font-size: 12px;
  background: #6fb546;
  border: 4px solid #2f5c25;
  clip-path: var(--notch);
  box-shadow: inset 0 4px #a6dd7c;
  color: #10240b;
}
.boot-play:active { box-shadow: inset 0 4px #00000040 }

@media (max-width:520px) {
  /* Three goal chips will not fit beside the title on a phone, so the goals take a
     row of their own instead of being clipped by the purse. */
  .hud { gap: 6px; padding: 4px 6px; flex-wrap: wrap }
  .goals { flex: 1 1 100%; order: 3; justify-content: flex-start; flex-wrap: wrap; overflow: visible }
  .brand h1 { font-size: 14px }
  .goal { font-size: 11px; padding: 3px 5px }
  .goal i { --px: 20px; width: 20px; height: 20px }
  .purse { font-size: 15px }
  .purse .px { --px: 20px }
}
/* Landscape on a phone: the HUD becomes a left rail so the stage owns the full
   height — the same bar letterboxed above an 800x600 scene left a strip nobody
   could play. The profile menu pops out of the rail's right side instead of
   hanging off its bottom edge, which would be off-screen. */
@media (orientation:landscape) and (max-height:430px) {
  .frame { grid-template-columns: auto minmax(0,1fr); grid-template-rows: minmax(0,1fr); gap: 8px }
  .hud { flex-direction: column; flex-wrap: nowrap; align-items: center; justify-content: flex-start; width: 88px; padding: 8px 6px; gap: 8px }
  .brand { flex: none; text-align: center }
  .brand h1 { font-size: 11px; white-space: normal; line-height: 1.3 }
  .kicker { font-size: 10px }
  .goals { order: 0; flex: 0 1 auto; flex-direction: column; flex-wrap: nowrap; justify-content: flex-start; gap: 4px; overflow-y: auto; overflow-x: hidden }
  .purse { flex-direction: column; gap: 2px; font-size: 13px }
  .profile { margin-top: auto; width: 40px; height: 40px }
  .menu { left: calc(100% + 10px); right: auto; top: 0; width: 240px }
}
@media (prefers-reduced-motion:reduce) { *, *::before, *::after { transition: none !important; animation: none !important } }
