/* ============================================================
   PixelFlow — game screen
   ============================================================
   One flex column pinned to the viewport. The board takes
   whatever is left after the bars, measured rather than
   guessed, so the same layout serves a phone in portrait and a
   desktop window without a media query deciding the size.
   ============================================================ */

:root{ --accent:#1E88E5 }

/* 100dvh, not 100vh: on iOS Safari the visual viewport shrinks when the URL
   bar is showing, and 100vh keeps reporting the taller number — which put the
   bottom bar under the chrome on exactly the devices this is built for. */
html,body{height:100%;overscroll-behavior:none}
body.game{
  height:100dvh;display:flex;flex-direction:column;overflow:hidden;
  background:var(--bg);
  /* The board is a drag surface. Without this, a swipe that starts on the
     canvas is claimed by the browser as a scroll or a back-gesture and the
     flow dies halfway. */
  touch-action:manipulation;
  -webkit-user-select:none;user-select:none;
}

/* ---------- top bar ---------- */
.gbar{
  flex:none;display:flex;align-items:center;gap:10px;
  padding:calc(8px + var(--safe-t)) calc(12px + var(--safe-r)) 8px calc(12px + var(--safe-l));
  background:var(--surface);border-bottom:1px solid var(--edge);
}
.gbar .where{display:flex;flex-direction:column;line-height:1.2;min-width:0}
.gbar .where b{font-size:15px;font-weight:700;letter-spacing:-.01em}
.gbar .where span{font-size:11.5px;color:var(--dim);font-weight:560}
.gbar .spacer{margin-left:auto}
.gbar .btn-icon{border-color:var(--edge);background:var(--surface-2)}
.gbar .btn-icon svg{width:19px;height:19px;display:block}

/* ---------- colour chips ---------- */
/* One chip per pair: a tap tears that flow up, which is how the Android
   build's header worked and is faster than hunting for the endpoint. */
.chips{
  flex:none;display:flex;flex-wrap:wrap;justify-content:center;gap:7px;
  padding:0 0 11px;
}
.chip{
  /* Square, like the cells they stand for. */
  width:30px;height:30px;border-radius:2px;border:none;padding:0;cursor:pointer;
  position:relative;opacity:.34;transition:opacity .2s,transform .12s;
  -webkit-tap-highlight-color:transparent;
}
.chip.done{opacity:1}
.chip.done::after{
  content:'';position:absolute;inset:0;
  background:no-repeat center/13px url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23fff' stroke-width='3.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E");
}
.chip:active{transform:scale(.9)}
.chip[disabled]{cursor:default}

/* ---------- the space above the board ---------- */
/* Takes whatever height the board and chips have not claimed, down to none.
   min-height:0 is what lets it give that space up rather than pushing the
   board off the bottom of a short screen. */
.ambient{
  flex:1 1 auto;min-height:0;width:100%;position:relative;overflow:hidden;
  display:flex;align-items:flex-end;justify-content:center;
}

/* Quiet enough to ignore while you are thinking, and worth a look when you
   are not. It sits at the bottom of the space, near the board, rather than
   floating in the middle of nowhere. */
.glance{
  width:min(300px,100%);text-align:center;padding-bottom:18px;
  pointer-events:none;
}
.glance[hidden]{display:none}

.glance-nums{display:flex;justify-content:center;gap:34px;margin-bottom:15px}
.glance-nums b{
  display:block;font-size:26px;font-weight:760;letter-spacing:-.03em;
  line-height:1;color:var(--txt-2);font-variant-numeric:tabular-nums;
}
.glance-nums span{
  display:block;margin-top:5px;font-size:9.5px;font-weight:640;
  letter-spacing:.11em;text-transform:uppercase;color:var(--faint);
}

/* One square per flow in the pack. Square, like everything else on this
   screen — the shape is the game's, and a row of dots would not be. The
   shade tracks the rating, so a pack you three-starred reads darker than one
   you scraped through. */
.packmap{
  display:grid;grid-template-columns:repeat(20,1fr);gap:3px;
  margin-bottom:11px;
}
.packmap i{
  display:block;aspect-ratio:1;background:rgba(15,23,42,.07);
}
.packmap i.s1{background:rgba(30,136,229,.30)}
.packmap i.s2{background:rgba(30,136,229,.55)}
.packmap i.s3{background:rgba(30,136,229,.85)}
/* The one you are on, marked by a ring rather than a fill so it reads as
   "here" and not as "done". */
.packmap i.now{background:transparent;box-shadow:inset 0 0 0 2px var(--accent)}

.glance-note{
  display:block;font-size:10.5px;font-weight:600;
  letter-spacing:.07em;text-transform:uppercase;color:var(--faint);
}

/* ---------- board ---------- */
/* The board sits at the bottom of whatever space is left, not in the middle
   of it. On a phone held one-handed the top third of the screen is the part a
   thumb cannot reach, so the grid — and the colour chips, which are tap
   targets too — live down where the hand already is. The empty space collects
   above them, which is also where the original put it. */
.stage{
  flex:1 1 auto;min-height:0;
  display:flex;flex-direction:column;justify-content:flex-end;align-items:center;
  padding:0 calc(12px + var(--safe-r)) 0 calc(12px + var(--safe-l));
}
.board-wrap{position:relative;line-height:0}
/* Square, like the board it draws. The original ran the grid edge to edge
   with nothing rounded anywhere on it. */
canvas#board{
  display:block;
  box-shadow:0 1px 2px rgba(15,23,42,.05),0 10px 30px rgba(15,23,42,.08);
  touch-action:none;   /* the canvas owns every gesture on it */
}
/* The completion burst gets its own layer so it can run its own frame loop
   without the board having to redraw underneath it, and so clearing it cannot
   wipe the board. It never takes a pointer event.

   It also sits outside the defocus below, which is the point: the board goes
   soft, the tick stays sharp. */
