/**
 * Stepping Stones — stepping-stones.css
 *
 * A pond seen from directly above, at night. The whole design serves one
 * thing: reading a plan at a glance. Thirty five numbered discs scattered
 * down a tall board is a list, not a route, so the laid route is drawn as a
 * line through the rocks it uses, and the stretches Ponoki slides are drawn
 * in the moss colour so a slide reads as the one long movement it is rather
 * than as three ordinary steps.
 *
 * The board is tall and narrow and it scrolls, on purpose. A long route reads
 * far better vertically than a wide field of rocks does, and it keeps every
 * rock near the decision it belongs to. The compass pills say which way the
 * flag and home are when they have scrolled out of sight.
 *
 * Colour carries meaning and nothing else:
 *   teal    the route you have laid, and anything you can act on
 *   green   moss, and every slid stretch of the route
 *   amber   the lantern, the lit flag, and the tide when it is nearly spent
 *   grey    rock, and the flag before it is lit
 *
 * One visual world on purpose: every Ponoki game is glow on black by
 * construction, and the light theme is deferred project wide.
 */

:root {
  --ink:        hsl(228, 34%,  8%);
  --surface:    hsl(228, 28%, 11%);
  --card:       hsl(228, 25%, 14%);
  --line:       hsl(228, 20%, 22%);

  --text:       hsl(220, 20%, 92%);
  --muted:      hsl(220, 15%, 58%);
  --dim:        hsl(220, 12%, 38%);

  --w-deep:     hsl(203, 52%,  9%);
  --w-mid:      hsl(198, 44%, 15%);
  --w-shallow:  hsl(190, 38%, 21%);

  --rock-hi:    hsl(232, 13%, 60%);
  --rock-mid:   hsl(230, 12%, 44%);
  --rock-lo:    hsl(228, 16%, 27%);
  --rock-wet:   hsl(222, 22%, 20%);

  --moss:       hsl(138, 42%, 42%);
  --moss-hi:    hsl(132, 46%, 54%);

  --lantern:    hsl(38, 95%, 64%);
  --lantern-lo: hsl(26, 85%, 50%);
  --flame:      hsl(48, 100%, 78%);
  --glow:       hsla(38, 95%, 64%, 0.42);

  --accent:     hsl(185, 70%, 55%);
  --accent-dim: hsl(185, 55%, 34%);
  --warn:       hsl(28, 88%, 62%);

  --pk-accent: hsl(248, 38%, 72%);
  --pk-band:   hsl(250, 44%, 81%);
  --pk-eye:    hsl(24, 34%, 19%);
  --pk-body:   hsl(28, 66%, 80%);
  --pk-cheek:  hsl(8, 66%, 72%);

  /* The brand stacks come from shared/styles/fonts.css, which also carries
     the metric-matched fallbacks. Never restate the family names here. */
  --display: var(--font-display, "DM Sans", sans-serif);
  --body:    var(--font-body, "Inter", sans-serif);
  --mono:    ui-monospace, "SF Mono", Menlo, monospace;
}

/* One visual world on purpose: every Ponoki game is glow on black by
   construction and the light theme is deferred project wide. */

* { box-sizing: border-box; }

html, body { height: 100%; }
body {
  margin: 0; background: var(--ink); color: var(--text);
  font-family: var(--body); font-size: 16px; line-height: 1.62;
  -webkit-font-smoothing: antialiased;
  overflow: hidden;
}

/* ═══════════════ SCREENS ═══════════════
   The house pattern: each screen is a fixed full-viewport column, and
   shared/styles/desktop-frame.css narrows .screen into a phone-shaped column
   on anything 900px and wider. The safe-area padding is what keeps the tide
   bar and the Send button clear of a home indicator. */
.screen {
  position: fixed; inset: 0; z-index: 1;
  display: none; flex-direction: column; gap: 6px;
  padding: calc(6px + env(safe-area-inset-top)) calc(10px + env(safe-area-inset-right))
           calc(6px + env(safe-area-inset-bottom)) calc(10px + env(safe-area-inset-left));
}
.screen.is-active { display: flex; }
#screen-start, #screen-end { justify-content: center; overflow-y: auto; }

