/* SEAM/60 shell. Inked Dark, the same kit the hub is drawn in: near-black
   canvas, warm off-white text, one warm amber accent. The hub used to hand the
   player to a navy page with a magenta button — a coherent product does not
   change palette under a click, so the shell now reads its colours from the
   site's own tokens and only the arena inside the canvas keeps its own art.

   Square corners and no shadows stay: that is this surface's line against the
   site around it, and it is a drawing decision, not a palette one. Relative
   asset paths only, so the bundle runs the same from our origin, a store frame,
   or a folder on disk. */

@font-face {
  font-family: "Fraunces";
  font-style: normal;
  font-weight: 600;
  src: url("assets/fonts/Fraunces144ptSoft-SemiBold.woff2") format("woff2");
  font-display: swap;
}

@font-face {
  font-family: "IBM Plex Sans";
  font-style: normal;
  font-weight: 400 600;
  src: url("assets/fonts/IBMPlexSans-Regular.woff2") format("woff2");
  font-display: swap;
}

* { box-sizing: border-box; }
button, textarea { font: inherit; }

html, body { height: 100%; }

body {
  background: var(--cn-color-canvas);
  color: var(--cn-color-ink);
  font-family: "IBM Plex Sans", Arial, sans-serif;
  margin: 0;
  overflow: hidden;
}

.frame {
  display: flex;
  flex-direction: column;
  gap: .5rem;
  height: 100%;
  margin: 0 auto;
  max-width: 68rem;
  padding: clamp(.5rem, 2vw, 1rem);
}

.game-bar {
  align-items: baseline;
  display: flex;
  flex: 0 0 auto;
  flex-wrap: wrap;
  gap: .3rem 1.4rem;
  justify-content: space-between;
}

.bar-identity { align-items: baseline; display: flex; flex-wrap: wrap; gap: .3rem .8rem; min-width: 0; }

.bar-identity h1 {
  font-family: Fraunces, Georgia, serif;
  font-size: 1.25rem;
  font-weight: 600;
  letter-spacing: -.01em;
  margin: 0;
}

.wordmark {
  color: inherit;
  font-size: .7rem;
  font-weight: 600;
  letter-spacing: .14em;
  text-decoration: none;
  text-transform: uppercase;
}

.wordmark:hover, .wordmark:focus-visible { text-decoration: underline; text-underline-offset: .2em; }

/* Four readings, always in the same order and always the same width, so the
   clock does not shove the score sideways as it counts down. */
.scoreboard { display: flex; flex-wrap: wrap; gap: .3rem 1rem; }
.scoreboard p { font-size: .9rem; font-weight: 600; margin: 0; font-variant-numeric: tabular-nums; }
#clock-label { min-width: 3.6rem; text-align: right; }

.play-wrap {
  align-items: center;
  container-type: size;
  display: flex;
  flex: 1 1 auto;
  justify-content: center;
  min-height: 0;
}

/* The stage carries the 960:560 ratio and letterboxes in either direction, so
   getBoundingClientRect always equals the drawn area — which is what the
   pointer maths divides by to place a seam under a finger. */
.stage-frame {
  height: min(100cqh, 100cqw * 560 / 960);
  position: relative;
  width: min(100cqw, 100cqh * 960 / 560);
}

#field {
  background: var(--cn-game-field);
  display: block;
  height: 100%;
  touch-action: none;
  width: 100%;
}

.game-controls { align-items: center; display: flex; flex: 0 0 auto; flex-wrap: wrap; gap: .8rem; }
.game-controls p { font-size: .9rem; margin: 0; }

/* On this surface Signal is a genuine amber, so the on-fill label reaches
   9.27:1 and the primary control can be a filled button with words on it. */
button {
  background: var(--cn-color-amber);
  border: 2px solid var(--cn-color-amber);
  color: var(--cn-color-on-fill);
  cursor: pointer;
  font-weight: 600;
  padding: .5rem .8rem;
}

/* A filled control moves *within* the accent family when it responds; it does
   not swap its fill for a surface token. Wash is a surface: on this polarity it
   washes down to #1a262a, which is 1.13:1 against Panel. Using it as the fill
   here took the primary button from 8.38:1 to 1.13:1 against the card it sits
   on — the fill stopped existing and only the 2px stroke was left. The label
   contrast was checked and passed at 13.52:1, which is why this survived: the
   text was legible on a control that was no longer visible as a control.
   It bit hardest at the one moment the game exists for. showPanel() focuses its
   own first action, so "Share this result" rendered in this vanished state the
   instant a run ended, handing the only amber on screen to "Play again".
   Link is Signal one step lighter — 9.63:1 on Panel, label at 10.65:1 — so the
   control brightens on a dark surface instead of dimming into it, and the ring
   sits outside the border where it reads against Panel at 8.38:1. */