canvas.burst{
  position:absolute;inset:0;pointer-events:none;z-index:2;
}

/* ---------- the focus pull ----------
   A solved board goes out of focus while the tick plays over it, stays soft
   under the results card, and the next level is then revealed by pulling it
   back in. The blur is doing two jobs: it makes the tick the subject, and it
   turns the level change from a cut into a load that you can watch happen.

   Only paint properties, so nothing here reflows, and only three elements, so
   the compositor has little to do. */
#board,.chips,.glance{
  transition:filter .42s cubic-bezier(.2,.8,.3,1),
             transform .42s cubic-bezier(.2,.8,.3,1),
             opacity .42s ease;
}
body.settling #board{filter:blur(11px);transform:scale(1.04)}
body.settling .chips{filter:blur(5px);opacity:.45}
body.settling .glance{filter:blur(5px);opacity:.4}

@media (prefers-reduced-motion:reduce){
  #board,.chips,.glance{transition:none}
  body.settling #board,body.settling .chips,body.settling .glance{
    filter:none;transform:none;opacity:1;
  }
}

/* ---------- bottom bar ---------- */
/* Three columns, not a flex row. As a flex row the stat took whatever width
   was left over, so the number that reads as "the middle one" sat wherever
   the buttons happened to leave it — 51px left of centre on a phone, which
   made the whole bar look tipped over. 1fr auto 1fr puts it in the real
   middle and makes the two sides the same width whatever is in them. */