/* Back to the rest of Ponoki. Sits in the corner of the start screen only, so
   it can never be mistaken for a game control. */
.nav-back {
  position: absolute; top: calc(8px + env(safe-area-inset-top)); left: calc(8px + env(safe-area-inset-left));
  display: grid; place-items: center; width: 40px; height: 40px;
  color: var(--muted); border-radius: 50%; text-decoration: none;
}
.nav-back:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
@media (hover: hover) and (pointer: fine) { .nav-back:hover { color: var(--text); } }

/* The start and end screens centre their whole group. margin-top: auto here
   used to push everything to the bottom half and leave a third of the screen
   empty above the mascot. */
.s-top { display: flex; flex-direction: column; align-items: center; gap: 2px; }
.s-title { font-family: var(--display); font-size: 1.7rem; font-weight: 700; letter-spacing: -0.02em; }

.pk-say { display: flex; align-items: flex-end; gap: 8px; padding: 0 2px; }
.bubble { background: hsl(40,30%,94%); color: hsl(228,30%,16%); font-size: 0.84rem; line-height: 1.38; padding: 9px 13px; border-radius: 15px; flex: 1; }

.tiers { display: flex; flex-direction: column; gap: 8px; padding: 0 2px; }
.tier { width: 100%; text-align: left; font: inherit; cursor: pointer; border: 1px solid var(--line); border-radius: 13px; padding: 10px 13px; background: var(--surface); color: var(--text); display: flex; flex-direction: column; gap: 1px; min-height: 44px; }
.tier .n { font-family: var(--display); font-size: 0.9rem; font-weight: 500; }
.tier .d { font-size: 0.72rem; color: var(--muted); }
.tier[aria-pressed="true"] { border-color: var(--accent); background: hsla(185,70%,55%,0.10); }
.tier:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.hud { display: flex; justify-content: space-between; align-items: center; font-family: var(--mono); font-size: 0.62rem; color: var(--dim); padding: 0 2px; letter-spacing: 0.06em; text-transform: uppercase; flex: 0 0 auto; }
.hud .back, .mute { background: none; border: 0; color: var(--muted); cursor: pointer; padding: 3px 6px; min-height: 30px; font: inherit; letter-spacing: inherit; }
.mute { color: var(--dim); }
.mute[aria-pressed="true"] { color: var(--accent); }
.hud .back:focus-visible, .mute:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Ponoki's line. It is the only teaching the game has, so a change has to
   catch the eye without shouting. */
/* Ponoki's line used to be a panel above the board, permanently holding open
   about seventy pixels of the one thing there is never enough of. It is a
   bubble over the water now: it fades in, sits for a few seconds and goes.
   pointer-events stays off it throughout, so a rock underneath is always
   still tappable, which is what makes an overlay safe here at all, and it
   sizes to its own text rather than spanning the board.

   It sits at the TOP and not the bottom, which is the opposite of where a
   speech bubble wants to be. The board is worked from the bottom upward and
   auto-scrolls to keep the head of the route in view, so the bottom edge is
   always the decision being made right now and the top edge is rocks several
   moves away. A bubble low down covered the near shore and the first rock of
   every pond. */
.says {
  position: absolute; left: 10px; right: 10px; top: 8px; z-index: 8;
  display: flex; justify-content: center; pointer-events: none;
  opacity: 0; transform: translateY(-7px);
  transition: opacity 240ms ease, transform 240ms cubic-bezier(0.3,1.1,0.5,1);
}
.says.on { opacity: 1; transform: translateY(0); }
.says > span {
  display: flex; align-items: center; gap: 8px; max-width: 100%;
  background: hsl(40,30%,94%); color: hsl(228,30%,16%);
  font-size: 0.78rem; line-height: 1.36; padding: 8px 13px; border-radius: 15px;
  box-shadow: 0 8px 22px hsla(210,60%,3%,0.6);
}
.says .who { width: 24px; flex: 0 0 24px; }
.says .who .pk-svg { width: 100%; height: auto; display: block; }
.says.flash > span { animation: flash 1500ms ease-out; }
@keyframes flash {
  0%   { box-shadow: 0 0 0 0 hsla(185,70%,55%,0.9), 0 8px 22px hsla(210,60%,3%,0.6); background: hsl(48,90%,90%); }
  55%  { box-shadow: 0 0 0 7px hsla(185,70%,55%,0), 0 8px 22px hsla(210,60%,3%,0.6); background: hsl(48,90%,92%); }
  100% { box-shadow: 0 0 0 0 hsla(185,70%,55%,0), 0 8px 22px hsla(210,60%,3%,0.6); background: hsl(40,30%,94%); }
}

