/* Starpath design system.
 *
 * Self-contained on purpose (the 4x3 contract): the token VALUES below
 * mirror docs/assets/css/tn-tokens.css; if a shared value changes, update
 * both files. Visual language: Tommy's site (navy ink, coral, orange CTA,
 * warm paper, chunky borders, hard offset shadows, Libre Baskerville +
 * Raleway) with Starpath's own night-sky surfaces for the cosmos moments.
 *
 * Rendering laws carried from the labs (do not "clean up"):
 *  - state lives in inline styles or defaults; keyframes carry ONLY
 *    transforms (a throttled tab freezes animations mid-flight)
 *  - water/celebration cleanup runs on idempotent timeouts; Animation
 *    .onfinish never fires in throttled or embedded tabs
 *  - clip inset transitions are forbidden (Safari strands them and opens
 *    gaps between merged cells); only corner radii melt
 */

@font-face{
  font-family: 'Libre Baskerville';
  font-weight: 700;
  font-style: normal;
  font-display: swap;
  src: url('fonts/libre-baskerville-700.woff2') format('woff2');
}
@font-face{
  font-family: 'Libre Baskerville';
  font-weight: 700;
  font-style: italic;
  font-display: swap;
  src: url('fonts/libre-baskerville-700italic.woff2') format('woff2');
}
@font-face{
  font-family: 'Raleway';
  font-weight: 500 800;
  font-style: normal;
  font-display: swap;
  src: url('fonts/raleway.woff2') format('woff2');
}

:root{
  --ink: #023047;
  --muted: #5c677d;
  --paper: #fbf5ea;
  --card: #ffffff;
  --coral: #E76F51;
  --cta: #fb8500;
  --amber: #ffb703;
  --bad: #c0392b;
  --bad-bg: #fbe9e5;   /* mirrors --down-bg */
  --gold: #e7b416;
  --ok: #2f8f4e;
  --night: #052a44;
  --night-star: #dfe9f2;
  --night-dim: #7d94a9;
  --serif: 'Libre Baskerville', Georgia, 'Times New Roman', serif;
  --sans: 'Raleway', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, sans-serif;
  --shadow: 5px 5px 0 rgba(2, 48, 71, .16);
  --shadow-sm: 3px 3px 0 rgba(2, 48, 71, .12);
}

*{box-sizing: border-box; -webkit-tap-highlight-color: transparent}
html, body{margin: 0; background: var(--paper); color: var(--ink);
  font-family: var(--sans); font-weight: 500}
body{padding: 12px 12px 56px; display: flex; flex-direction: column;
  align-items: center; gap: 12px; min-height: 100dvh}
[hidden]{display: none !important}
a{color: var(--ink)}

/* cross-document view transitions: progressive enhancement, instant nav
 * where unsupported; the mark persists, the board morphs to its week tile */
@view-transition{navigation: auto}
.vt-mark{view-transition-name: sp-mark}
.vt-board{view-transition-name: sp-board}
@media (prefers-reduced-motion: reduce){
  .vt-mark, .vt-board{view-transition-name: none}
}

/* ---------------------------------------------------------- page chrome */
.site-head{display: flex; flex-direction: column; align-items: center;
  gap: 8px; width: 100%; max-width: 520px}
.brand{display: flex; align-items: center; gap: 9px; text-decoration: none}
.brand .mark{width: 30px; height: 30px; flex: none}
.brand .name{font-family: var(--serif); font-weight: 700; font-size: 21px;
  letter-spacing: .02em}
.site-nav{display: flex; gap: 4px; flex-wrap: wrap; justify-content: center}
/* six items wrap as five-plus-orphan on a phone; two deliberate rows of
 * three read as a design instead of an accident */
@media (max-width: 520px){
  .site-nav{display: grid; grid-template-columns: repeat(3, auto);
    justify-content: center; justify-items: center}
}
.site-nav a{font-size: 12.5px; font-weight: 700; text-decoration: none;
  padding: 5px 10px; border: 2px solid transparent}
.site-nav a.here{border-color: var(--ink); background: var(--card);
  box-shadow: 2px 2px 0 rgba(2, 48, 71, .14)}
.site-nav a:not(.here):hover{border-color: rgba(2, 48, 71, .25)}
/* quiet corner control (2026-08-05, Tommy's ask: out of the middle,
 * full language names); scrolls away with the page on purpose */
.top-actions{position: absolute;
  top: max(10px, env(safe-area-inset-top));
  right: max(10px, env(safe-area-inset-right));
  display: flex; gap: 6px; align-items: center; z-index: 5}
.top-actions button{font-size: 11.5px; padding: 4px 9px;
  border-width: 1.5px; box-shadow: 2px 2px 0 rgba(2, 48, 71, .12)}