.gfoot{
  flex:none;display:grid;grid-template-columns:1fr auto 1fr;
  align-items:center;gap:10px;
  padding:10px calc(12px + var(--safe-r)) calc(10px + var(--safe-b)) calc(12px + var(--safe-l));
  background:var(--surface);border-top:1px solid var(--edge);
}
.gfoot > :nth-child(1){justify-self:start}
.gfoot > :nth-child(2){justify-self:center}
.gfoot > :nth-child(3){justify-self:end}
/* The two buttons that share the right-hand column. */
.gfoot .fgroup{display:flex;align-items:center;gap:10px}
.gfoot .stat{display:flex;flex-direction:column;line-height:1.15;min-width:0}
.gfoot .stat b{font-size:14px;font-weight:700}
.gfoot .stat span{font-size:11px;color:var(--dim);letter-spacing:.04em;text-transform:uppercase;font-weight:600}
.gfoot .btn{padding:10px 15px}

/* ---------- stars ---------- */
.stars{display:inline-flex;gap:2px;align-items:center}
.stars svg{width:15px;height:15px;display:block}
.stars .off{opacity:.2}

/* ---------- rejection shake ---------- */
/* The Android build shook the grid and the title together and buzzed the
   phone whenever a move was refused (GameActivity.isInvalidFlow). Live
   validation means a flow can no longer be drawn into an illegal shape at
   all, so what is left to refuse is letting go before the far square — and
   that still deserves to be felt rather than only read. */
@keyframes shake{
  0%,100%{transform:translateX(0)}
  12%{transform:translateX(-8px)}  28%{transform:translateX(7px)}
  44%{transform:translateX(-5px)}  60%{transform:translateX(4px)}
  76%{transform:translateX(-2px)}  88%{transform:translateX(1px)}
}
.shake{animation:shake .5s cubic-bezier(.36,.07,.19,.97) both}
@media (prefers-reduced-motion:reduce){ .shake{animation:none} }

/* ---------- hint line ---------- */
/* Sits over the board rather than in the flow, so a message appearing cannot
   resize the board underneath the finger that caused it. */
.hint{
  position:absolute;left:50%;bottom:12px;transform:translateX(-50%);
  background:rgba(11,18,32,.88);color:#fff;font-size:12.5px;font-weight:560;
  padding:8px 14px;border-radius:999px;white-space:nowrap;pointer-events:none;
  opacity:0;transition:opacity .2s;backdrop-filter:blur(6px);max-width:92%;
}
.hint.show{opacity:1}

/* ---------- solved overlay ---------- */
.done-veil{
  position:fixed;inset:0;z-index:80;display:grid;place-items:center;
  /* Light, because the board underneath has already gone soft; stacking a
     strong backdrop blur on a blurred board just turned the screen to soup. */
  background:rgba(238,242,248,.52);backdrop-filter:blur(3px) saturate(1.15);
  -webkit-backdrop-filter:blur(3px) saturate(1.15);
  opacity:0;pointer-events:none;transition:opacity .16s;
}
.done-veil.show{opacity:1;pointer-events:auto;cursor:pointer}

.done-card{
  position:relative;overflow:hidden;
  width:min(330px,calc(100vw - 44px));text-align:center;
  padding:30px 26px 0;
  background:var(--surface);border:1px solid var(--edge);border-radius:26px;
  box-shadow:0 2px 6px rgba(15,23,42,.06),0 32px 70px rgba(15,23,42,.16);
  /* Starts small and low and springs up. The overshoot in the curve is what
     makes it land rather than merely arrive. */
  transform:translateY(16px) scale(.9);
  /* Springy in, brisk out. The exit shares the screen with the next level
     coming into focus, and a card still drifting away during that reveal
     reads as two things happening at once. */
  transition:transform .16s ease-in;
}
.done-veil.show .done-card{
  transform:none;
  transition:transform .42s cubic-bezier(.18,1.2,.3,1);
}

/* A warm wash behind the stars, so the top of the card glows gold instead of
   being white all the way up. Purely decorative, hence the empty span. */
.done-card .glow{
  position:absolute;left:50%;top:-58px;transform:translateX(-50%);
  width:260px;height:190px;pointer-events:none;
  background:radial-gradient(50% 50% at 50% 50%,rgba(255,193,60,.30),transparent 70%);
}
.done-card > *{position:relative}