/* ═══════════════ THE POND ═══════════════
   Tall and narrow, and it scrolls. A long route reads far better than a wide
   field of rocks, and it keeps every rock near the decision. */
.board { position: relative; flex: 1; min-height: 0; width: 100%; border-radius: 20px; overflow: hidden; border: 1px solid hsl(200,30%,20%); isolation: isolate; }
/* The water was one flat colour top to bottom, which read as a dark rectangle
   with stones on it. A little depth down the board and a soft edge round it
   and it reads as water you would not want to stand in. */
.board::after { content: ""; position: absolute; inset: 0; z-index: 6; pointer-events: none; border-radius: inherit;
  background: radial-gradient(120% 78% at 50% 6%, transparent 42%, hsla(215,55%,4%,0.42) 100%); }
.board::before { content: ""; position: absolute; inset: 0; z-index: 1; pointer-events: none;
  background: linear-gradient(hsla(190,55%,30%,0.13), transparent 34%, hsla(220,60%,4%,0.30)); }

/* the banks they set off from and comes home to, with a wet line at the water */
.shore-rock::after { content: ""; position: absolute; left: 0; right: 0; height: 3px; border-radius: 2px;
  background: linear-gradient(90deg, transparent, hsla(185,70%,72%,0.30), transparent); }
.dock.near .shore-rock::after { top: -1px; }
.dock.far .shore-rock::after { bottom: -1px; }
.scroller { position: absolute; inset: 0; overflow-y: auto; overflow-x: hidden; overscroll-behavior: contain; scrollbar-width: none; }
.scroller::-webkit-scrollbar { display: none; }
/* min-height is what keeps the water under a short pond. The canvas is sized
   to the scene, so an Easy pond in a board this tall left the scene shorter
   than the view and the bottom of the board went black.

   Do NOT add justify-content: center here. A centred flex container splits
   its overflow above and below, and the part above the top edge cannot be
   scrolled back to, so on a tall Deep pond the first rows become unreachable.
   That was tried and it broke a pond partway through a verification run. */
.scene { position: relative; width: 100%; min-height: 100%; display: flex; flex-direction: column; align-items: center; }
#water { position: absolute; inset: 0; width: 100%; height: 100%; z-index: 0; }

.dock { position: relative; width: 100%; height: 76px; flex: 0 0 76px; z-index: 2; }
.shore-rock { position: absolute; width: 92px; height: 32px; translate: -50% 0; border-radius: 50% 50% 44% 44% / 62% 62% 38% 38%; background: linear-gradient(hsl(230,13%,47%), hsl(228,17%,28%)); box-shadow: 0 5px 0 hsl(226,20%,19%), 0 12px 22px hsla(210,60%,3%,0.6); }
.dock.far .shore-rock { bottom: 4px; }
.dock.near .shore-rock { top: 6px; left: 0; width: 100%; height: 34px; translate: none; border-radius: 20px 20px 8px 8px; }
.dock.far .shore-rock { left: 0; width: 100%; height: 26px; translate: none; border-radius: 8px 8px 20px 20px; opacity: 0.75; }
.flagpost { display: none; }
.cell .flag { position: absolute; left: 50%; bottom: 52%; width: 26px; height: 34px; translate: -50% 0; pointer-events: none; z-index: 4; }
.cell .flag svg { width: 100%; height: 100%; overflow: visible; }
.cell .flag .cloth { fill: hsl(222,13%,38%); transition: fill 500ms ease; transform-origin: 9px 6px; }
.board.is-lit .cell.is-goal .flag .cloth { fill: var(--lantern); animation: wave 2.4s ease-in-out infinite; }
.board.is-lit .cell.is-goal .flag { filter: drop-shadow(0 0 12px var(--glow)); }
.cell.is-goal::after { content: ''; position: absolute; inset: 14% 10% 10%; border-radius: 50%; box-shadow: 0 0 0 2px hsla(38,92%,62%,0.34); pointer-events: none; }
.board.is-lit .cell.is-goal::after { box-shadow: 0 0 0 2px hsla(38,92%,62%,0.75); }