h1{font-family: var(--serif); font-size: 20px; margin: 0; text-align: center}
h1 small{display: block; font-family: var(--sans); font-size: 12.5px;
  font-weight: 500; color: var(--muted); margin-top: 4px; line-height: 1.45;
  max-width: 370px}
h2{font-family: var(--serif); font-size: 17px; margin: 0}

button{font: inherit; font-size: 13px; font-weight: 600; padding: 7px 11px;
  border: 2px solid var(--ink); background: var(--card); color: var(--ink);
  cursor: pointer; border-radius: 0}
button:active{transform: translateY(1px)}
button.on{background: var(--amber)}
button.go{background: var(--cta); color: #fff; font-weight: 700}
button.warn{border-color: var(--bad); color: var(--bad)}
button:disabled{opacity: .45; cursor: default}
button.linkish{border: none; background: none; padding: 3px 4px;
  text-decoration: underline; font-weight: 600; font-size: 12.5px;
  color: var(--muted)}

.panel{width: 100%; max-width: 520px; background: var(--card);
  border: 2.5px solid var(--ink); box-shadow: var(--shadow);
  padding: 12px 13px; display: flex; flex-direction: column; gap: 10px}
.row{display: flex; flex-wrap: wrap; gap: 7px; align-items: center}
.lbl{font-size: 11px; text-transform: uppercase; letter-spacing: .08em;
  color: var(--muted); width: 100%; font-weight: 700}
.hint-text{font-size: 12px; color: var(--muted); line-height: 1.5; margin: 0}
/* the mode strip on Dine data: the drawing's three columns as a state
 * indicator. The current way is filled amber (the game's «current»
 * color, like button.on and .day-pick.cur); the others stay quiet.
 * Not buttons on purpose: the actions live in the row below. */
.mode-strip{display: flex; gap: 7px; flex-wrap: wrap}
.mode-seg{border: 2px solid rgba(2, 48, 71, .25); padding: 5px 11px;
  font-size: 12.5px; font-weight: 600; color: var(--muted);
  background: var(--card)}
.mode-seg.cur{border-color: var(--ink); background: var(--amber);
  color: var(--ink); font-weight: 800}
/* an explainer drawing inside a panel (Dine data). The border matches the
 * panel's own so the picture reads as a plate rather than a screenshot,
 * and the caption below it carries the drawing's numbered rows in the
 * reader's language, since the drawing itself is one English image. */
.explain-fig{margin: 0; display: flex; flex-direction: column; gap: 7px}
.explain-fig img{display: block; width: 100%; height: auto;
  border: 2px solid var(--ink); background: var(--paper)}
.explain-fig figcaption{font-size: 12px; color: var(--muted);
  line-height: 1.5}
.store-warn{display: none; width: 100%; max-width: 520px; background: var(--bad-bg);
  border: 2.5px solid var(--bad); color: var(--bad); padding: 9px 11px;
  font-size: 13px; font-weight: 600}
.store-warn.show{display: block}
.site-foot{margin: 26px auto 10px; max-width: 520px; text-align: center;
  font-size: 12px; color: var(--muted); line-height: 1.5}
.site-foot a{color: var(--muted)}

/* one-off notices (ui/notice.js): quiet paper cards under the header,
 * amber edge for «worth a glance», never the alarm red of .store-warn */
.notices{width: 100%; max-width: 520px; display: flex;
  flex-direction: column; gap: 8px}
.notices:empty{display: none}
.notice{display: flex; gap: 10px; align-items: flex-start;
  background: var(--card); border: 2px solid var(--ink);
  border-left: 6px solid var(--amber); box-shadow: var(--shadow-sm);
  padding: 9px 11px; font-size: 13px; line-height: 1.5}
.notice-text{flex: 1}
.notice-x{border: none; background: none; padding: 0 2px; font-size: 14px;
  font-weight: 700; color: var(--muted); line-height: 1.3}

/* the two ambient lines around the board wear quiet cards (Tommy,
 * 2026-08-22): bare grey text read as unanchored beside the chunky
 * buttons. The statusline is a run-status notice (the uncounted line,
 * «Ny uke») and wears the notice's amber edge; the rule line is
 * standing reference and stays edge-free. No display property here,
 * so the hidden attribute keeps working. */
#statusline, #rule-line, #nextsky{background: var(--card);
  border: 2px solid var(--ink); box-shadow: var(--shadow-sm);
  padding: 9px 11px; font-size: 13px}
#statusline{border-left: 6px solid var(--amber)}

/* the changelog page: day headers + entry lists inside one panel */
.changelog h3{font-family: var(--serif); font-size: 15px;
  margin: 14px 0 0; color: var(--ink)}
