/* por.bi — shared styles.
   Palette sampled straight out of porbi.gif. No words anywhere on this site,
   so everything here is shape, colour and motion. */

:root {
  --yolk:  #f4b436;  /* porbi's body            */
  --plum:  #1d0857;  /* porbi's outline + ears  */
  --blush: #cf7382;  /* porbi's cheeks          */
  --cream: #fffdf7;
  --dusk:  #453c7d;  /* dark-mode ground; kept well clear of --plum
                        so the ears stay visible against it          */

  --bg: var(--cream);
  --ink: #2e2359;
  --muted: #6b5f93;
  --card: #fff;
  --line: rgba(46, 35, 89, 0.14);

  --round: ui-rounded, "Hiragino Maru Gothic ProN", Quicksand, Comfortaa, system-ui, sans-serif;
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: var(--dusk);
    --ink: #f9b233;
    --muted: #cfc4f0;
    --card: rgba(255, 255, 255, 0.07);
    --line: rgba(255, 255, 255, 0.18);
  }
}

* { margin: 0; padding: 0; box-sizing: border-box; }

/* iOS Safari treats a long-press anywhere as the start of text selection and
   will happily grab the whole page (Android doesn't). Nothing on this site is
   meant to be selected — there are no words — so selection is off everywhere
   and handed back to the one place typing happens. */
* {
  -webkit-user-select: none;
  user-select: none;
}

input, textarea {
  -webkit-user-select: text;
  user-select: text;
}

/* The same long-press on a picture brings up the save-image sheet mid-play. */
img, canvas, svg {
  -webkit-touch-callout: none;
}

html { height: 100%; }

/* body grows past the viewport rather than being pinned to it — otherwise a
   vertically-centred page taller than the screen clips its own top off with
   no way to scroll back up. Pages that must not scroll set overflow: hidden. */
body {
  min-height: 100%;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--round);
  -webkit-font-smoothing: antialiased;
}

/* Order matters: the last one a browser understands is the one it uses, and
   pixelated is the one we want. crisp-edges is the fallback underneath it. */
img.pix, canvas.pix {
  image-rendering: crisp-edges;
  image-rendering: pixelated;
}

/* ---------- centring porbi ---------- */

/* porbi's body is not in the middle of its own frame: in the 128px frame the
   body circle sits at (63.25, 73.10), which is 9.1px low. Centring the picture
   therefore leaves porbi sitting low in whatever space it is given, so centre
   the body instead. The standalone `translate` property is used rather than
   `transform` so it composes with the hop, the squish and the breathing
   without any of them having to know about it. */
.porbi-anchor {
  translate: 0.5859% -7.1094%;
}

/* And when porbi is scaled or spun, the body is what it should pivot about. */
.porbi-pivot {
  transform-origin: 49.4141% 57.1094%;
}

/* ---------- the tappable, jumping porbi ---------- */

button.porbi {
  display: block;
  margin: 0 auto;
  border: none;
  background: none;
  padding: 0;
  cursor: pointer;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}

button.porbi img {
  display: block;
  width: min(60vmin, 320px);
  height: auto;
  translate: 0.5859% -7.1094%;      /* centre the body, not the picture */
  transform-origin: bottom center;  /* but hop from the feet */
  user-select: none;
  -webkit-user-drag: none;
}

button.porbi.jump img {
  animation: porbi-jump 0.55s cubic-bezier(0.28, 0.6, 0.36, 1);
}

@keyframes porbi-jump {
  0%   { transform: scale(1, 1) translateY(0); }
  15%  { transform: scale(1.15, 0.82) translateY(0); }
  45%  { transform: scale(0.92, 1.1) translateY(-40%); }
  70%  { transform: scale(1, 1) translateY(0); }
  82%  { transform: scale(1.12, 0.88) translateY(0); }
  100% { transform: scale(1, 1) translateY(0); }
}

/* ---------- landmarks ---------- */

main.stack {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--stack-gap, 1rem);
  width: 100%;
}

/* The only words on the site are the ones screen readers read out. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ---------- back arrow ---------- */