.flagpost { position: absolute; bottom: 26px; translate: -50% 0; width: 46px; height: 58px; }
.flagpost svg { width: 100%; height: 100%; overflow: visible; }
.flagpost .cloth { fill: hsl(222,13%,34%); transition: fill 500ms ease; transform-origin: 12px 10px; }
.board.is-lit .flagpost .cloth { fill: var(--lantern); animation: wave 2.4s ease-in-out infinite; }
.board.is-lit .flagpost { filter: drop-shadow(0 0 16px var(--glow)); }
@keyframes wave { 0%,100% { transform: skewY(0deg) scaleX(1); } 50% { transform: skewY(-3.5deg) scaleX(1.05); } }

.pond-wrap { position: relative; z-index: 2; width: 100%; }
.pond { position: relative; z-index: 2; display: grid; gap: 4px; width: 100%; }

/* The route drawn as a route.

   Thirty five numbered discs scattered over a tall board is a list, not a
   path: the eye has to sort them before it can read the shape of the plan,
   and the shape of the plan is the whole game. A line through them turns it
   back into a route you can take in at a glance, and the stretches they slide
   are drawn in the moss colour, so the slides read as the one long movement
   they are rather than as three ordinary steps. */
.trail { position: absolute; inset: 0; z-index: 3; pointer-events: none; overflow: visible; }
.trail path { fill: none; stroke-linecap: round; stroke-linejoin: round; }
.trail .t-walk { stroke: var(--accent); stroke-width: 4.5; opacity: 0.5; }
.trail .t-slide { stroke: var(--moss-hi); stroke-width: 5.5; opacity: 0.62; }
.trail .t-ghost { stroke: var(--accent-dim); stroke-width: 3; opacity: 0.45; stroke-dasharray: 3 7; }
@media (prefers-reduced-motion: no-preference) {
  .trail .t-slide { animation: slideGlow 2.6s ease-in-out infinite; }
  @keyframes slideGlow { 0%, 100% { opacity: 0.5; } 50% { opacity: 0.78; } }
}

.cell { position: relative; border: 0; padding: 0; background: transparent; font: inherit; color: inherit; aspect-ratio: 1; -webkit-tap-highlight-color: transparent; }
.cell[data-kind="water"] { cursor: default; }
/* touch-action: none is what makes drawing a route by dragging work at all.
   Without it the browser claims a vertical drag as a scroll gesture, fires
   pointercancel, and the drag ends on the first rock. Water keeps the default
   so the board can still be scrolled by dragging on open water, and the route
   auto-scrolls the board as it is laid anyway. */
.cell[data-kind="rock"], .cell[data-kind="moss"] { cursor: pointer; touch-action: none; }
/* A slow travelling highlight over the green: wet things catch the light and
   still things do not, and this is the whole difference between the two. */
@media (prefers-reduced-motion: no-preference) {
  .cell[data-kind="moss"]::after {
    content: ""; position: absolute; inset: 14% 12% 34%; border-radius: 50%; pointer-events: none; z-index: 2;
    background: linear-gradient(112deg, transparent 34%, hsla(160,80%,94%,0.30) 48%, transparent 62%);
    background-size: 260% 100%; animation: sheen 4.6s ease-in-out infinite;
  }
  @keyframes sheen { 0%, 62% { background-position: 130% 0; } 100% { background-position: -60% 0; } }
}
.cell .rock { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; transition: opacity 420ms ease, transform 420ms cubic-bezier(0.5,0,0.75,0); }
.cell.is-sunk .rock { opacity: 0; transform: translateY(9px) scale(0.72); }

