/* ReasonEducation student app — shared style system (docs: shared-style-system.md).
   Warm cream, Nunito, rounded, pill buttons, no red, no scores in student view. */

* { box-sizing: border-box; margin: 0; }
:root {
  --page: #faf6ee; --card: #ffffff; --ink: #2b2620; --ink-2: #5c5346; --ink-3: #8a8272;
  --border: #e6e0d3; --canvas-ring: #ece5d4; --accent: #0088b0; --accent-soft: #e3f4fb;
  --footer: #fffaf0;
}
html, body { height: 100%; }
body {
  background: var(--page); color: var(--ink);
  /* Nunito is intentionally NOT pulled from an external font CDN (no child IP leaves to Google).
     The stack falls back to the local system fonts. Self-host Nunito as a local woff2 and it will
     be used automatically, since it stays first in the stack. */
  font-family: 'Nunito', -apple-system, 'Segoe UI', Roboto, system-ui, sans-serif;
  font-size: 16px;
}
button { font-family: inherit; cursor: pointer; }
input { font-family: inherit; }

@keyframes slide-up { from { opacity:0; transform:translateY(14px) scale(.98); } to { opacity:1; transform:none; } }
@keyframes slide-side { from { opacity:0; transform:translateX(28px); } to { opacity:1; transform:none; } }
@keyframes pop { 0%{transform:scale(.7);opacity:0;} 60%{transform:scale(1.08);opacity:1;} 100%{transform:scale(1);} }
@keyframes fade-scale { from{opacity:0;transform:scale(.92);} to{opacity:1;transform:scale(1);} }
@keyframes mark-draw { from { stroke-dashoffset:96; } to { stroke-dashoffset:0; } }
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { transition-duration:.01ms !important; animation-duration:.01ms !important; }
}

.app { min-height: 100vh; display: flex; flex-direction: column; position: relative; }
.anim-up { animation: slide-up 260ms ease; }
.anim-side { animation: slide-side 260ms ease; }

/* pills & buttons */
.pill { border: none; border-radius: 999px; min-height: 56px; padding: 17px 40px;
  font-size: 19px; font-weight: 800; color: #fff; background: var(--accent);
  transition: transform 150ms cubic-bezier(.34,1.56,.64,1); display: inline-flex; align-items: center; gap: 10px; }