.changelog h3:first-child{margin-top: 0}
.changelog ul{margin: 0; padding-left: 19px}
.changelog li{margin: 0 0 6px}
.changelog li:last-child{margin-bottom: 0}

/* ------------------------------------------------------------- the sky */
/* max-width + scroll = the backstop: should a stale viewport read ever
 * size the sky too wide again (Pixel find 2026-08-17), the board pans
 * instead of losing both flanks off-screen; inert when sizing is right */
#stage{position: relative; max-width: 100%; overflow-x: auto}
#board{position: relative; background: var(--card);
  border: 2.5px solid var(--ink); touch-action: none; user-select: none;
  -webkit-user-select: none;
  /* content-box on purpose: cells fill exactly n*cs INSIDE the frame.
     border-box let the frame eat the content box and the bottom/right
     rows overflowed the line by 5px (phone find 2026-08-04) */
  box-sizing: content-box;
  width: calc(var(--n) * var(--cs)); height: calc(var(--n) * var(--cs))}
#grid{position: absolute; inset: 0; pointer-events: none;
  transition: opacity .7s ease;
  background-image:
    repeating-linear-gradient(to right, rgba(2,48,71,.16) 0 1px, transparent 1px var(--cs)),
    repeating-linear-gradient(to bottom, rgba(2,48,71,.16) 0 1px, transparent 1px var(--cs))}
#seeds, #cells, #fillets, #comets, #dots, #overlay{position: absolute;
  inset: 0; pointer-events: none}
#overlay{overflow: hidden}
#tether{position: absolute; inset: 0; pointer-events: none}
.fillet{position: absolute; pointer-events: none}
.comet{position: absolute; left: 0; top: 0; width: 14px; height: 14px;
  margin: -7px 0 0 -7px; border-radius: 50%; opacity: 0;
  pointer-events: none; will-change: transform, opacity}
.seed{position: absolute; width: var(--cs); height: var(--cs);
  left: calc(var(--c) * var(--cs)); top: calc(var(--r) * var(--cs));
  background: rgba(2, 48, 71, .06)}
.cell{position: absolute; width: var(--cs); height: var(--cs);
  left: calc(var(--c) * var(--cs)); top: calc(var(--r) * var(--cs))}
.clip{position: absolute; inset: 2px; border-radius: 8px; overflow: hidden;
  transform: rotate(0deg)}
#board.live .clip{
  transition: border-top-left-radius .14s, border-top-right-radius .14s,
    border-bottom-left-radius .14s, border-bottom-right-radius .14s;
  transition-timing-function: cubic-bezier(.3, .7, .3, 1)}
.cell.mirror .clip{transform: rotate(180deg)}
.flat{position: absolute; inset: 0; opacity: 0; background: var(--w)}
.blob{position: absolute; width: calc(var(--cs) * 4.4);
  height: calc(var(--cs) * 4.4); left: calc(var(--cs) * -2.2);
  top: calc(var(--cs) * -2.2); border-radius: 50%;
  background: var(--blobbg); opacity: 0}
/* the ink's wet sheet: a full-cell layer in the fill's dark shade
 * (background set by paintVars), flashed to a fraction and dried by
 * fillCell once the colour has landed */
.tint{position: absolute; inset: 0; opacity: 0}
.sheen{position: absolute; inset: 0; opacity: 0; background: rgba(255,255,255,.9)}
/* the glyph assist layer («Symboler», the underbar switch): one geometric
 * mark per galaxy, repeated in every claimed cell, so galaxy identity
 * survives with no color information at all. The node always exists
 * (ui/glyphs.js via board.js makeCell); this class is the whole on/off,
 * and the reveal drops it so the finished picture stays clean. Ink at a
 * third strength: present to a look, silent to a glance. */
.glyph{position: absolute; inset: 0; display: none; align-items: center;
  justify-content: center; color: var(--ink); opacity: .32;
  transition: opacity .25s ease; pointer-events: none}
#board.glyphs .glyph{display: flex}
/* the clip renders mirror cells under rotate(180); rotate the glyph back
 * so every mark reads upright (chevrons, slashes and waves carry
 * direction) */
.cell.mirror .glyph{transform: rotate(180deg)}
.glyph svg{width: 46%; height: 46%}
@media (prefers-reduced-motion: reduce){
  .glyph{transition: none}
}

/* the stars: small gold four-pointed, precise visual center */
.dot{position: absolute;
  width: clamp(15px, calc(var(--cs) * .38), 22px);
  height: clamp(15px, calc(var(--cs) * .38), 22px);
  left: calc(var(--x) * var(--cs)); top: calc(var(--y) * var(--cs));
  transform: translate(-50%, -50%)}