.cell .step { position: absolute; inset: 0; margin: auto; z-index: 4; width: 20px; height: 20px; border-radius: 50%; display: grid; place-items: center; font-family: var(--mono); font-size: 0.66rem; font-weight: 600; color: hsl(228,40%,9%); background: var(--accent); box-shadow: 0 0 0 2.5px hsla(228,40%,9%,0.5), 0 0 12px hsla(185,70%,55%,0.5); opacity: 0; transform: scale(0.4); transition: opacity 140ms ease, transform 160ms cubic-bezier(0.34,1.56,0.64,1); }
.cell.is-routed .step { opacity: 1; transform: scale(1); }
.cell.is-slid .step { background: var(--moss-hi); }
.cell.is-ghost .step { opacity: 1; transform: scale(1); background: hsla(228,40%,9%,0.7); color: var(--accent); border: 1px dashed var(--accent-dim); box-shadow: none; }
.rock-rim { opacity: 0; transition: opacity 180ms ease; }
.cell.is-routed .rock-rim { opacity: 0.9; }
.cell.is-sunk.is-routed .step, .cell.is-sunk .step, .cell.is-sunk .rock-rim { opacity: 0; }
.cell:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; border-radius: 10px; }

.lamp-token { position: absolute; top: 10%; right: 8%; width: 20px; height: 24px; pointer-events: none; z-index: 2; transition: opacity 260ms ease; display: grid; place-items: center; }
.lamp-token .body { position: absolute; inset: 0; border-radius: 4px 4px 6px 6px; background: linear-gradient(var(--flame), var(--lantern), var(--lantern-lo)); box-shadow: 0 0 15px 4px var(--glow); }
.lamp-token::before { content:""; position:absolute; top:-5px; left:50%; translate:-50% 0; width:9px; height:7px; border:1.5px solid var(--lantern-lo); border-bottom:0; border-radius:5px 5px 0 0; }
.lamp-token b { position: relative; z-index: 1; font-family: var(--mono); font-size: 0.66rem; font-weight: 700; color: hsl(24,60%,22%); }
.cell.is-sunk .lamp-token { opacity: 0; }
.cell.is-sunk::after { opacity: 0; }

.plant { position: absolute; inset: 0; pointer-events: none; opacity: 0.55; z-index: 1; }

/* Which ways they can be sent onto this one. Shown on the cell itself, because
   the answer is about that cell and nowhere else. */
.cell .ways { position: absolute; inset: 0; z-index: 4; pointer-events: none; }
.cell .ways svg { width: 100%; height: 100%; display: block; overflow: visible; }
@media (prefers-reduced-motion: no-preference) {
  .cell .ways { animation: waysIn 240ms cubic-bezier(0.3,1.3,0.5,1) both; }
  @keyframes waysIn { from { opacity: 0; scale: 0.7; } to { opacity: 1; scale: 1; } }
}

/* The one thing the game stops to explain, and only when somebody has just
   tried it and been told no. */
.card-over { position: absolute; inset: 0; z-index: 20; display: grid; place-items: center; padding: 18px; background: hsla(228,42%,6%,0.72); backdrop-filter: blur(3px); }
.card-over[hidden] { display: none; }
.card { width: 100%; max-width: 320px; background: var(--surface); border: 1px solid var(--line); border-radius: 18px; padding: 20px 20px 18px; box-shadow: 0 18px 44px hsla(228,50%,4%,0.55); }
.card-t { margin: 0 0 12px; font-size: 1.06rem; font-weight: 700; color: var(--text); }
.card p { margin: 0 0 11px; font-size: 0.9rem; line-height: 1.55; color: var(--muted); }
.card p em { color: var(--moss-hi); font-style: normal; font-weight: 600; }
.card .btn { width: 100%; margin-top: 6px; }
.card-fig { margin: 0 0 14px; }
.card-fig svg { width: 100%; height: auto; display: block; }
@media (prefers-reduced-motion: no-preference) {
  .card { animation: cardIn 260ms cubic-bezier(0.25,1.2,0.4,1) both; }
  @keyframes cardIn { from { opacity: 0; transform: translateY(14px) scale(0.97); } to { opacity: 1; transform: none; } }
}

/* In the water, which is a thing to look at, not a punishment. */
#pk-walker.inwater { animation: bob 1300ms ease-in-out infinite; }
#pk-walker.inwater .pk-foot { opacity: 0; }
#pk-walker.inwater .pk-body { clip-path: inset(0 0 26% 0); }
@keyframes bob { 0%,100% { translate: 0 4px; rotate: -2deg; } 50% { translate: 0 8px; rotate: 2deg; } }