/* Closes the card without moving on. Sized to the 44px Apple asks for even
   though it looks smaller, because it sits in the corner of a card that is
   itself near the edge of the screen. */
.done-card .done-x{
  position:absolute;top:6px;right:6px;width:40px;height:40px;
  display:grid;place-items:center;border:0;border-radius:11px;padding:0;
  background:transparent;color:var(--faint);cursor:pointer;z-index:2;
  -webkit-tap-highlight-color:transparent;
}
.done-card .done-x:hover{background:rgba(15,23,42,.05);color:var(--txt)}
.done-card .done-x svg{width:17px;height:17px;display:block}

.done-card .best{
  display:inline-block;margin-bottom:14px;
  font-size:10.5px;font-weight:720;letter-spacing:.13em;text-transform:uppercase;
  color:#9a6207;background:rgba(255,193,60,.19);
  border:1px solid rgba(240,152,11,.28);
  padding:4px 11px;border-radius:999px;
  animation:pop .4s .55s cubic-bezier(.2,.9,.3,1.3) both;
}
.done-card .best[hidden]{display:none}

/* ---------- the stars ---------- */
.stars.hero{display:flex;justify-content:center;gap:7px;margin-bottom:16px}
.stars.hero svg{width:44px;height:44px}
/* Each star lands a beat after the one before it. Three at once is a picture;
   three in sequence is an event. */
@keyframes starIn{
  0%  {transform:scale(.3) rotate(-28deg);opacity:0}
  62% {transform:scale(1.22) rotate(5deg);opacity:1}
  100%{transform:scale(1) rotate(0);opacity:1}
}
@keyframes pop{
  0%{transform:scale(.8);opacity:0} 100%{transform:scale(1);opacity:1}
}
.stars.hero svg{animation:starIn .34s cubic-bezier(.2,.9,.3,1.25) both;
  filter:drop-shadow(0 4px 10px rgba(240,152,11,.32))}
.stars.hero svg.off{animation:none;opacity:.16;filter:none}

.done-card .big{font-size:25px;font-weight:780;letter-spacing:-.028em;margin-bottom:4px;
  animation:riseIn .38s .2s cubic-bezier(.2,.8,.3,1) both}
.done-card .sub{font-size:12.5px;color:var(--dim);font-weight:560;margin-bottom:17px;
  animation:riseIn .38s .26s cubic-bezier(.2,.8,.3,1) both}
@keyframes riseIn{
  0%{transform:translateY(7px);opacity:0} 100%{transform:none;opacity:1}
}

/* ---------- stat chips ---------- */
.stat-row{display:flex;gap:7px;margin-bottom:16px;
  animation:riseIn .38s .32s cubic-bezier(.2,.8,.3,1) both}
.stat-row div{
  flex:1;padding:9px 4px;border-radius:12px;
  background:var(--surface-2);border:1px solid var(--edge);
}
.stat-row b{display:block;font-size:15px;font-weight:720;letter-spacing:-.02em;line-height:1.2}
.stat-row span{display:block;font-size:9.5px;color:var(--faint);font-weight:620;
  letter-spacing:.09em;text-transform:uppercase;margin-top:3px}

/* ---------- pack progress ---------- */
.prog{margin-bottom:16px;animation:riseIn .38s .38s cubic-bezier(.2,.8,.3,1) both}
.prog-track{display:block;height:5px;border-radius:999px;background:var(--surface-2);
  border:1px solid var(--edge);overflow:hidden}