.dot svg{display: block; width: 100%; height: 100%; overflow: visible}
.dot svg path{fill: var(--gold); stroke: var(--ink); stroke-width: 1.6;
  paint-order: stroke; stroke-linejoin: round}
.dot::before{content: ''; position: absolute; inset: -5px;
  border-radius: 50%; border: 2.5px solid var(--cta); opacity: 0}
.dot.on::before{opacity: 1}
.dot.on svg{animation: breathe 2.6s ease-in-out infinite}
@keyframes breathe{0%, 100%{transform: scale(1)} 50%{transform: scale(1.16)}}
.dot.entrance{animation: kindle .55s ease-out backwards}
@keyframes kindle{from{opacity: 0} to{opacity: 1}}
.dot.hintpulse svg{animation: wave-hello .45s ease-in-out}
@keyframes wave-hello{0%, 100%{transform: scale(1)} 50%{transform: scale(1.35)}}
.dot.sealed svg path{filter: drop-shadow(0 0 3.5px rgba(255, 183, 3, .9))}
.dot.twinkle svg{animation: twinkle 1s ease-in-out}
@keyframes twinkle{0%, 100%{transform: scale(1)} 35%{transform: scale(1.5)}
  65%{transform: scale(.88)}}
.dot.target svg{animation: twinkle 1.25s ease-in-out infinite}
.dot.target::before{opacity: 1; animation: goalpulse 1.25s ease-in-out infinite}

/* motif particles (ui/particles.js): one salvo over the finished picture,
 * ported from the reveal lab. Pure motion; reduced-motion callers simply
 * never spawn them. */
.p-heart{position: absolute; font-size: 15px; color: var(--coral);
  opacity: 0; pointer-events: none; will-change: transform, opacity}
.p-spark{position: absolute; font-size: 14px; color: var(--gold);
  opacity: 0; pointer-events: none;
  text-shadow: 0 0 4px rgba(255, 183, 3, .8);
  will-change: transform, opacity}
.p-ray{position: absolute; width: 3px; height: 26px; border-radius: 2px;
  background: var(--amber); opacity: 0; pointer-events: none;
  transform-origin: 50% 0; will-change: transform, opacity}

.ghost{position: absolute; width: var(--cs); height: var(--cs);
  left: calc(var(--c) * var(--cs)); top: calc(var(--r) * var(--cs));
  border-radius: 8px; border: 2px dashed var(--bad); opacity: .85}
.shake{animation: shake .34s cubic-bezier(.36, .07, .19, .97)}
@keyframes shake{10%, 90%{transform: translateX(-2px)}
  20%, 80%{transform: translateX(3px)}
  30%, 50%, 70%{transform: translateX(-5px)}
  40%, 60%{transform: translateX(5px)}}

/* hint marks: spotlight, goal rings, area glow */
.spot{position: absolute; border-radius: 16px; pointer-events: none;
  box-shadow: 0 0 0 9999px rgba(2, 48, 71, .44)}
.goal{position: absolute; width: var(--cs); height: var(--cs);
  left: calc(var(--c) * var(--cs)); top: calc(var(--r) * var(--cs));
  border: 3px solid var(--cta); border-radius: 8px; pointer-events: none;
  box-shadow: 0 0 0 2.5px rgba(255,255,255,.95), 0 0 12px rgba(2,48,71,.4);
  animation: goalpulse 1.15s ease-in-out infinite}
.goal.bad{border-color: var(--bad)}
.goal.soft{border-width: 2px; border-style: dashed; animation: none;
  opacity: .75; box-shadow: 0 0 0 2px rgba(255,255,255,.85)}
@keyframes goalpulse{0%, 100%{transform: scale(1); opacity: .85}
  50%{transform: scale(1.07); opacity: 1}}
.glowcell{position: absolute; width: var(--cs); height: var(--cs);
  left: calc(var(--c) * var(--cs)); top: calc(var(--r) * var(--cs));
  background: rgba(255, 183, 3, .3); pointer-events: none}

/* the name plate */
#plate{position: absolute; left: 50%; bottom: 8%; transform: translateX(-50%);
  background: var(--card); border: 2.5px solid var(--ink);
  box-shadow: 4px 4px 0 rgba(2, 48, 71, .22); padding: 9px 18px 10px;
  text-align: center; white-space: nowrap}
#plate .plate-name{font-family: var(--serif); font-weight: 700;
  font-size: 21px; letter-spacing: .04em}
#plate .plate-date{font-size: 12px; color: var(--muted); margin-top: 1px}
#plate.stamp{animation: stamp .3s cubic-bezier(.2, 1.6, .4, 1)}
@keyframes stamp{0%{transform: translateX(-50%) scale(1.25) rotate(-2deg)}
  100%{transform: translateX(-50%) scale(1) rotate(0deg)}}