#pk-walker { position: absolute; left: 0; top: 0; width: 44px; z-index: 5; pointer-events: none; transition: transform 320ms cubic-bezier(0.34,1.2,0.64,1); }
#pk-walker .pk-svg { width: 100%; height: auto; display: block; overflow: visible; }
#pk-walker.hop { animation: hop 320ms ease; }
@keyframes hop { 50% { translate: 0 -9px; } }
/* The slip is in their feet.

   Turning the whole sprite thirteen degrees and squashing it read as a
   pratfall: loud, and it pulled the eye off the board. What actually says
   slippery is small and low down. The feet go out from under them a few pixels
   in opposite directions, they dip one and a half pixels and leans two degrees
   while they scrabble, and everything is back where it started inside a third
   of a second. The body barely moves, which is what makes it read as footing
   rather than as falling over. */
.pk-foot, .pk-body { transform-box: fill-box; transform-origin: center; }
#pk-walker.wobble .pk-foot-l { animation: footOutL 250ms cubic-bezier(0.32,0,0.28,1); }
#pk-walker.wobble .pk-foot-r { animation: footOutR 250ms cubic-bezier(0.32,0,0.28,1); }
#pk-walker.wobble .pk-body   { animation: bodyDip 250ms cubic-bezier(0.32,0,0.28,1); }
@keyframes footOutL {
  0% { translate: 0 0; } 34% { translate: -6px 2px; } 66% { translate: 2.5px 0.5px; } 100% { translate: 0 0; }
}
@keyframes footOutR {
  0% { translate: 0 0; } 38% { translate: 5.5px 1.5px; } 70% { translate: -2px 0.5px; } 100% { translate: 0 0; }
}
@keyframes bodyDip {
  0% { translate: 0 0; rotate: 0deg; } 36% { translate: 0 1.5px; rotate: -2.2deg; }
  72% { translate: 0 0.4px; rotate: 1.1deg; } 100% { translate: 0 0; rotate: 0deg; }
}

/* And on the glide: feet trailing a little behind them, a degree and a half of
   lean, nothing else. */
#pk-walker.slip { transition-timing-function: cubic-bezier(0.25,0.9,0.3,1); }
#pk-walker.slip .pk-foot { animation: footTrail 240ms ease-out; }
#pk-walker.slip .pk-body { animation: leanGlide 240ms ease-out; }
@keyframes footTrail {
  0% { translate: 0 2.5px; scale: 1.14 0.88; } 55% { translate: 0 0.8px; scale: 1.04 0.97; }
  100% { translate: 0 0; scale: 1 1; }
}
@keyframes leanGlide {
  0% { rotate: -1.6deg; } 65% { rotate: 0.7deg; } 100% { rotate: 0deg; }
}
#pk-walker .carry { position: absolute; right: -4px; top: 48%; width: 11px; height: 14px; border-radius: 3px 3px 5px 5px; background: linear-gradient(var(--flame), var(--lantern-lo)); box-shadow: 0 0 13px 4px var(--glow); opacity: 0; transition: opacity 200ms ease; }
#pk-walker.carrying .carry { opacity: 1; }
#pk-walker.cheer { animation: cheer 1200ms cubic-bezier(0.3,1.4,0.5,1); }
@keyframes cheer {
  0% { translate:0 0; rotate:0deg; scale:1 1; } 12% { translate:0 2px; scale:1.1 0.9; }
  32% { translate:0 -15px; rotate:-9deg; scale:0.94 1.08; } 50% { translate:0 0; scale:1.08 0.94; }
  68% { translate:0 -10px; rotate:8deg; scale:0.96 1.05; } 84% { translate:0 0; scale:1.05 0.96; }
  100% { translate:0 0; rotate:0deg; scale:1 1; }
}
.spark { position: absolute; width: 5px; height: 5px; border-radius: 50%; background: var(--flame); z-index: 4; pointer-events: none; opacity: 0; }
.spark.go { animation: spark 900ms ease-out forwards; }
@keyframes spark { 0% { opacity:1; transform: translate(0,0) scale(1); } 100% { opacity:0; transform: translate(var(--dx),var(--dy)) scale(0.3); } }