.pill:active { transform: scale(.92); }
.pill-ghost { background: var(--card); color: #7d7466; border: 2px solid var(--border); }
.pill-small { min-height: 44px; padding: 10px 22px; font-size: 15px; }
.chip { border: none; background: var(--card); color: var(--ink); border-radius: 999px;
  min-height: 44px; padding: 8px 20px; font-size: 14px; font-weight: 800;
  box-shadow: 0 2px 6px rgba(60,40,10,.1); transition: transform 150ms ease; }
.chip:active { transform: scale(.94); }

/* speaker */
.speak { width: 44px; height: 44px; min-width: 44px; border-radius: 50%; background: var(--accent-soft);
  border: none; position: relative; flex: none; transition: transform 150ms ease; }
.speak:active { transform: scale(.9); }
.speak::before { content:""; position:absolute; left:13px; top:16px; width:7px; height:10px; background:var(--accent); }
.speak::after { content:""; position:absolute; left:20px; top:11px; border-top:10px solid transparent;
  border-bottom:10px solid transparent; border-left:10px solid var(--accent); }

/* login */
.login-wrap { min-height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 26px; padding: 24px; }
.login-card { background: var(--card); border-radius: 32px; box-shadow: 0 4px 18px rgba(60,40,10,.08);
  padding: 36px 40px; width: 100%; max-width: 460px; display: flex; flex-direction: column; gap: 16px; }
.login-card label { font-size: 16px; font-weight: 800; color: var(--ink-2); }
.login-card input { width: 100%; height: 64px; border: 4px solid var(--canvas-ring); border-radius: 20px;
  background: var(--page); font-size: 26px; font-weight: 800; color: var(--ink); text-align: center; letter-spacing: 1px; }
.login-card input:focus { outline: none; border-color: var(--accent); }
.login-err { background: #fdf1d9; color: #8a6a2e; border-radius: 16px; padding: 12px 16px; font-size: 15px; font-weight: 700; text-align: center; }
.wordmark { font-size: 26px; font-weight: 900; }
.wordmark span { color: #0f766e; }

/* welcome */
.center-col { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 22px; padding: 24px; }
.welcome-name { font-size: 34px; font-weight: 900; }
.big-card { width: 460px; max-width: 92vw; background: var(--card); border-radius: 32px; padding: 30px 36px;
  box-shadow: 0 4px 18px rgba(60,40,10,.08); display: flex; flex-direction: column; align-items: center; gap: 16px;
  animation: slide-up 320ms ease 60ms both; }
.card-title { font-size: 26px; font-weight: 900; text-align: center; }
.card-sub { font-size: 16px; font-weight: 700; color: var(--ink-3); }

/* floating header — no banner bar, so the work area gets the height back */
.q-shell { flex: 1; min-height: 0; display: flex; flex-direction: column; position: relative; }
.top-float { position: absolute; top: 14px; left: 0; right: 0; z-index: 5; display: flex;
  align-items: flex-start; justify-content: space-between; padding: 0 28px; pointer-events: none; }
.top-float > * { pointer-events: auto; }
.dots { display: flex; align-items: center; gap: 6px; }
.dot { width: 10px; height: 10px; border-radius: 50%; background: #e6e0d3; }
.dot.done { background: var(--accent); }
.dot.now { width: 14px; height: 14px; border: 3px solid var(--accent); background: #fff; }
/* A finished dot is a button back to that question. The dot stays 10px because that
   is the right size to read a row of them at a glance; the button around it is what
   a finger actually has to hit, so it is padded out to a usable target and the
   padding is pulled back in with a negative margin so the row keeps its rhythm. */
.dot-tap { border: none; background: none; padding: 10px 4px; margin: -10px -2px;
  display: flex; align-items: center; cursor: pointer; }
.dot-tap:active .dot { transform: scale(1.4); }
.dot-tap .dot { transition: transform 120ms ease; }

/* question */
.q-row { padding: 44px 40px 8px; display: flex; align-items: center; justify-content: center; gap: 18px; }
.q-text { font-size: 48px; font-weight: 900; letter-spacing: -.01em; text-align: center; }
.q-text.q-swap { animation: q-swap 240ms ease; }
@keyframes q-swap { from { opacity: 0; transform: translateY(9px); } to { opacity: 1; transform: none; } }
.work-zone { flex: 1; min-height: 0; padding: 0 40px 14px; display: flex; flex-direction: column; gap: 8px; }
.work-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap; }
.work-label { font-size: 15px; color: #7d7466; font-weight: 700; }
.tool-rail { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.pens { display: flex; align-items: center; gap: 6px; background: var(--card); border-radius: 999px;
  padding: 5px 9px; box-shadow: 0 2px 6px rgba(60,40,10,.1); }
.pen { width: 26px; height: 26px; min-width: 26px; border-radius: 50%; border: 3px solid transparent; padding: 0;
  transition: transform 180ms cubic-bezier(.34,1.56,.64,1); }
.pen.sel { border-color: var(--ink); transform: scale(1.15); }
.pen-tool { border: none; background: none; color: var(--ink-3); font-size: 13px; font-weight: 800; min-height: 40px; padding: 8px 7px; }

/* icon tools — pencil, eraser, ruler, protractor. Lit when active. */
.pen-ic { width: 38px; height: 38px; min-width: 38px; padding: 0; border: none; border-radius: 50%;
  background: transparent; color: var(--ink-3); display: flex; align-items: center; justify-content: center;
  transition: background 140ms ease, color 140ms ease, transform 150ms ease; }
.pen-ic svg { width: 21px; height: 21px; }
.pen-ic:hover { background: #f3eee2; color: var(--ink-2); }
.pen-ic:active { transform: scale(.9); }
.pen-ic.on { background: var(--accent); color: #fff; }
.pen-ic.on:hover { background: var(--accent); color: #fff; }
.pen-div { width: 1px; height: 22px; background: var(--border); margin: 0 2px; flex: none; }
.canvas-box { flex: 1; min-height: 220px; position: relative; border-radius: 28px; background: #fff;
  background-image: radial-gradient(rgba(43,38,32,.16) 1.4px, transparent 1.6px); background-size: 30px 30px;
  overflow: hidden; box-shadow: inset 0 0 0 3px var(--canvas-ring), 0 3px 10px rgba(60,40,10,.06); }
.canvas-box canvas { position: absolute; inset: 0; width: 100%; height: 100%; cursor: crosshair; touch-action: none; }
.canvas-hint { position: absolute; inset: 0; pointer-events: none; display: flex; align-items: center; justify-content: center;
  color: #b9b09d; font-size: 19px; font-weight: 700; }
.canvas-box.erasing canvas { cursor: url("data:image/svg+xml;utf8,<svg xmlns=\'http://www.w3.org/2000/svg\' width=\'30\' height=\'30\'><rect x=\'6\' y=\'6\' width=\'18\' height=\'18\' rx=\'3\' fill=\'white\' stroke=\'%235c5346\' stroke-width=\'2\'/><path d=\'M6 17h18\' stroke=\'%235c5346\' stroke-width=\'2\'/></svg>") 15 15, cell; }

/* ruler + protractor: real objects the student drags around the page.
   They live above the canvas but never touch the stroke data, so they are
   absent from the snapshot sent for AI analysis. */
.tool-layer { position: absolute; inset: 0; pointer-events: none; z-index: 3; }
.mtool { position: absolute; pointer-events: auto; touch-action: none; cursor: grab;
  filter: drop-shadow(0 4px 10px rgba(60,40,10,.22)); }
.mtool:active { cursor: grabbing; }
.mtool svg { display: block; }
.mtool-rot { position: absolute; width: 30px; height: 30px; border-radius: 50%; padding: 0;
  border: 2px solid var(--accent); background: #fff; cursor: grab; touch-action: none;
  display: flex; align-items: center; justify-content: center; }
.mtool-rot::after { content: ""; width: 12px; height: 12px; border: 2.5px solid var(--accent);
  border-right-color: transparent; border-radius: 50%; }
/* handles sit fully clear of the tool so they never cover its markings */
.mtool-ruler .mtool-rot { right: -40px; top: 50%; transform: translateY(-50%); }
.mtool-prot  .mtool-rot { right: -14px; bottom: -12px; }
.tools { display: flex; gap: 10px; margin: 2px 0 14px; flex-wrap: wrap; }
.tool-btn { border: none; background: var(--card); color: var(--ink-3); border-radius: 999px; min-height: 44px;
  padding: 10px 22px; font-size: 15px; font-weight: 800; box-shadow: 0 2px 6px rgba(60,40,10,.08); transition: transform 150ms ease; }
.tool-btn:active { transform: scale(.94); }
.tool-btn.on { background: var(--accent); color: #fff; }
.tool-panel { position: absolute; inset: 0; background: #fff; padding: 24px 32px; overflow: auto; animation: fade-scale 200ms ease; }
.tool-panel input { border: 3px solid var(--canvas-ring); border-radius: 16px; background: var(--page);
  font-size: 22px; font-weight: 700; color: var(--ink); text-align: center; }
.tool-panel input:focus { outline: none; border-color: var(--accent); }

/* ── question diagram (doc 22) ─────────────────────────────────────────────
   Sits above the canvas, never beside it: a child looks at the picture, then
   writes. Splitting the screen horizontally would shrink both to uselessness
   on the narrower iPads.

   The SVGs carry a viewBox and no width/height, so they take whatever box we
   give them. max-height lives on the <svg> rather than the wrapper — a cap on
   the wrapper alone does not scale the child, so tall diagrams would overflow
   instead of shrinking. With width:100% and an intrinsic ratio the browser
   recomputes the width from max-height, which keeps the aspect ratio coming
   from the viewBox. No fixed height, no object-fit. */
.stimulus {
  /* One cap, two uses: the height limit below, and a width limit derived from
     it and the diagram's own ratio (--ar, set at render time from the viewBox).
     Without the derived width a 136x388 diagram sits in a 496px-wide box with
     the drawing letterboxed in the middle. */
  --stim-fit: 100vh;   /* replaced with the measured remainder by fitStimulus() */
  --stim-cap: max(140px, min(42vh, var(--stim-fit)));
  --ar: 1;
  /* Everything is border-box, so any padding a theme adds has to be added back
     here or it eats into the drawing and the derived caps come out short. */
  --stim-pad: 0px;
  max-width: min(calc(520px + var(--stim-pad)), calc(var(--stim-cap) * var(--ar) + var(--stim-pad)));
  width: 100%; margin: 8px auto 4px; flex: 0 0 auto; display: block; break-inside: avoid;
}
.stimulus[hidden] { display: none; }
.stimulus svg { width: 100%; height: auto; display: block; max-height: var(--stim-cap); }
/* A published pack ships drawn artwork instead of a diagram. Same caps, same
   derived width, so the canvas floor below is honoured either way. The width and
   height attributes on the <img> give the browser the ratio before the file
   arrives — without them the canvas resizes under a child's pen when it loads. */
.stimulus img { width: 100%; height: auto; display: block; max-height: var(--stim-cap);
                object-fit: contain; user-select: none; -webkit-user-drag: none; }

/* ── pre-printed working, with typeable boxes ────────────────────────────────
   Sits over the drawing surface, not instead of it: the layer ignores pointers
   so the whole canvas stays drawable, and only the boxes take a tap. That is what
   lets one template work on a laptop and an iPad — type the numbers, draw the
   thinking around them.
   Not absolutely positioned to the full box: it flows from the top so the lines
   never sit on top of each other, and leaves the rest of the canvas clear. */
.tpl-layer { position: absolute; left: 0; right: 0; top: 0; z-index: 2;
             pointer-events: none; padding: 14px 16px 0; text-align: center; }
.tpl-layer[hidden] { display: none; }
.tpl-line { font-size: 24px; font-weight: 700; color: var(--ink, #2b2620);
            line-height: 1.9; letter-spacing: .01em; white-space: pre-wrap; }
.tpl-box {
  pointer-events: auto;            /* the one thing on this layer that is tappable */
  width: 3.2em; min-width: 56px; text-align: center;
  font: inherit; font-size: inherit; font-weight: 800;
  color: var(--accent, #2b2620); background: #fff;
  border: 2.5px solid var(--accent, #8a8378); border-radius: 10px;
  padding: 2px 4px; margin: 0 4px; vertical-align: baseline;
}
.tpl-box:focus { outline: 3px solid var(--accent, #8a8378); outline-offset: 2px; }
/* Years 1-2 get the same scaling everything else on this screen gets. */
.band12 .tpl-line { font-size: 30px; }
/* A short canvas must not push the boxes off it; the lines shrink before the
   working-out floor is allowed to. */
@media (max-height: 700px) { .tpl-line { font-size: 20px; line-height: 1.7; } }
@media (max-width: 700px)  { .tpl-line { font-size: 19px; }
                             .tpl-box { min-width: 46px; } }

/* The canvas gives up room to a diagram, but only so much: working out is the
   point of the product, and a 120px strip is not something a child can work in.
   This floor is what the diagram caps above are derived against. */
.q-shell.has-stim .canvas-box { min-height: 175px; }

/* Years 1–2: most diagrams and the youngest eyes, so they scale with the skin. */
/* Years 1-2: most diagrams and the youngest eyes, so they scale with the skin. */
.band12 .stimulus { --stim-cap: max(140px, min(50vh, var(--stim-fit)));
  max-width: min(calc(600px + var(--stim-pad)), calc(var(--stim-cap) * var(--ar) + var(--stim-pad))); }
/* When a diagram is doing the communicating, the Years 1-2 numerals do not also
   need to be 68px — that alone costs 100px of the height the diagram wants. */
.band12 .q-shell.has-stim .q-text { font-size: 52px; }

@media print { .stimulus { break-inside: avoid; } }

/* answer footer */
.foot { display: flex; align-items: center; justify-content: space-between; gap: 22px; padding: 18px 40px 24px;
  background: var(--footer); box-shadow: 0 -4px 16px rgba(60,40,10,.06); }
.ans-label { font-size: 17px; font-weight: 800; color: var(--ink-2); margin-bottom: 8px; }
.ans-input { width: 260px; max-width: 46vw; height: 76px; padding: 0 20px; font-size: 40px; font-weight: 900;
  color: var(--ink); background: #fff; border: 4px solid var(--canvas-ring); border-radius: 22px; text-align: center; }
.ans-input:focus { outline: none; border-color: var(--accent); }

/* review */
.review-cards { display: flex; gap: 16px; flex-wrap: wrap; justify-content: center; max-width: 700px; }
.rev-card { border: none; background: var(--card); border-radius: 22px; width: 108px; height: 108px;
  box-shadow: 0 2px 8px rgba(60,40,10,.08); display: flex; flex-direction: column; align-items: center;
  justify-content: center; gap: 8px; transition: transform 150ms ease; }
.rev-card:active { transform: scale(.94); }
.rev-num { font-size: 17px; font-weight: 800; color: var(--ink-3); }
.rev-icon { width: 40px; height: 40px; border-radius: 50%; display: flex; align-items: center; justify-content: center; }

/* badges & themes */
.grid-badges { display: flex; gap: 22px; flex-wrap: wrap; justify-content: center; max-width: 760px; }
.badge-tile { border: none; background: var(--card); border-radius: 28px; width: 150px; height: 150px;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 10px;
  box-shadow: 0 3px 10px rgba(60,40,10,.08); transition: transform 150ms ease; font-size: 14px; font-weight: 800; color: var(--ink-2); }
.badge-tile:active { transform: scale(.94); }
.badge-ic { width: 56px; height: 56px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 28px; }
.themes-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; max-width: 720px; width: 100%; }
.theme-tile { border: 3px solid transparent; border-radius: 24px; padding: 18px; display: flex; flex-direction: column;
  align-items: center; gap: 12px; position: relative; transition: transform 150ms ease; background: var(--card); }
.theme-tile:active { transform: scale(.96); }
.theme-sw { width: 100%; height: 64px; border-radius: 16px; }
.theme-name { font-size: 15px; font-weight: 800; }

/* overlay modal */
.overlay { position: fixed; inset: 0; background: rgba(43,38,32,.35); display: flex; align-items: center;
  justify-content: center; z-index: 10; animation: fade-scale 180ms ease; padding: 16px; }
.modal { background: var(--card); border-radius: 28px; padding: 32px 34px; max-width: 380px; width: 100%;
  box-shadow: 0 8px 28px rgba(0,0,0,.18); display: flex; flex-direction: column; align-items: center; gap: 16px; text-align: center; }
.modal h3 { font-size: 20px; font-weight: 900; }
.modal p { font-size: 15px; font-weight: 600; color: var(--ink-2); line-height: 1.5; }

/* years 1-2 skin scale-up */
.band12 .q-text { font-size: 68px; }
.band12 .welcome-name { font-size: 44px; }
.band12 .card-title { font-size: 32px; }
.band12 .card-sub { font-size: 20px; }
.band12 .pill { font-size: 22px; padding: 19px 46px; }
.band12 .ans-input { font-size: 34px; }
.band12 .canvas-hint { font-size: 24px; }

@media (max-width: 700px) {
  /* narrow screens: the centred question would run under the floating Help chip */
  .top-float { padding: 0 14px; top: 12px; }
  .q-row { padding: 62px 16px 6px; } .q-text { font-size: 34px; }
  .work-zone { padding: 0 16px 10px; }
  .work-head { gap: 8px; }
  .stimulus, .band12 .stimulus { --stim-cap: max(120px, min(34vh, var(--stim-fit))); }
  /* On a phone a diagram, a workable canvas and the Years 1-2 skin genuinely do
     not fit at once, so the page scrolls — but the answer box stays pinned. A
     child who cannot see where the answer goes assumes the question is broken. */
  .foot { position: sticky; bottom: 0; z-index: 4; }
  .foot { padding: 14px 16px 18px; flex-wrap: wrap; }
}

/* ══════════════════════════════════════════════════════════════════════════
   "Space" theme — animated illustrated backdrop.

   Mounted by assets/space.js only when the student has chosen Space, so no
   other theme downloads the artwork. Add .is-frozen (space.js does this
   automatically during an assessment, and for reduced-motion devices) and the
   whole scene holds a composed still frame — nothing moves while a student
   is working.
   ══════════════════════════════════════════════════════════════════════════ */

body.theme-space { background: #05081c; }
body.theme-space .app { position: relative; z-index: 1; background: transparent !important; }

.space-scene { position: fixed; inset: 0; z-index: 0; overflow: hidden; pointer-events: none; }
.space-scene picture { display: block; }
.space-plate { position: absolute; inset: 0; z-index: 1; }
.space-plate img { width: 100%; height: 100%; object-fit: cover; object-position: center; display: block; }
.space-scrim { position: absolute; inset: 0; z-index: 8;
  background: radial-gradient(ellipse at 50% 42%, rgba(3,6,26,.10) 0%, rgba(3,6,26,.62) 100%); }
.space-img { display: block; width: 100%; }
/* <picture> is an inline wrapper — the real <img> has to be told to fill it. */
.space-naut img, .space-ship img { display: block; width: 100%; height: auto; }

/* ── star field · STATIC ───────────────────────────────────────────────────
   Only the astronaut animates. The stars, rocket and shooting star hold fixed
   positions on every screen. To bring their motion back, restore the
   `animation:` line on .space-twinkles / .space-ship and drop the
   `display:none` on .space-shooter — the keyframes below are still here. */
.space-twinkles { position: absolute; z-index: 2; left: 0; top: 0; width: 4px; height: 4px; border-radius: 50%;
  background: #fff; box-shadow:
    6vw 12vh #fff, 14vw 39vh #ffccf8, 23vw 20vh #8cecff, 31vw 10vh #fff, 40vw 67vh #a8ddff,
    48vw 24vh #fff, 57vw 14vh #ffb7ee, 66vw 36vh #fff, 74vw 19vh #8cecff, 83vw 42vh #fff,
    91vw 23vh #ffcf9f, 96vw 66vh #fff, 70vw 74vh #eab7ff, 45vw 81vh #fff, 18vw 75vh #a8ddff;
  opacity: .88; filter: drop-shadow(0 0 6px currentColor); transform: scale(1.1); }
.space-twinkles-b { color: #80dcff; transform: translate(3vw,7vh) scale(.7); }
@keyframes sp-twinkle {
  0%,18%   { opacity:.18; filter:drop-shadow(0 0 1px currentColor);  transform:scale(.65); }
  72%,100% { opacity:1;   filter:drop-shadow(0 0 10px currentColor); transform:scale(1.55); }
}

/* shooting star — off. A streak that never travels reads as a glitch, not a comet. */
.space-shooter { display: none; }

/* astronaut — the one moving thing: wrapper drifts, image bobs.
   Kept off the centre column so it never competes with the card or the name. */
.space-naut { position: absolute; z-index: 6; left: 6%; top: 15%; width: clamp(140px,17vw,340px);
  animation: sp-drift 24s ease-in-out infinite alternate; will-change: transform; }
.space-naut .space-img { filter: drop-shadow(0 12px 14px rgba(0,0,20,.45));
  animation: sp-float 9.5s ease-in-out infinite; }
@keyframes sp-drift {
  0%   { transform: translate(-4vw, 4vh); }
  35%  { transform: translate(2vw, -2vh); }
  70%  { transform: translate(7vw, 3vh); }
  100% { transform: translate(3vw, 8vh); }
}
@keyframes sp-float {
  0%,100% { transform: rotate(-7deg) translateY(0); }
  50%     { transform: rotate(5deg) translateY(-2.5vh); }
}

/* spaceship — STATIC, parked in clear sky upper-right. */
.space-ship { position: absolute; z-index: 5; left: 0; top: 0; width: clamp(150px,20vw,400px);
  transform: translate(62vw, 15vh) rotate(4deg) scale(.62); }
.space-ship .space-img { filter: drop-shadow(0 10px 12px rgba(0,0,20,.48)); }

/* ── FROZEN: assessment in progress (and reduced-motion devices) ──────────
   Everything else is already static, so this only has to still the astronaut —
   into a deliberately composed pose, not a paused animation frame. */
.space-scene.is-frozen .space-naut,
.space-scene.is-frozen .space-naut .space-img { animation: none !important; }
.space-scene.is-frozen .space-naut { transform: translate(1vw, 3vh); }
.space-scene.is-frozen .space-naut .space-img { transform: rotate(-6deg); }

/* ── legibility on the dark backdrop ──────────────────────────────────── */
body.theme-space .center-col { color: #f2eeff; }
body.theme-space .welcome-name,
body.theme-space .center-col .card-sub { color: #efeaff; text-shadow: 0 2px 12px rgba(0,0,12,.6); }
body.theme-space .big-card,
body.theme-space .badge-tile,
body.theme-space .rev-card,
body.theme-space .modal { color: var(--ink); box-shadow: 0 12px 40px rgba(0,0,20,.45); }
body.theme-space .big-card .card-sub,
body.theme-space .modal p { color: var(--ink-3); text-shadow: none; }

/* assessment screen: with the banner gone, the whole top band is bare backdrop */
body.theme-space .q-text { color: #f4f1ff; text-shadow: 0 2px 14px rgba(0,0,12,.65); }
body.theme-space .work-label { color: #d3cbf0; text-shadow: 0 1px 8px rgba(0,0,12,.5); }
body.theme-space .dot { background: rgba(255,255,255,.34); }
/* diagrams are drawn in warm dark ink — give them a page to sit on */
body.theme-space .stimulus:not([hidden]) { --stim-pad: 24px;
  background: rgba(255,251,235,.94); border-radius: 18px;
  padding: 8px 12px; box-shadow: 0 8px 26px rgba(0,0,20,.4); }
body.theme-space .dot.now { background: #fff; }
body.theme-space .canvas-box { box-shadow: inset 0 0 0 3px var(--canvas-ring), 0 8px 26px rgba(0,0,20,.4); }
body.theme-space .pens,
body.theme-space .tool-btn { box-shadow: 0 8px 26px rgba(0,0,20,.4); }
body.theme-space .foot { box-shadow: 0 -4px 26px rgba(0,0,20,.4); }

/* Space swatch in the themes grid gets a live mini-preview of the real thing */
.theme-tile.is-space { background: #0b1030; }
.theme-tile.is-space .theme-name { color: #efeaff; }
.theme-sw.sw-space { position: relative; overflow: hidden;
  background: radial-gradient(ellipse at 30% 20%, #3b2a72 0%, #140e33 55%, #05081c 100%); }
.theme-sw.sw-space::before { content: ""; position: absolute; left: 14%; top: 30%;
  width: 2px; height: 2px; border-radius: 50%; background: #fff;
  box-shadow: 10px 6px #fff, 30px 22px #8cecff, 52px 8px #fff, 74px 30px #ffccf8,
              96px 14px #fff, 118px 26px #a8ddff, 140px 10px #fff; }
.theme-sw.sw-space::after { content: ""; position: absolute; right: 12%; top: 46%;
  width: 26px; height: 8px; border-radius: 999px;
  background: linear-gradient(90deg, transparent, #b6a2ea, #efeaff);
  filter: drop-shadow(0 0 5px #8b6ef0); }

/* Portrait iPad / phone: pull the scene in so the artwork still reads. */
@media (max-aspect-ratio: 4/5) {
  .space-naut { left: 4%; top: 11%; width: clamp(120px,30vw,260px); }
  .space-ship { width: clamp(130px,32vw,300px); transform: translate(52vw, 10vh) rotate(4deg) scale(.62); }
}

/* ══════════════════════════════════════════════════════════════════════════
   Hand up, and being looked at
   ══════════════════════════════════════════════════════════════════════════
   Two states of one button and one quiet notice. The tone is deliberate: a
   child who needs help is already having a hard minute, so nothing here is red,
   nothing flashes, and nothing says "request". The button says Help, then Hand
   up, then Coming — three words a Year 1 can read.

   The band12 sizes below follow the same rule as the rest of the app: Years 1–2
   get bigger type and bigger targets, not a different layout.
   ────────────────────────────────────────────────────────────────────────── */
.help-wrap { position: relative; display: flex; flex-direction: column; align-items: flex-end; gap: 8px; }

.help-chip { display: inline-flex; align-items: center; gap: 8px; }
.help-chip svg { width: 20px; height: 20px; flex: none; }

/* Hand up — warm amber, steady. It is a signal, not an alarm. */
.help-chip.is-up { background: #ffd977; color: #4a3a12; box-shadow: 0 2px 10px rgba(180,130,20,.28); }
/* Acknowledged — the teacher pressed "I'm coming". Green, and it fades itself. */
.help-chip.is-ack { background: #b8e6c0; color: #1f5c2e; box-shadow: 0 2px 10px rgba(40,120,60,.24); }

.help-note { max-width: 260px; background: #2b2620; color: #faf6ee; font-size: 14px; line-height: 1.5;
  font-weight: 600; padding: 12px 16px; border-radius: 20px; text-align: left;
  animation: help-note-in 200ms ease both; }
@keyframes help-note-in { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; transform: none; } }
.help-note[hidden] { display: none; }

.band12 .help-chip { min-height: 52px; font-size: 17px; padding: 10px 24px; }
.band12 .help-note { font-size: 16px; max-width: 300px; }

/* The notice that a teacher is looking.
   A child is never watched without a sign on their own screen — that is the
   whole reason this exists. It sits bottom-left, out of the working area, and
   it is calm on purpose: a child who reads it should feel checked on, not
   caught. The dot pulses slowly so the notice is obviously LIVE rather than a
   leftover, and reduced-motion turns the pulse off without hiding the notice. */
.watch-pill { position: absolute; left: 40px; bottom: 96px; z-index: 4; pointer-events: none;
  display: inline-flex; align-items: center; gap: 9px;
  background: var(--accent-soft); color: #4a463d; border-radius: 999px;
  padding: 8px 16px; font-size: 14px; font-weight: 700;
  box-shadow: 0 2px 8px rgba(60,40,10,.08); animation: help-note-in 220ms ease both; }
.watch-pill[hidden] { display: none; }
.watch-dot { width: 9px; height: 9px; border-radius: 50%; background: var(--accent); flex: none;
  animation: watch-pulse 2.4s ease-in-out infinite; }
@keyframes watch-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .35; } }
.band12 .watch-pill { font-size: 16px; padding: 9px 18px; }

@media (max-width: 700px) {
  .watch-pill { left: 16px; bottom: 84px; font-size: 13px; padding: 7px 13px; }
  .help-note { max-width: 210px; font-size: 13px; }
}
@media (prefers-reduced-motion: reduce) {
  .watch-dot { animation: none; }
  .help-note, .watch-pill { animation: none; }
}

/* ══════════════════════════════════════════════════════════════════════════
   STUDENT SIGN-IN — the Reason Education brand, turned up for a six-year-old
   ══════════════════════════════════════════════════════════════════════════
   Everything below is scoped to `body.signin`, which only index.php carries.
   assess.php, welcome.php, live.php and install.php share this stylesheet and
   are deliberately untouched by any of it — the older .login-wrap/.login-card/
   .login-err rules above still belong to install.php and still work.

   Colours, radii, shadows and the type face are the marketing site's
   assets/css/tokens.css, copied rather than imported because this app ships its
   own stylesheet. The staff portal's sign-in took the same tokens and dialled
   them DOWN for a teacher on a laptop. This screen dials them UP: the mascot,
   the yellow, the blobs, one enormous green button. Same family, different age.

   #009E7F is decorative only — white text on it is 3.4:1 and fails AA — so
   every surface carrying white text uses --green-ui (4.8:1) or --green-800.

   THE FONT IS SELF-HOSTED. No remote font, no CDN, no third-party request of
   any kind is made by this page; see the comment in index.php's <head>.
   ────────────────────────────────────────────────────────────────────────── */

@font-face{font-family:'Plus Jakarta Sans';font-style:normal;font-weight:400;font-display:swap;
  src:url('fonts/plus-jakarta-sans-latin-400-normal.woff2') format('woff2');}
@font-face{font-family:'Plus Jakarta Sans';font-style:normal;font-weight:700;font-display:swap;
  src:url('fonts/plus-jakarta-sans-latin-700-normal.woff2') format('woff2');}
@font-face{font-family:'Plus Jakarta Sans';font-style:normal;font-weight:800;font-display:swap;
  src:url('fonts/plus-jakarta-sans-latin-800-normal.woff2') format('woff2');}

body.signin{
  --ink:#17205E; --ink-body:#4F5691; --ink-muted:#666CA0;
  --green:#009E7F; --green-ui:#00816A; --green-800:#006B58; --green-050:#E6F5F1;
  --blue:#1878F0; --blue-ink:#0B57B8;
  --yellow:#FDB203; --yellow-050:#FFF4DC; --yellow-ink:#5C4708; --yellow-line:#F0D69B;
  --white:#FFFFFF; --paper:#FAFAF7; --band:#F6FAFD; --line:#E4EBF3;
  --r-md:14px; --r-lg:22px; --r-xl:32px; --r-pill:999px;
  --shadow-md:0 2px 6px rgba(23,32,94,.05), 0 10px 26px rgba(23,32,94,.07);
  --shadow-lg:0 4px 12px rgba(23,32,94,.06), 0 24px 60px rgba(23,32,94,.10);
  --font-brand:'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
               Helvetica, Arial, sans-serif;

  font-family:var(--font-brand);
  color:var(--ink-body);
  /* Warm at the bottom where the hands are, green at the top where the brand is. */
  background:
    radial-gradient(820px 520px at 50% -12%, rgba(0,158,127,.11), rgba(0,158,127,0) 70%),
    radial-gradient(760px 500px at 50% 110%, rgba(253,178,3,.15), rgba(253,178,3,0) 72%),
    var(--paper);
  -webkit-font-smoothing:antialiased;
  overflow-x:hidden;
}
body.signin *{ font-family:inherit; }
body.signin p, body.signin h1, body.signin h3{ margin:0; }
body.signin :where(a,button,input,[tabindex]):focus-visible{
  outline:3px solid var(--blue); outline-offset:3px;
}

/* ── the decorative sky ────────────────────────────────────────────────────
   Fixed, behind everything, and clipped by its own overflow so the blobs can
   hang off every edge without ever giving the page a horizontal scrollbar. */
.signin-sky{ position:fixed; inset:0; z-index:0; overflow:hidden; pointer-events:none; }
.signin-sky svg{ position:absolute; display:block; }
.sky-green   { width:min(70vw,640px); left:-18vw;  top:-16vh; }
.sky-teal    { width:min(54vw,480px); right:-15vw; bottom:-12vh; opacity:.8; }
.sky-yellow  { width:min(34vw,300px); right:-5vw;  top:4vh;  opacity:.15; }
.sky-sparkles{ width:min(26vw,210px); left:1.5vw;  bottom:2.5vh; opacity:.45; }

/* ── the shell ─────────────────────────────────────────────────────────────
   Three bands: brand at the top, the message in the middle taking the slack,
   and everything touchable pinned to the bottom. On a held iPad the scan
   button lands under the thumbs rather than up by the camera. */
.signin-shell{
  position:relative; z-index:1;
  min-height:100vh; min-height:100dvh;
  display:flex; flex-direction:column; align-items:center;
  gap:clamp(12px,2.2vh,26px);
  padding:clamp(16px,3vh,34px) clamp(16px,4vw,40px) clamp(18px,3.4vh,40px);
  text-align:center;
  animation:slide-up 300ms ease both;
}

.signin-head{ flex:none; }
.signin-wordmark{ font-size:clamp(18px,1.2vw + 12px,23px); font-weight:800;
  letter-spacing:-.02em; color:var(--ink); }
.signin-wordmark span{ color:var(--green-ui); }
.signin-tag{ margin-top:3px; font-size:clamp(13px,.6vw + 10px,15.5px);
  font-weight:600; color:var(--ink-muted); }

/* ── the message ───────────────────────────────────────────────────────── */
.signin-hello{
  flex:0 1 auto; min-height:0; margin-top:auto;
  display:flex; flex-direction:column; align-items:center; justify-content:center;
  gap:clamp(6px,1.2vh,14px);
}
.signin-mascot{ display:block; }
/* vh in the clamp is what makes this shrink on a landscape iPad instead of
   pushing the button off the bottom. */
.signin-mascot img{ display:block; width:auto; height:clamp(78px,17vh,190px); }
.signin-title{
  font-size:clamp(30px,3.4vw + 13px,52px); font-weight:800;
  letter-spacing:-.025em; line-height:1.08; color:var(--ink);
}
.signin-underline{ display:block; width:clamp(160px,24vw,300px); height:auto; margin:-2px auto 0; }
.signin-sub{
  max-width:min(27ch,100%);
  font-size:clamp(16px,.9vw + 11px,23px); font-weight:600; line-height:1.4;
  color:var(--ink-body);
}

/* ── the action zone ───────────────────────────────────────────────────── */
.signin-act{
  flex:none; width:100%; max-width:540px; margin-bottom:auto;
  /* a deliberate breath between the instruction and the button — bounded, so it
     never becomes the hole it was */
  margin-top:clamp(18px,6vh,72px);
  display:flex; flex-direction:column; align-items:stretch;
  gap:clamp(9px,1.5vh,15px);
}

/* THE BUTTON. It is the page. Nothing else on this screen is allowed to be
   this big, this green or this loud. */
.scan-btn{
  position:relative;
  display:flex; align-items:center; justify-content:center; gap:14px;
  width:100%; min-height:clamp(70px,10.5vh,104px);
  padding:16px 24px;
  border:none; border-radius:var(--r-pill);
  background:var(--green-ui); color:#fff;
  font-size:clamp(21px,1.5vw + 13px,31px); font-weight:800; letter-spacing:-.015em;
  box-shadow:0 8px 22px rgba(0,107,88,.30);
  cursor:pointer; -webkit-tap-highlight-color:transparent;
  transition:background-color .16s ease, box-shadow .16s ease, transform .16s cubic-bezier(.34,1.42,.5,1);
}
.scan-btn svg{ width:clamp(26px,2.4vw,38px); height:clamp(26px,2.4vw,38px); flex:none; }
.scan-btn:hover{ background:var(--green-800); box-shadow:0 10px 28px rgba(0,107,88,.34); }
.scan-btn:active{ transform:scale(.97); box-shadow:0 4px 12px rgba(0,107,88,.28); }
/* A slow halo, once every three seconds. A child who cannot yet read the label
   still sees which thing on the screen is alive. */
.scan-btn::after{
  content:""; position:absolute; inset:-7px; border-radius:inherit; pointer-events:none;
  border:3px solid rgba(0,158,127,.4); opacity:0;
  animation:scan-halo 3s ease-out infinite;
}
@keyframes scan-halo{
  0%{ transform:scale(.985); opacity:0; }
  22%{ opacity:.8; }
  70%,100%{ transform:scale(1.045); opacity:0; }
}

/* The fallback. Findable, never competing: no fill, no shadow, muted ink —
   but still a 52px target, because it is a six-year-old's finger. */
.quiet-btn{
  align-self:center; min-height:52px; padding:12px 22px;
  border:none; background:transparent; border-radius:var(--r-pill);
  color:var(--ink-muted); font-size:clamp(15px,.5vw + 12px,18px); font-weight:700;
  text-decoration:underline; text-decoration-thickness:2px; text-underline-offset:5px;
  text-decoration-color:rgba(102,108,160,.42);
  cursor:pointer; transition:color .15s ease, background-color .15s ease;
}
.quiet-btn:hover{ color:var(--ink-body); background:rgba(23,32,94,.045); }

.manual-panel{
  display:flex; flex-direction:column; gap:clamp(10px,1.4vh,14px);
  text-align:left; margin-top:2px;
  background:var(--white); border:1px solid var(--line); border-radius:var(--r-xl);
  box-shadow:var(--shadow-md); padding:clamp(18px,3.4vw,26px);
}
.manual-intro{ font-size:15.5px; font-weight:600; line-height:1.5; color:var(--ink-body); }
.manual-field{ display:flex; flex-direction:column; gap:6px; }
.manual-panel label{ font-size:14.5px; font-weight:700; color:var(--ink); }
.manual-panel input{
  width:100%; min-height:58px; padding:10px 14px;
  border:2px solid var(--line); border-radius:var(--r-md); background:var(--band);
  font-size:22px; font-weight:700; letter-spacing:.06em; text-align:center; color:var(--ink);
  transition:border-color .15s ease, box-shadow .15s ease, background-color .15s ease;
}
.manual-panel input::placeholder{ color:#A6ADCB; font-weight:600; letter-spacing:.04em; }
.manual-panel input:focus{
  outline:none; border-color:var(--green-ui); background:var(--white);
  box-shadow:0 0 0 4px rgba(0,129,106,.15);
}
.manual-go{
  display:flex; align-items:center; justify-content:center; gap:10px;
  width:100%; min-height:58px; margin-top:2px; padding:12px 24px;
  border:none; border-radius:var(--r-pill);
  background:var(--green-ui); color:#fff;
  font-size:20px; font-weight:800; letter-spacing:-.01em;
  box-shadow:0 4px 12px rgba(0,107,88,.22);
  cursor:pointer; transition:background-color .16s ease, transform .16s ease;
}
.manual-go:hover{ background:var(--green-800); }
.manual-go:active{ transform:scale(.98); }

.signin-foot{ text-align:center; font-size:clamp(13px,.4vw + 11px,15px);
  font-weight:600; color:var(--ink-muted); }

/* ── the gentle failure ────────────────────────────────────────────────────
   Never red. A child who mis-scans has done nothing wrong, and a red alarm
   tells them they have. Brand yellow, a smiling face, and text dark enough to
   read: #5C4708 on #FFF4DC is about 8:1. Announced with role="alert". */
.signin-err{
  display:flex; align-items:flex-start; gap:12px; text-align:left;
  padding:14px 18px;
  background:var(--yellow-050);
  border:1px solid var(--yellow-line); border-left:5px solid var(--yellow);
  border-radius:var(--r-lg);
  font-size:clamp(15px,.6vw + 11px,19px); font-weight:700; line-height:1.45;
  color:var(--yellow-ink);
}
.signin-err-face{ width:30px; height:30px; flex:none; margin-top:-1px; }

/* ── the scanner ───────────────────────────────────────────────────────────
   Its own classes, not the app-wide .overlay/.modal, so welcome.php's badge
   modal keeps the styling it has. display is driven inline by openScanner()
   and closeScanner() ('flex' / 'none'), so nothing here may set it. */
.scan-overlay{
  position:fixed; inset:0; z-index:20;
  align-items:center; justify-content:center;
  padding:clamp(14px,3vw,28px);
  background:rgba(11,17,54,.62);
  -webkit-backdrop-filter:blur(3px); backdrop-filter:blur(3px);
  animation:fade-scale 180ms ease;
}
.scan-modal{
  width:100%; max-width:min(560px,94vw);
  display:flex; flex-direction:column; align-items:center; gap:clamp(12px,2vh,18px);
  padding:clamp(18px,3vw,28px);
  background:var(--white); border-radius:var(--r-xl); box-shadow:var(--shadow-lg);
  text-align:center;
}
.scan-modal h3{ font-size:clamp(20px,1.1vw + 13px,27px); font-weight:800;
  letter-spacing:-.02em; line-height:1.2; color:var(--ink); }
.scan-stage{
  position:relative; width:100%; aspect-ratio:4/3;
  border-radius:var(--r-lg); overflow:hidden; background:#0B1136;
}
.scan-stage video{ width:100%; height:100%; object-fit:cover; display:block; }
/* Corner brackets: where to hold the card. Purely decorative. */
.scan-frame{ position:absolute; inset:11%; pointer-events:none; }
.scan-frame span{ position:absolute; width:34px; height:34px;
  border:5px solid rgba(255,255,255,.9); }
.scan-frame span:nth-child(1){ left:0; top:0; border-right:0; border-bottom:0; border-radius:10px 0 0 0; }
.scan-frame span:nth-child(2){ right:0; top:0; border-left:0; border-bottom:0; border-radius:0 10px 0 0; }
.scan-frame span:nth-child(3){ left:0; bottom:0; border-right:0; border-top:0; border-radius:0 0 0 10px; }
.scan-frame span:nth-child(4){ right:0; bottom:0; border-left:0; border-top:0; border-radius:0 0 10px 0; }
#scanMsg{ font-size:clamp(15px,.6vw + 11px,19px); font-weight:700; line-height:1.45;
  color:var(--ink-body); min-height:1.45em; }
.scan-cancel{
  min-height:52px; padding:12px 30px;
  border:2px solid var(--line); border-radius:var(--r-pill);
  background:var(--white); color:var(--ink-body);
  font-size:17px; font-weight:700; cursor:pointer;
  transition:border-color .15s ease, color .15s ease;
}
.scan-cancel:hover{ border-color:#CFD9E7; color:var(--ink); }

/* ── landscape iPad, and any desktop ───────────────────────────────────────
   A class set on desks is usually landscape. Two columns keeps the mascot and
   the heading out of the button's way, so the button is still comfortably
   inside the first viewport at 1080x810 with nothing scrolled. */
@media (min-width:860px) and (min-aspect-ratio:1/1){
  .signin-shell{
    display:grid; width:100%; max-width:1180px; margin:0 auto;
    grid-template-columns:1fr 1fr; grid-template-rows:auto 1fr;
    align-items:center; column-gap:clamp(28px,5vw,72px); row-gap:clamp(10px,2vh,24px);
  }
  .signin-head{ grid-column:1 / -1; }
  .signin-hello{ grid-column:1; grid-row:2; margin-top:0; }
  .signin-act{ grid-column:2; grid-row:2; justify-self:center; margin:0; }
  .signin-sub{ max-width:min(24ch,100%); }
}

/* A short landscape window (a phone on its side, a small laptop): the camera
   preview has to give up height or the Cancel button falls off the bottom. */
@media (max-height:620px){
  .scan-stage{ aspect-ratio:16/9; }
  .scan-modal{ gap:12px; }
}

@media (max-width:520px){
  .sky-sparkles{ display:none; }
}
@media (max-width:430px){
  .signin-act{ max-width:100%; }
  .manual-panel{ border-radius:var(--r-lg); }
  .signin-err{ font-size:15px; padding:12px 14px; }
}

/* ── movement is optional ──────────────────────────────────────────────────
   The blanket reduce rule near the top of this file shortens every animation
   to nothing, which would leave the halo stuck mid-flash. Turn it off outright
   and hold it as a plain steady ring instead. */
@media (prefers-reduced-motion: reduce){
  .signin-shell{ animation:none; }
  .scan-btn::after{ animation:none; opacity:.55; transform:none; }
  .scan-overlay{ animation:none; }
}