/* under-board rows + the hint/message card. Meta and buttons are STACKED
 * on purpose: as one wrapping row, the motif emoji appended to the meta
 * on solve pushed the buttons onto a new line at borderline widths, so
 * the chrome jumped at the exact moment of the win (Tommy, 2026-08-13) */
#underbar{width: 100%; max-width: 520px; display: flex;
  flex-direction: column; align-items: flex-start; gap: 8px}
/* line-height pinned: the motif emoji that joins on solve is taller than
 * the text, and without the pin it still nudged the buttons 2px down */
#boardmeta{font-size: 13.5px; font-weight: 700; line-height: 20px}
#boardmeta .stars{color: var(--cta); letter-spacing: .05em}
/* the solve playback transport (ui/playback.js): scrubber + position line
 * over one row of icon buttons. Same card language as the message card;
 * hidden except while a finished run is being walked. */
#playback{width: 100%; max-width: 520px; background: var(--card);
  border: 2.5px solid var(--ink); padding: 9px 12px;
  box-shadow: var(--shadow-sm);
  display: flex; flex-direction: column; gap: 8px}
#playback .pb-row{display: flex; align-items: center; gap: 8px}
#playback input[type=range]{flex: 1; min-width: 0; accent-color: var(--cta)}
#playback .pb-pos{font-size: 12.5px; font-weight: 600; color: var(--muted);
  white-space: nowrap; font-variant-numeric: tabular-nums}
#playback button{display: inline-flex; align-items: center;
  justify-content: center; padding: 7px 10px}
#playback button svg{display: block}
#playback .pb-spacer{flex: 1}
#msgline{width: 100%; max-width: 520px; background: var(--card);
  border: 2.5px solid var(--amber); padding: 9px 12px; font-size: 13.5px;
  line-height: 1.5; box-shadow: var(--shadow-sm);
  display: flex; flex-direction: column; gap: 7px}
#msgline.bad{border-color: var(--bad)}
.mrow{display: flex; align-items: center; gap: 8px}
/* the practice ladder's progress row (pages/tutorial.js): one star per
 * board, done rungs gold, the rest faint ink. Exists because the bare
 * «øv videre»-offer read as never-ending (Tommy, 2026-08-11). */
.ladder-stars{display: flex; align-items: center; gap: .22em;
  font-size: 15px; color: var(--muted)}
.ladder-stars .star-ico{opacity: .35}
.ladder-stars .star-ico.earned{opacity: 1; color: var(--gold)}
.ladder-label{font-size: 12px; color: var(--muted); margin-left: .45em}
.mtag{font-size: 10px; font-weight: 800; letter-spacing: .07em;
  text-transform: uppercase; padding: 2.5px 8px; border: 1.5px solid var(--ink);
  background: var(--amber); white-space: nowrap}
.mtag.bad{background: var(--bad-bg); border-color: var(--bad); color: var(--bad)}
.mnext{margin-left: auto; font-size: 12px; padding: 4px 10px; white-space: nowrap}
/* until the player has grown a hint once, the grow button pulses twice:
 * the ladder's continuation is the least discoverable part of the game */
.mnext.nudge, #toweek.nudge{animation: nudge 1.3s ease-in-out .7s 2 backwards}
@keyframes nudge{
  0%, 100%{transform: scale(1); box-shadow: none}
  30%{transform: scale(1.09); box-shadow: 0 0 0 3px rgba(255, 183, 3, .55)}
  60%{transform: scale(1)}}
.mtext{font-size: 13.5px; line-height: 1.5}
.mwhytext{font-size: 12.5px; color: var(--muted); line-height: 1.55;
  border-top: 1.5px dashed rgba(2, 48, 71, .28); padding-top: 7px}

/* ------------------------------------------------------------ result */
#result{border-color: var(--ok)}
#result h2{font-size: 19px; color: var(--ok)}
#rfacts{font-size: 14px; line-height: 1.5}
#rbadges{display: flex; flex-wrap: wrap; gap: 6px}
.badge{font-size: 11px; font-weight: 800; letter-spacing: .05em;
  text-transform: uppercase; padding: 3px 9px; border: 2px solid var(--ink);
  background: var(--amber); opacity: 0; transform: scale(1.3) rotate(-2deg);
  cursor: pointer;
  display: inline-flex; align-items: center; gap: 5px}
/* the badge glyph (ui/badge-glyphs.js): currentColor, so it draws in ink
 * on an earned badge and in muted on a locked one, and never needs its own
 * color rule */
.badge-ico{width: 15px; height: 15px; flex: none; display: block}
/* An honor's own face: night with gold, the card's colors, so shine
 * looks like shine everywhere it is EARNED. A locked honor on the wall
 * keeps the quiet locked look (the :not), only the ✦ says its kind. */