.prog-track i{display:block;height:100%;border-radius:999px;
  background:linear-gradient(90deg,var(--accent),#7c3aed);
  width:0;transition:width .7s .45s cubic-bezier(.2,.8,.3,1)}
.prog-note{display:block;font-size:11px;color:var(--faint);margin-top:7px;font-weight:560}

/* ---------- the onward line ---------- */
/* Replaces the buttons. It says what is about to happen, and the bar under it
   shows how long there is to look at the stars first. */
.onward{
  padding:13px 0 15px;margin:0 -26px;
  border-top:1px solid var(--edge);
  font-size:12.5px;font-weight:640;color:var(--dim);
  display:flex;align-items:center;justify-content:center;gap:7px;
}
.onward::after{
  content:'';width:13px;height:13px;flex:none;
  background:no-repeat center/13px url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%236b7a92' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12h14M13 6l6 6-6 6'/%3E%3C/svg%3E");
  animation:nudge 1.1s ease-in-out infinite;
}
@keyframes nudge{0%,100%{transform:translateX(0)}50%{transform:translateX(3px)}}

.done-card .auto{
  position:absolute;left:0;bottom:0;height:3px;width:100%;
  background:linear-gradient(90deg,var(--accent),#7c3aed);
  transform:scaleX(0);transform-origin:left;
}
.done-card .auto.run{transform:scaleX(1);transition:transform linear}

@media (prefers-reduced-motion:reduce){
  .onward::after{animation:none}
}

/* ---------- level picker ---------- */
.sheet{
  position:fixed;inset:0;z-index:90;display:flex;flex-direction:column;
  background:var(--bg);transform:translateY(100%);transition:transform .28s cubic-bezier(.2,.8,.3,1);
}
.sheet.show{transform:none}
.sheet-bar{
  flex:none;display:flex;align-items:center;gap:12px;
  padding:calc(10px + var(--safe-t)) 14px 10px;
  background:var(--surface);border-bottom:1px solid var(--edge);
}
.sheet-bar h2{font-size:16px;flex:1}
.sheet-body{flex:1 1 auto;overflow-y:auto;-webkit-overflow-scrolling:touch;
  padding:14px 14px calc(24px + var(--safe-b))}
.packs{display:flex;gap:7px;overflow-x:auto;padding-bottom:12px;scrollbar-width:none}
.packs::-webkit-scrollbar{display:none}
.packtab{
  flex:none;padding:8px 14px;border-radius:11px;border:1px solid var(--edge);
  background:var(--surface);font-size:13px;font-weight:620;cursor:pointer;white-space:nowrap;
}
.packtab.on{background:var(--accent);border-color:transparent;color:#fff}
.packtab[disabled]{opacity:.42;cursor:default}
.packtab .lk{font-size:10px;opacity:.75;margin-left:5px}

.grid-levels{
  display:grid;grid-template-columns:repeat(auto-fill,minmax(58px,1fr));gap:7px;
}
.lvl{
  aspect-ratio:1;border-radius:12px;border:1px solid var(--edge);background:var(--surface);
  display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;
  font-size:13.5px;font-weight:650;cursor:pointer;padding:0;
}
.lvl .dots{display:flex;gap:1.5px}
.lvl .dots i{width:4px;height:4px;border-radius:50%;background:var(--warn);display:block}
.lvl .dots i.off{background:var(--edge-strong)}
.lvl.done{background:#eef7f0;border-color:rgba(22,163,74,.22);color:#166534}
.lvl.now{border-color:var(--accent);box-shadow:0 0 0 2px rgba(30,136,229,.16)}
.lvl[disabled]{opacity:.38;cursor:default}
.lvl[disabled] .dots{display:none}

.packnote{font-size:12.5px;color:var(--dim);margin:2px 0 13px}

@media (min-width:760px){
  .chips{padding:14px}
  .chip{width:34px;height:34px}
  .gbar .where b{font-size:16px}

  /* On a phone the controls spread across the full width because that is
     where thumbs are. On a desktop the same rule flings Prev and Next to
     opposite corners of a 1400px window, so they gather in the middle
     instead, under the board they belong to. */
  .gfoot{justify-content:center;gap:14px}
  .gfoot .stat{flex:0 0 auto;min-width:76px}
}