button:hover, button:focus-visible {
  background: var(--cn-color-link);
  border-color: var(--cn-color-link);
  color: var(--cn-color-on-fill);
  outline: 2px solid var(--cn-color-amber);
  outline-offset: 2px;
}

/* The quiet control is a stroke, not a fill. Ember's overlay language uses a
   two-pixel Signal border even when the control yields the fill to its sibling. */
button.quiet { background: transparent; border-color: var(--cn-color-amber); color: var(--cn-color-ink); }

/* A stroked control has no fill to brighten, and promoting it to the accent on
   hover would put two filled controls on screen at once. This is the one place
   Wash is the right token: it lifts the empty interior without claiming the
   accent, and the label stays Ink. */
button.quiet:hover, button.quiet:focus-visible {
  background: var(--cn-color-mint);
  border-color: var(--cn-color-amber);
  color: var(--cn-color-ink);
}

a:focus-visible, textarea:focus-visible {
  outline: 2px solid var(--cn-color-amber);
  outline-offset: 2px;
}

.visually-hidden {
  clip-path: inset(50%);
  height: 1px;
  overflow: hidden;
  position: absolute;
  white-space: nowrap;
  width: 1px;
}

/* Ready, paused, result and error are the same panel wearing different words.
   It sits over the field rather than pushing it out of frame.

   It is sized to its own words, not to the box behind it. Both offsets are set
   on each axis with `margin: auto`, which centres a definite size and stretches
   an automatic one — so `max-content` on the block axis and a capped inline size
   are what make this a card in the middle of the field rather than a full-bleed
   sheet with its text in one corner. */
.panel {
  align-content: start;
  background: var(--cn-color-panel);
  block-size: max-content;
  border: 2px solid var(--cn-color-amber);
  bottom: 0;
  display: grid;
  gap: .6rem;
  inline-size: min(100%, 34rem);
  left: 0;
  margin: auto;
  max-height: 100%;
  overflow-y: auto;
  padding: clamp(.75rem, 2.4vw, 1.4rem);
  position: absolute;
  right: 0;
  top: 0;
}

.panel[hidden] { display: none; }

.panel .eyebrow {
  font-size: .8rem;
  font-weight: 600;
  letter-spacing: .12em;
  margin: 0;
  text-transform: uppercase;
}

.panel h2 {
  font-family: Fraunces, Georgia, serif;
  font-size: clamp(1.1rem, 3vw, 1.6rem);
  margin: 0;
}

#panel-body p { font-size: .85rem; line-height: 1.35; margin: 0 0 .5rem; max-width: 46ch; }
#panel-body p:last-child { margin-bottom: 0; }

.panel-actions { display: flex; flex-wrap: wrap; gap: .6rem; }

.poster { display: grid; gap: .5rem; grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr); margin: 0; }
.poster[hidden] { display: none; }
.poster img { border: 2px solid var(--cn-color-edge); display: block; width: 100%; }
.poster figcaption { display: grid; }

.poster textarea {
  background: var(--cn-color-canvas);
  border: 2px solid var(--cn-color-edge);
  color: var(--cn-color-ink);
  font-size: .85rem;
  line-height: 1.35;
  padding: .4rem .5rem;
  resize: none;
  width: 100%;
}

.noscript-note {
  border: 2px solid var(--cn-color-amber);
  font-size: .85rem;
  line-height: 1.35;
  margin: 0;
  padding: .6rem .8rem;
}

@media (max-width: 40rem) {
  .poster { grid-template-columns: 1fr; }
  .scoreboard { gap: .2rem .7rem; }
  .scoreboard p { font-size: .8rem; }

  /* The panel is positioned against the stage, and on a portrait phone the
     960:560 stage letterboxes to a ~214px strip — shorter than the ready
     panel's own 330px of content, so the start control and the third
     instruction line rendered inside its scroll overflow, 62px below the
     visible edge. A player arriving from the hub saw no controls and no way in.

     Below 40rem the panel positions against .play-wrap instead, which is the
     tall box the letterboxing leaves empty either side of the strip. Same
     markup, same states, no change to the field: the dialog simply stops being
     bounded by the drawing it sits over. #field is the only other child of
     .stage-frame and is in normal flow, so dropping the containing block costs
     nothing. */
  .play-wrap { position: relative; }
  .stage-frame { position: static; }
}

/* Decoration answers the preference; the game itself does not. A minute-long
   run has to keep moving or there is nothing to play. game.js drops the spark
   trail and the fold ripple when this matches. */
@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; transition: none !important; }
}