.badge.honor:not(.locked){background: var(--night); color: var(--gold);
  border-color: var(--ink)}
.badge.inn{animation: badgestamp .3s cubic-bezier(.2, 1.5, .4, 1) forwards}
.badge.still{opacity: 1; transform: none; animation: none}
@keyframes badgestamp{to{opacity: 1; transform: scale(1) rotate(0)}}
.badge-about{font-size: 12.5px; color: var(--muted); line-height: 1.5;
  border-top: 1.5px dashed rgba(2, 48, 71, .28); padding-top: 7px}
#cardholder{align-self: center; filter: drop-shadow(4px 4px 0 rgba(2,48,71,.18))}
#bigview{display: flex; flex-direction: column; align-items: center;
  gap: 8px; width: 100%; max-width: 520px}
#bigtext{font-size: 13px}
#starcard{display: block; width: min(84vw, 300px); height: auto}
/* the example card on Help: same drawing, slightly smaller, self-centered
 * (it stands in prose, not in the result panel's centered holder) */
#egcard{display: block; width: min(78vw, 270px); height: auto; margin: 2px auto}
.gold-text{color: var(--cta); font-weight: 700}

/* an anchor dressed as the go button: the week door, «spill selv»-links
 * on help/archive/tutorial and the first-run CTA all repeated this
 * styling inline until 2026-08-10 */