/* The wrapper does the positioning so the <nav> landmark takes no space in
   the page's own flex layout. */
nav.backnav {
  position: fixed;
  top: max(0.75rem, env(safe-area-inset-top));
  left: max(0.75rem, env(safe-area-inset-left));
  z-index: 20;
}

.back {
  display: grid;
  place-items: center;
  width: 2.5rem;
  height: 2.5rem;
  border-radius: 50%;
  border: 2px solid var(--line);
  background: var(--card);
  backdrop-filter: blur(6px);
  color: var(--ink);
  text-decoration: none;
  transition: transform 0.15s ease, border-color 0.15s ease;
}

.back svg { width: 1.1rem; height: 1.1rem; display: block; }

.back:hover { transform: translateX(-2px); border-color: currentColor; }

/* On the loud pages the arrow needs to hold its own against the background. */
.back.on-color {
  background: rgba(0, 0, 0, 0.32);
  border-color: rgba(255, 255, 255, 0.45);
  color: #fff;
}

/* ---------- controls ---------- */

.btn {
  appearance: none;
  display: grid;
  place-items: center;
  width: 2.6rem;
  height: 2.6rem;
  border: 2px solid var(--line);
  border-radius: 50%;
  background: var(--card);
  color: var(--ink);
  padding: 0;
  cursor: pointer;
  touch-action: manipulation;
  transition: transform 0.12s ease, border-color 0.12s ease;
}

.btn svg { width: 1.15rem; height: 1.15rem; display: block; }
.btn:hover { transform: translateY(-1px); border-color: currentColor; }
.btn:active { transform: translateY(1px); }

:where(a, button, input, [tabindex]):focus-visible {
  outline: 3px solid var(--yolk);
  outline-offset: 3px;
}

input[type="range"] { accent-color: var(--yolk); }

/* ---------- the tile grid on /more ---------- */

.tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(112px, 1fr));
  gap: 0.85rem;
  width: 100%;
  margin: 0 auto;
  list-style: none;
}

.tiles a {
  display: grid;
  place-items: center;
  aspect-ratio: 1;
  border: 2px solid var(--line);
  border-radius: 22px;
  background: var(--card);
  overflow: hidden;
  transition: transform 0.16s ease, border-color 0.16s ease;
}

.tiles a:hover { transform: translateY(-4px); border-color: var(--yolk); }
.tiles a:active { transform: translateY(-1px); }

.tiles svg { width: 62%; height: 62%; display: block; overflow: visible; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
}

/* ---------- the halo ---------- *
   A slow sweep of yolk around a rounded box, plus a soft breathing glow. Born
   on the pairing modal (the ring around an answer code); /more/ uses the same
   one to show which tile the other porbi is on.

   The sweep is a conic gradient masked down to a border band — two mask
   layers, content-box against everything, composited away — so only a rim
   ever shows, whatever size the host is. The first version rotated an
   oversized gradient square instead and relied on the content to cover the
   middle, which worked on a QR code and flooded a whole /more/ tile in
   yolk. The angle animates rather than the element, so the corners never
   swing. Whatever wears this needs a border-radius of its own; the ring
   inherits it. */
@property --halo-from {
  syntax: '<angle>';
  inherits: false;
  initial-value: 0turn;
}
.halo {
  position: relative;
  animation: halo-glow 2.4s ease-in-out infinite;
}
.halo::before {
  content: '';
  position: absolute; inset: 0;
  border-radius: inherit;
  padding: 3px;
  background: conic-gradient(from var(--halo-from),
              var(--yolk), rgba(244, 180, 54, 0) 30%,
              rgba(244, 180, 54, 0) 70%, var(--yolk));
  -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  mask-composite: exclude;
  animation: halo-sweep 2.6s linear infinite;
  pointer-events: none;
}
.halo > * { position: relative; }
@keyframes halo-sweep { to { --halo-from: 1turn; } }
@keyframes halo-glow {
  0%, 100% { box-shadow: 0 0 0 0 rgba(244, 180, 54, 0.42); }
  50%      { box-shadow: 0 0 1.1rem 0.15rem rgba(244, 180, 54, 0.42); }
}
@media (prefers-reduced-motion: reduce) {
  .halo, .halo::before { animation: none; }
}