/* Which way is the flag, when it has scrolled off. */
/* The compass used to float in the middle of the top row, over whichever rock
   happened to be there, which is the one place on the board you cannot put
   something. It sits in the corner now, over water far more often than not,
   and the pill is opaque enough to read either way. */
.compass { position: absolute; top: 7px; right: 8px; z-index: 7; display: flex; align-items: center; gap: 6px; padding: 5px 11px; border-radius: 999px; background: hsla(228,44%,7%,0.94); border: 1px solid var(--line); box-shadow: 0 3px 14px hsla(228,50%,4%,0.6); font-family: var(--mono); font-size: 0.63rem; letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted); opacity: 0; transition: opacity 220ms ease; pointer-events: none; }
.compass.on { opacity: 1; }
.compass.home { top: auto; bottom: 8px; right: 8px; }
/* The flag compass and the bubble both want the top of the board. They are
   both transient hints and two at once is noise, so the pill stands down
   while Ponoki is talking. */
.board:has(.says.on) .compass { opacity: 0; }
.compass i { color: var(--lantern); font-style: normal; }

/* Everything under the board, in as little height as it can honestly take:
   two lines of tide and one row of three buttons. The row that used to hold
   "Unlocks after one try" is gone; the hint says the same thing out loud when
   it is tapped early, which is when anybody wanted to know. */
.play-foot { display: flex; flex-direction: column; gap: 6px; padding: 6px 2px 0; flex: 0 0 auto; }
.tide { display: flex; align-items: center; justify-content: space-between; font-family: var(--mono); font-size: 0.63rem; letter-spacing: 0.07em; text-transform: uppercase; color: var(--dim); padding: 0 4px; }
.tide .n { color: var(--accent); font-variant-numeric: tabular-nums; }
.tide.tight .n { color: var(--warn); }
.tide.homeward #tide-goal { color: var(--lantern); }
.tide.full .n { color: var(--warn); animation: nudge 900ms ease-in-out infinite; }
/* Steps left, as a thing rather than as arithmetic. On a thirty six step tide
   the difference between plenty and nearly none is the whole planning
   problem, and two numbers in a row do not carry it. */
.tide-bar { height: 4px; border-radius: 999px; background: hsla(200,30%,50%,0.16); overflow: hidden; margin: 0 4px; }
.tide-bar i { display: block; height: 100%; width: 0; border-radius: inherit; background: var(--accent); transition: width 220ms cubic-bezier(0.3,0.9,0.4,1), background-color 260ms ease; }
#tide.tight ~ .tide-bar i, #tide.full ~ .tide-bar i { background: var(--warn); }
#tide.homeward ~ .tide-bar i { background: var(--lantern); }
@keyframes nudge { 0%,100% { opacity: 1; } 50% { opacity: 0.4; } }

/* Three across on a 390px phone. The labels are short so they fit without
   shrinking the tap target below 44px, which is the floor and not negotiable;
   the primary takes a little more of the row because it is the one being
   aimed at. */
.controls { display: flex; gap: 6px; }
.controls .btn { padding-left: 6px; padding-right: 6px; }
.controls .btn.primary { flex: 1.25; }
.btn { flex: 1; font-family: var(--display); font-size: 0.8rem; font-weight: 500; padding: 11px 12px; min-height: 44px; border-radius: 999px; border: 1px solid var(--line); background: var(--card); color: var(--text); cursor: pointer; transition: background 160ms ease, border-color 160ms ease, opacity 160ms ease; }
.btn.primary { background: var(--accent); border-color: var(--accent); color: hsl(228,40%,8%); font-weight: 700; }
.btn:disabled { opacity: 0.3; cursor: default; }
.btn.is-locked { opacity: 0.38; }
@media (hover: hover) and (pointer: fine) {
  .btn:not(:disabled):hover { border-color: var(--accent-dim); }
  .btn.primary:not(:disabled):hover { background: hsl(185,70%,62%); }
}
.btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.proto-note { font-family: var(--mono); font-size: 0.63rem; color: var(--dim); text-align: center; }

/* The toast and the say line were two ways of putting one sentence on screen,
   in two different places, and they could both be up at once. There is one
   now: .says above. */

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