a.go{display: inline-block; text-decoration: none; font-size: 13px;
  font-weight: 700; padding: 7px 11px; border: 2px solid var(--ink);
  background: var(--cta); color: #fff}
/* the quieter sibling of a.go, dressed as the default button (Help's
 * «start læreturen på nytt» beside the resume door) */
a.plain{display: inline-block; text-decoration: none; font-size: 13px;
  font-weight: 600; padding: 7px 11px; border: 2px solid var(--ink);
  background: var(--card); color: var(--ink)}
/* the way onward as a DOOR: full width, its own kind among the actions
 * (the six-buttons verdict, 2026-08-10) */
a.go.door{display: block; text-align: center; margin-top: 14px;
  padding: 10px 11px; box-sizing: border-box}
/* the countdown under the door: centered, a breath of air above, so it
 * closes the panel instead of dangling off its left edge; the midnight
 * new-sky button inherits the centering */
#nextsky{text-align: center; margin-top: 10px}

/* a load failure: one plain sentence and one button, so the fix is a
 * single tap wherever the player happens to be (ui/page.js) */
.loadfail{display: flex; flex-wrap: wrap; align-items: center; gap: 9px;
  margin: 8px 0}
.loadfail button{font-size: 13px; padding: 5px 11px}

/* The one star, inline in text (ui/mark.js). It is sized in em and filled
 * with currentColor, so it inherits size and color from the run it sits
 * in exactly as the ★ and ✦ glyphs it replaced did. */
.star-ico{display: inline-block; vertical-align: -.13em; overflow: visible}
.stars{display: inline-flex; align-items: center; gap: .1em;
  color: var(--cta)}
.one-shot{background: #fff8e8; border: 2px solid var(--amber); padding: 9px 11px;
  font-size: 13px; line-height: 1.5; display: flex; flex-direction: column; gap: 7px}
.one-shot button{align-self: flex-end; font-size: 12px; padding: 4px 10px}

/* ------------------------------------------------------ the week strip */
.week-card{width: 100%; max-width: 520px; background: var(--card);
  border: 2.5px solid var(--ink); box-shadow: var(--shadow);
  padding: 14px 12px 12px; position: relative}
.week-title{font-family: var(--serif); font-weight: 700; font-size: 17px;
  letter-spacing: .04em; text-align: center; margin-bottom: 10px}
/* the card's own share row (whole weeks only) and explainer footer: the
 * one-card decision of 2026-08-11 (the old page spent three panels on
 * this, one of them a disabled button with an excuse) */
.week-card .week-share{margin-top: 12px}
.week-card .week-explain{margin-top: 10px; padding-top: 6px;
  border-top: 1.5px dashed rgba(2, 48, 71, .18)}
.week-card .week-explain .hint-text{margin: 6px 0 4px}
.week-card .week-explain button:not(.linkish){font-size: 12px;
  padding: 4px 9px; margin-top: 2px}
.week-title small{font-family: var(--sans); font-weight: 500; font-size: 11px;
  color: var(--muted); letter-spacing: .02em; display: block; margin-top: 1px}
.strip{display: flex; gap: 4px; justify-content: center}
.day-col{display: flex; flex-direction: column; align-items: center; gap: 4px;
  min-width: 0; width: calc((100% - 24px) / 7)}
.tile{width: 100%; aspect-ratio: 1; position: relative; cursor: pointer;
  border: 2px solid var(--ink); border-radius: 5px; overflow: hidden;
  background: var(--card); flex-shrink: 0; padding: 0}
.tile canvas{display: block; width: 100%; height: 100%; image-rendering: pixelated}
.tile.empty{border-style: dashed; border-color: rgba(2, 48, 71, .4);
  cursor: default;
  background: repeating-linear-gradient(45deg, transparent 0 5px,
    rgba(2, 48, 71, .04) 5px 10px)}
.tile.empty .st{position: absolute; inset: 0; display: flex;
  align-items: center; justify-content: center; color: var(--gold);
  font-size: 13px}
.tile.today{box-shadow: 0 0 0 2.5px var(--card), 0 0 0 5px var(--cta)}
.tile .late{position: absolute; right: 2px; bottom: 2px; font-size: 9px;
  line-height: 1; background: rgba(255, 255, 255, .85); border-radius: 3px;
  padding: 1px 2px}
.day-letter{font-size: 10.5px; font-weight: 700; color: var(--muted)}
.day-col.today .day-letter{color: var(--cta)}
.tile.pop{animation: pop .6s cubic-bezier(.2, 1.4, .4, 1)}
@keyframes pop{0%{transform: scale(1)} 45%{transform: scale(1.22)}
  100%{transform: scale(1)}}
.ribbon{position: absolute; left: 50%; bottom: -13px;
  transform: translateX(-50%); background: var(--cta); color: #fff;
  font-weight: 800; font-size: 11px; letter-spacing: .09em;
  text-transform: uppercase; padding: 4px 14px; border: 2px solid var(--ink);
  white-space: nowrap; cursor: pointer}
.ribbon.stamp{animation: ribbonstamp .34s cubic-bezier(.2, 1.6, .4, 1)}
@keyframes ribbonstamp{0%{transform: translateX(-50%) scale(1.3) rotate(-2deg)}
  100%{transform: translateX(-50%) scale(1) rotate(0)}}
/* the tapestry follows the week card directly since the one-card round
 * (2026-08-11), and the WHOLE-ribbon hangs 13px below the card's edge:
 * give the seal air so it never crowds the heading */
.tapestry{margin-top: 10px}
.tapestry{width: 100%; max-width: 520px; display: flex;
  flex-direction: column; gap: 8px}
.old-week{opacity: .62}
.old-week .week-tag{font-size: 10.5px; font-weight: 700; letter-spacing: .07em;
  text-transform: uppercase; color: var(--muted); margin-bottom: 3px}
.mini-strip{display: flex; gap: 3px}
/* a WHOLE past week earns a quiet gold thread around its miniature: the
 * lasting form of the binding wave, no text, no numbers */
.old-week.whole .mini-strip{box-shadow: 0 0 0 2px var(--gold);
  border-radius: 3px}
.mini-strip canvas, .mini-strip .mini-hole{width: calc((100% - 18px) / 7);
  aspect-ratio: 1; border: 1.5px solid rgba(2, 48, 71, .5); border-radius: 4px;
  image-rendering: pixelated}
.mini-strip .mini-hole{border-style: dashed; opacity: .6}
/* a whole week's share door in the tapestry (pages/week.js) */
.old-week .old-share{display: flex; align-items: center; gap: 8px;
  margin-top: 6px}
.old-week .old-share button{font-size: 12px; padding: 4px 9px}

/* the big day view (archive + week) */
.big-frame{position: relative; width: min(74vw, 320px); aspect-ratio: 1;
  border: 2.5px solid var(--ink); border-radius: 8px; overflow: hidden;
  box-shadow: var(--shadow); background: var(--card); align-self: center}
.big-frame canvas{display: block; width: 100%; height: 100%;
  image-rendering: pixelated}
.grow-in{animation: grow-in .38s cubic-bezier(.2, 1.2, .35, 1)}
@keyframes grow-in{from{transform: scale(.55)} to{transform: scale(1)}}

/* ------------------------------------------------------- archive + stats */
.day-list{display: grid; grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
  gap: 7px; width: 100%}
.day-pick{display: flex; flex-direction: column; gap: 2px; padding: 7px 4px;
  align-items: center; line-height: 1.25}
.day-pick b{font-size: 12.5px}
.day-pick .stars{color: var(--cta); font-size: 11px}
.day-pick .st{font-size: 10.5px; color: var(--muted); min-height: 12px}
.day-pick.cur{background: var(--amber)}
.day-pick.done{border-color: var(--ok)}
.stat-grid{display: grid; grid-template-columns: repeat(2, 1fr); gap: 7px;
  width: 100%}
.stat{border: 2px solid var(--ink); padding: 10px 8px; text-align: center;
  background: var(--card); box-shadow: 2.5px 2.5px 0 rgba(2, 48, 71, .12)}
.stat b{font-variant-numeric: tabular-nums; display: block; font-family: var(--serif); font-size: 24px}
.stat span{font-size: 11px; color: var(--muted); text-transform: uppercase;
  letter-spacing: .06em; font-weight: 700}
/* The honors wall hangs on shelves, one per badge family (ui/stats-page.js).
 * The point of the shelf is hierarchy: a family you have finished reads as
 * finished at a glance, and an unearned badge is quiet without being
 * mistaken for a disabled control. */
.shelves{display: flex; flex-direction: column; gap: 14px}
.shelf-head{display: flex; align-items: baseline; gap: 8px; margin: 0 0 6px;
  border-bottom: 1.5px dashed rgba(2, 48, 71, .22); padding-bottom: 3px}
.shelf-head h3{margin: 0; font-size: 11px; font-weight: 800;
  letter-spacing: .08em; text-transform: uppercase; color: var(--muted)}
.shelf-tally{margin-left: auto; font-size: 10.5px; font-weight: 700;
  color: var(--muted); font-variant-numeric: tabular-nums}
.shelf.full .shelf-head h3, .shelf.full .shelf-tally{color: var(--ink)}
.shelf.full .shelf-head{border-bottom-style: solid;
  border-bottom-color: var(--gold)}
.shelf-row{display: flex; flex-wrap: wrap; gap: 6px}
/* earned wears the gold face the result panel stamps; unearned keeps its
 * name but steps back; an unearned secret is sealed */
.badge.won{background: var(--gold); border-color: var(--ink); opacity: 1}
.badge.locked{background: var(--card); border-color: rgba(2, 48, 71, .3);
  color: var(--muted); cursor: pointer}
.badge.locked.sealed{border-style: dashed; cursor: default;
  letter-spacing: .14em}

/* ------------------------------------------- the skyclock (ui/skyclock.js)
 * The one night-colored panel in the game: before the first sky there is
 * no board to look at, so the state wears the mark's own face (navy with a
 * gold star) instead of being one gray sentence on an empty page. It is
 * deliberately unlike every other panel, because it appears only when
 * there is nothing else on the page. */
/* the host is a flex item of the page column, so it must be told to span
 * it: without this the panel shrinks to its content and stands narrower
 * than every other panel on the page */
#skyclock{width: 100%; display: flex; justify-content: center}
#skyclock[hidden]{display: none}
.skyclock{background: var(--night); border-color: var(--ink);
  color: var(--night-star); align-items: center; text-align: center;
  gap: 8px; padding: 20px 14px 16px; position: relative; overflow: hidden}
.skyclock h2{color: var(--night-star); margin: 0; font-size: 19px}
.skyclock .skyclock-when{color: var(--night-dim); max-width: 34ch}
.skyclock-star{color: var(--gold); line-height: 1;
  filter: drop-shadow(0 0 14px rgba(231, 180, 22, .45))}
/* the dust: small stars behind the clock, placed to read as a
 * constellation. Decoration only, so they are hidden from the tree. */
.skyclock .dust{position: absolute; color: var(--night-star); opacity: .28;
  pointer-events: none; animation: dusttwinkle 4.5s ease-in-out infinite}
@keyframes dusttwinkle{0%, 100%{opacity: .16} 50%{opacity: .45}}
.skyclock .clock{display: flex; gap: 7px; justify-content: center;
  flex-wrap: wrap}
.skyclock .unit{min-width: 66px; padding: 8px 6px 6px;
  border: 2px solid rgba(223, 233, 242, .35); background: rgba(0, 0, 0, .18)}
.skyclock .unit b{display: block; font-family: var(--serif); font-size: 27px;
  font-variant-numeric: tabular-nums; color: var(--gold); line-height: 1.1}
.skyclock .unit span{font-size: 10.5px; text-transform: uppercase;
  letter-spacing: .07em; font-weight: 700; color: var(--night-dim)}
/* the seconds box breathes once per tick: the whole point of a countdown
 * is that it is moving */
.skyclock .unit.beat b{animation: cdbeat .5s ease-out}
@keyframes cdbeat{0%{transform: scale(1.14)} 100%{transform: scale(1)}}
.skyclock .door{margin-top: 2px}

/* ------------------------------------------------------------ help page */
.prose{font-size: 13.5px; line-height: 1.6}
.prose p{margin: 0 0 8px}

/* --------------------------------------------------------- reduced motion */
@media (prefers-reduced-motion: reduce){
  .dot.entrance, .dot.on svg, .tile.pop, .ribbon.stamp, .grow-in,
  .mnext.nudge, #toweek.nudge,
  .skyclock .dust, .skyclock .unit.beat b{animation: none}
}