/* The mute button the noisy games wear. Same shape as .back, same reason it
   has an on-color variant: over a dark game the tokens are the wrong end of
   the palette. Where it sits is the page's business; how it looks is not. */
.sound {
  z-index: 5;
  width: 2.1rem; height: 2.1rem;
  display: grid; place-items: center;
  border-radius: 999px;
  border: 2px solid var(--line);
  background: var(--card);
  color: var(--ink);
  cursor: pointer;
  touch-action: manipulation;
  opacity: 0.7;
}
.sound svg { width: 1.05rem; height: 1.05rem; display: block; }
.sound:hover { opacity: 1; }
.sound.on-color {
  border-color: rgba(255, 255, 255, 0.35);
  background: rgba(0, 0, 0, 0.3);
  color: #fff;
}

/* A page that is one canvas filling the window — the games, the spinners,
   club. The page puts `canvas-page` on <body> beside its own name and
   `fullbleed` on the canvas itself; an opt-in class rather than a descendant
   selector, because club has nine canvases and only one of them is the
   stage. overscroll-behavior is half the point: without it a swipe the game
   has already handled still rubber-bands the page on iOS. */
body.canvas-page { overflow: hidden; overscroll-behavior: none; }
canvas.fullbleed {
  position: fixed; inset: 0;
  width: 100%; height: 100%;
  display: block;
  touch-action: none;
}

/* The dimmed ground a sheet drops onto — the pairing sheet and the debug
   modal both sit on one. Shared rather than scoped because they are two
   components now, and z-index 100 because a page may reach 40 for its own
   overlays. */
.scrim {
  position: fixed; inset: 0; z-index: 100;
  display: grid; place-items: center;
  /* On a phone held sideways the notch is down one edge, so the padding that
     keeps a sheet off the edges has to know about the insets — otherwise the
     modal is centred in the screen and clipped by the hardware. */
  padding: max(1.25rem, env(safe-area-inset-top)) max(1.25rem, env(safe-area-inset-right))
           max(1.25rem, env(safe-area-inset-bottom)) max(1.25rem, env(safe-area-inset-left));
  background: rgba(12, 7, 32, 0.62);
  -webkit-backdrop-filter: blur(4px);
  backdrop-filter: blur(4px);
  animation: fade 0.22s ease both;
}
@keyframes fade { from { opacity: 0; } }
/* And the sheet that rises onto it — same reason it is shared. */
@keyframes rise { from { opacity: 0; transform: translateY(10px) scale(0.97); } }

/* The corner stack, top right. The pairing dot has the first slot always; the
   refresh button takes the second whenever a new build is waiting, and the
   debug button reads this to step out of its way. It lives here rather than
   in either component because it is the one fact both of them need and
   neither owns — the same reason .scrim and .halo are here. */
body.updating { --corner-shift: 3.2rem; }

/* "Turn me sideways", for the pages that only make sense that way — /club/'s
   island and /duel/'s corridor. src/lib/landscape.ts puts `portrait-hold` on
   <body> while the phone is upright, and these rules do the rest: the game
   goes away and a pictogram of a phone turning takes the screen. It is here
   rather than in either page because both need it and neither owns it, the
   same reason .scrim and the corner stack are here.

   A page opts in with the markup — a .turnme holding an <svg> whose rotating
   part is class="ph" — and by wearing `portrait-hold`, which the module does
   for it. Nothing here is a word: the arrow and the phone say it. */
.turnme { position: fixed; inset: 0; display: none; place-items: center;
          color: var(--ink); z-index: 3; }
body.portrait-hold .turnme { display: grid; }
.turnme svg { width: min(52vmin, 230px); height: auto; }
.turnme .ph { transform-origin: 60px 56px; animation: turnme 2.8s ease-in-out infinite; }
@keyframes turnme {
  0%, 28% { transform: rotate(0deg); }
  62%, 100% { transform: rotate(-90deg); }
}
