/* mnozilica app styles, extracted from the chosen direction.
   Tokens live in variables.css; animations in animations.css. */

html{-webkit-text-size-adjust:100%;}
body{
  margin:0; overflow-x:hidden;
  font-family:var(--font-family); font-weight:600; line-height:1.15;
  color:var(--ink); background:#CDBDA2;
  -webkit-font-smoothing:antialiased;
}
button{font:inherit;color:inherit;border:0;background:none;cursor:pointer;text-align:center;}
svg{display:block;}

/* The UA rule for [hidden] is display:none at specificity (0,1,0), so any
   author rule setting display on a class beats it and the element stays
   visible. Every toggled panel below sets display, so this has to win. */
[hidden]{display:none !important;}

/* The paper, and its tooth. In the mockup this sat on a .phone box standing on
   a stage; here the page itself is the printed page. */
body{
  /* dvh, not vh, and the difference is visible. vh is the viewport with the
     browser's URL bar retracted, dvh is the viewport as it is right now, and
     every .screen is sized in dvh. With the bar showing, body was taller than
     the screen inside it by exactly the height of that bar, so the page
     scrolled and a strip of paper appeared under the round screen's blue.
     --vh is the same height measured with JS (layout.php): installed on a
     three-button phone, dvh itself comes out one bar-strip taller than the
     window the page really gets, and every screen scrolled by that strip. */
  min-height:var(--vh, 100dvh);
  background:var(--paper);
}
body::after{
  content:''; position:fixed; inset:0; pointer-events:none; z-index:var(--z-overlay);
  background-image:var(--paper-tooth); background-size:120px 120px;
  mix-blend-mode:multiply; opacity:.21;
}
.screen{
  position:relative; z-index:var(--z-content);
  min-height:var(--vh, 100dvh); display:flex; flex-direction:column;
  max-width:520px; margin-inline:auto;
  padding:var(--sp-5) var(--sp-4) calc(var(--sp-4) + env(safe-area-inset-bottom, 0px));
}

/* Installed, the viewport runs edge to edge under the system gesture bar, and
   Android does not reliably report a bottom inset for it, so the padding above
   comes out at a bare 16px. Everything this app pins to the bottom of the
   screen with margin-top:auto (the round keypad, the result actions, the parent
   tools) then lands underneath that bar. In a browser tab the browser's own
   chrome already occupies the strip, which is why this only shows up once the
   app is on the home screen and why the fix is scoped to that case. max() takes
   the real inset where the platform gives one and a floor where it does not.
   The floor is the 48dp three-button bar, not the thinner gesture bar: with a
   28px floor the keypad's bottom row and the Odjava button still sat half
   under the bar on a phone using button navigation. Gesture phones get a few
   spare pixels; button phones get their controls back. */
@media (display-mode: standalone) {
  .screen{ padding-bottom:calc(var(--sp-4) + max(env(safe-area-inset-bottom, 0px), 48px)); }
}

/* overprint: two plates crossing, the overlap goes dark */
.plate{ position:absolute; border-radius:50%; mix-blend-mode:multiply; z-index:var(--z-bg); pointer-events:none; }

/* ------------ shared print furniture ------------ */
.label{ margin:0; font-size:var(--fs-xs); font-weight:800; letter-spacing:.1em; color:var(--gray-700); }
.rule{ flex:1; height:2px; background:repeating-linear-gradient(90deg,var(--gray-400) 0 4px,transparent 4px 9px); }
.row-label{ display:flex; align-items:center; gap:var(--sp-2); margin-bottom:var(--sp-2); }

/* ------------ the x / : toggle ------------
   Which operation the screen is showing is one attribute on <html>, set in the
   head before the page paints. Everything that differs between the two is
   rendered twice as a [data-op-pane] and this picks, so switching moves no
   layout and needs no request. display:revert rather than a fixed value,
   because a pane is a <div> in one place and a <span> inside a label in
   another. */
[data-op-pane]{ display:none; }
html[data-op="mul"] [data-op-pane="mul"],
html[data-op="div"] [data-op-pane="div"]{ display:revert; }

.op-switch{ display:inline-flex; border:3px solid var(--ink); border-radius:var(--radius-full); overflow:hidden; }
.op-pick{
  /* 48 square, the floor for anything a child taps. A missed switch leaves her
     playing the other operation, which is worse than the row being taller. */
  min-width:48px; min-height:48px; padding:0 var(--sp-2); touch-action:manipulation;
  background:var(--white); color:var(--gray-500);
  border:0; font-family:inherit; font-size:var(--fs-base); font-weight:800; line-height:1;
  cursor:pointer;
}
.op-pick + .op-pick{ border-left:3px solid var(--ink); }
html[data-op="mul"] .op-pick[data-op-pick="mul"],
html[data-op="div"] .op-pick[data-op-pick="div"]{ background:var(--primary); color:var(--ink); }
.card{
  position:relative; background:var(--card-bg);
  border:3px solid var(--ink); border-radius:var(--radius-lg);
  box-shadow:var(--card-shadow-lg);
}
/* the way back out. Secondary, so it sits top-left where it is expected and
   out of the thumb arc the primary actions own. */
.back{
  width:48px; height:48px; flex:none; display:grid; place-items:center;
  background:var(--white); color:var(--ink);
  border:3px solid var(--ink); border-radius:var(--radius-md);
  box-shadow:3px 3px 0 var(--ink);
  transition:transform var(--transition-fast),box-shadow var(--transition-fast);
}
.back:active{ transform:translate(3px,3px); box-shadow:0 0 0 var(--ink); }
.back svg{ width:24px; height:24px; }

/* two-plate animal heads. Emoji rendered in the system's full-colour gradient
   style, the only thing on the page that was not flat spot ink. */
.ani{ width:30px; height:30px; }
.face{ display:flex; justify-content:center; }

.count{
  display:inline-block;
  background:var(--forest-green); border:2px solid var(--ink); border-radius:var(--radius-full);
  padding:3px 12px 5px; font-size:var(--fs-sm); font-weight:800;
  transform:rotate(3deg); box-shadow:2px 2px 0 rgba(33,27,19,.28);
}

/* ============================================================
   SCREEN 1: POCETNA
   ============================================================ */
.s-home{ background:var(--gray-100); }

.gap{ flex:1; min-height:var(--sp-4); }
.gap-wide{ flex:1.5; }
/* straight. Crooked has to mean "stuck-in sticker", so the printed page under
   the stickers does not wobble too. */
.hero{ padding:var(--sp-3) var(--sp-4) var(--sp-4); }
.hero-top{ display:flex; align-items:center; gap:var(--sp-3); }
.avatar{
  width:54px; height:54px; flex:none; border-radius:50%;
  border:3px solid var(--ink); background:var(--primary-light);
  display:grid; place-items:center;
  box-shadow:2px 3px 0 rgba(33,27,19,.28);
}
.avatar .ani{ width:46px; height:46px; }
.hero h1{ margin:0; font-size:var(--fs-2xl); font-weight:800; letter-spacing:-.01em; }
.badge{
  display:inline-block; margin-top:6px;
  background:var(--ink); color:var(--white); border-radius:var(--radius-full);
  padding:2px 12px 4px; font-size:var(--fs-sm); font-weight:800; letter-spacing:.04em;
}
.level-track{ display:flex; gap:3px; }
/* One block per level. Dashed is a level she has not opened, solid is one she
   has finished, and the block she is standing in fills from the left. */
.level-seg{
  position:relative; overflow:hidden;
  flex:1; height:14px; border-radius:3px;
  border:2px dashed var(--gray-400); background:var(--gray-50);
}
.level-seg.done, .level-seg.now{ border:2px solid var(--forest-dark); box-shadow:1px 1px 0 rgba(33,27,19,.25); }
.level-seg.done{ background:var(--forest-green); }
.level-seg.now i{ position:absolute; inset:0 auto 0 0; background:var(--forest-green); }

/* this week, three players in one row */
.week{ display:flex; gap:var(--sp-2); align-items:center; }
.player{
  flex:1; min-width:0; text-align:center;
  border:2px solid var(--ink); border-radius:var(--radius-md);
  background:var(--white); padding:7px 4px 6px;
  box-shadow:2px 2px 0 rgba(33,27,19,.2);
}
.player .who{ font-size:var(--fs-sm); font-weight:700; margin-top:3px;
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.player .pts{ font-size:var(--fs-xl); font-weight:800; line-height:1.05; }
.player .pts-unit{ font-size:var(--fs-xs); font-weight:700; letter-spacing:.06em; color:var(--gray-700);
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.player .pts-total{ display:block; margin:4px 2px 0; padding-top:3px;
  border-top:1px solid rgba(33,27,19,.22); font-size:var(--fs-xs); font-weight:700; color:var(--gray-700);
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
/* ink on fluorescent pink, the riso combination. Paper-white on this pink is
   3.4:1, which is not a colour for reading names off. */
.player.me{
  background:var(--primary); color:var(--ink);
  border-width:3px; transform:rotate(-1.4deg) scale(1.04);
  box-shadow:3px 4px 0 rgba(33,27,19,.32);
}
.player.me .pts-unit{ color:var(--ink); }
.player.me .pts-total{ color:var(--ink); }

/* pending duels */
.duel{
  display:flex; align-items:center; gap:var(--sp-3);
  border:2.5px solid var(--ink); border-radius:var(--radius-md);
  background:var(--white); padding:7px 8px 7px var(--sp-3);
  box-shadow:3px 3px 0 rgba(33,27,19,.22);
}
.duel + .duel{ margin-top:10px; }
.duel .face .ani{ width:32px; height:32px; }
.duel .txt{ flex:1; min-width:0; font-size:var(--fs-base); font-weight:700; }
/* The accent goes inside the outline, not instead of it. As a border-left it
   replaced the black stroke on that edge, so the card read as having a bite
   taken out of it rather than as having a marker. */
.duel.live{
  padding-left:var(--sp-5);
  box-shadow:inset 9px 0 0 var(--primary), 3px 3px 0 rgba(33,27,19,.22);
}
.duel.wait{ background:var(--gray-50); border-style:dashed; box-shadow:none; color:var(--gray-700); }
.btn-play{
  height:48px; padding:0 var(--sp-5);
  background:var(--primary); color:var(--ink);
  border:2.5px solid var(--ink); border-radius:var(--radius-md);
  font-size:var(--fs-base); font-weight:800; letter-spacing:.04em;
  box-shadow:3px 3px 0 var(--ink);
  transition:transform var(--transition-fast),box-shadow var(--transition-fast);
}
.btn-play:active{ transform:translate(3px,3px); box-shadow:0 0 0 var(--ink); }
.waiting{
  height:48px; width:62px; display:flex; align-items:center; justify-content:center; gap:5px;
  border:2.5px dashed var(--gray-400); border-radius:var(--radius-md);
}
.waiting i{ width:6px; height:6px; border-radius:50%; background:var(--gray-400); }

/* The mockup's three-across mode tiles used to be styled here: height:126px,
   flex-direction:column, centred, with their own colours and :active. The home
   screen has stacked them in a single column since it was built, so every one
   of those declarations was either overridden further down or actively wrong.
   The two that were neither are what made the buttons 95px tall with their text
   centred: nothing below ever set flex-direction back to row, and nothing
   overrode the fixed height. Styled in one place now, under HOME. */

/* .to-grid, the mockup's way through to the album, was styled here and again
   under HOME, the same split that made the mode buttons 95px tall. Both are
   gone: the album is reached from its own ladder's card now, the same way
   written multiplication is. */

/* ============================================================
   SCREEN 2: IGRA, a card being played on a blue table
   ============================================================ */
.s-round{ background:var(--ocean-blue); color:var(--white); height:var(--vh, 100dvh); }
.round-head{ display:flex; align-items:center; gap:var(--sp-3); }
.qtag{
  margin:0;
  background:var(--white); color:var(--ink);
  border:2.5px solid var(--ink); border-radius:var(--radius-full);
  padding:4px 15px 6px; font-size:var(--fs-base); font-weight:800; letter-spacing:.03em;
  transform:rotate(-1.2deg); box-shadow:2px 2px 0 rgba(18,26,60,.4);
}
/* Trening serves twelve of these and Sprint forty, and a flex item will not
   shrink below its own automatic minimum unless told to. Measured at 390px:
   twelve dots ran 22px past the row and off the side of the screen. */
.dots{ display:flex; gap:5px; margin-top:var(--sp-2); }
.dot{
  flex:1; min-width:0; aspect-ratio:1; max-height:26px; border-radius:var(--radius-sm);
  border:2px solid var(--ink); display:grid; place-items:center;
}
.dot svg{ width:72%; height:72%; }
.dot-ok{ background:var(--success); color:var(--white); box-shadow:1px 2px 0 rgba(10,20,50,.5); }
.dot-bad{ background:var(--error); color:var(--white); box-shadow:1px 2px 0 rgba(10,20,50,.5); }
.dot-now{
  background:var(--primary); border-width:3px;
  box-shadow:2px 3px 0 rgba(10,20,50,.55);
  animation:beat 1.4s ease-in-out infinite;
}
.dot-now::after{  /* the slot you are filling right now */
  content:''; width:7px; height:7px; border-radius:50%; background:var(--white);
}
.dot-empty{ background:rgba(255,251,240,.16); border-style:dashed; border-color:var(--ocean-light); }
@keyframes beat{ 0%,100%{transform:scale(1);} 50%{transform:scale(1.16);} }

/* time as a draining bar. Never a counting digit. */
.timer{
  margin-top:var(--sp-3); height:18px; overflow:hidden;
  border:2.5px solid var(--ink); border-radius:var(--radius-full);
  background:var(--ocean-dark); box-shadow:2px 2px 0 rgba(10,20,50,.45);
}
.timer i{
  display:block; height:100%; width:60%;
  background:repeating-linear-gradient(90deg,var(--forest-green) 0 7px,#D08F00 7px 10px);
  border-right:2.5px solid var(--ink);
}

/* The controls scale with viewport height, because they are what divides it up.
   A 104px question and a 101px answer box fit an 844-tall phone and overflow a
   640-tall one by 64px, and a round screen that scrolls is a round screen where
   the controls are somewhere below the fold. */
.s-round .choice{ min-height:clamp(58px, 9.5dvh, 76px); }
.s-round .key{ height:clamp(48px, 7.6dvh, 62px); }

/* The CARD scales with the space it is actually given, which is not the same
   number. The head, the dots, the hint button and the keypad come off the top
   of the viewport first, and pisano adds the paper note on top of that: 96px
   the other modes never spend. Sized against dvh the card asked for 170px of a
   122px playfield on a 650-tall phone and scrolled the question off its own
   card on question one, which is a written round where she cannot see the sum
   she is copying onto paper.
   cqh reads the playfield's own box, so every fixed row above and below is
   already subtracted and no mode needs its own numbers. It also drops the last
   dvh out of the card: layout.php sizes .s-round with --vh (innerHeight)
   because Chrome reports dvh a nav-bar strip too tall in an installed app, and
   a card measured in dvh inside a screen measured in --vh inherited exactly
   that disagreement. */
.playfield{ container-type:size; }
.s-round .q{ font-size:clamp(30px, 36cqh, 104px); }
.s-round .answer{ height:clamp(34px, 28cqh, 92px); margin-top:clamp(6px, 5cqh, 24px); }
.s-round .qcard{ padding:clamp(8px, 7cqh, 32px) var(--sp-4) clamp(6px, 6cqh, 24px); }

/* The card sits at the TOP of the space, not centred in it. The rule is that
   the question is at the top and the controls are at the bottom, and centring
   drifts the question down toward the thumb zone on a tall phone. The flex:1
   still hands the leftover height to this element so the controls stay pinned
   at the bottom. */
/* The card is allowed to be taller than the space left for it, and on a short
   phone with the worked steps open it is: measured at 780px tall, 100px past
   the playfield and 92px over the top row of the keypad. min-height:0 lets the
   box shrink, which is what pins the controls to the bottom, but nothing was
   telling the overflow where to go, so it went over the keys. It scrolls inside
   its own area now, and overscroll-behavior stops that scroll turning into a
   page scroll once it reaches the end. */
.playfield{
  flex:1; min-height:0; overflow-y:auto; overscroll-behavior:contain;
  display:flex; align-items:flex-start; justify-content:center; padding:var(--sp-2) 0;
}
.qcard{
  position:relative; width:100%;
  background:var(--card-bg); color:var(--ink);
  border:3.5px solid var(--ink); border-radius:var(--radius-xl);
  padding:var(--sp-8) var(--sp-4) var(--sp-6);
  box-shadow:7px 8px 0 rgba(10,20,50,.5);
  transform:rotate(-1.4deg);
}
.qcard::before{  /* the plate that did not register */
  content:''; position:absolute; inset:0; z-index:-1;
  border:3.5px solid var(--primary); border-radius:var(--radius-xl);
  transform:translate(-6px,-6px);
}
.suit{ position:absolute; font-size:var(--fs-xl); font-weight:800; color:var(--primary); opacity:.85; }
.suit.tl{ top:6px; left:14px; }
.suit.br{ bottom:6px; right:14px; }
/* No offset plate on the question. A saturated pink fringe against near-black
   letterforms makes the type vibrate: the two wavelengths focus at different
   depths, so the eye cannot settle on either. Real riso prints type on a single
   plate for exactly this reason. Misregistration stays on the plates and
   stickers below, where it is an area rather than an edge. */
.q{
  text-align:center; font-size:var(--fs-5xl); font-weight:800; line-height:1;
  letter-spacing:-.02em;
}
.q em{
  font-style:normal; font-size:.64em; color:var(--ocean-blue);
  position:relative; top:-.23em; padding:0 .04em;
}
/* The colon sits on the baseline at nearly full size. The lift and the shrink
   that make a × read as a multiplication sign turn a colon into a full stop,
   and the two dots have to be unmistakable at arm's length. */
.q em.colon{ font-size:.86em; top:-.06em; }
.answer{
  margin:var(--sp-6) auto 0; width:76%; height:96px;
  border:3px dashed var(--gray-400); border-radius:var(--radius-lg);
  background:var(--gray-50);
  display:flex; align-items:center; justify-content:center; gap:5px;
}
.answer b{ font-size:var(--fs-4xl); font-weight:800; line-height:1; }
.caret{ width:5px; height:48px; background:var(--primary); border-radius:2px;
  animation:blink 1s steps(1) infinite; }
@keyframes blink{ 0%,55%{opacity:1;} 56%,100%{opacity:0;} }

/* keypad: chunky printed tiles, pinned to the bottom */
.pad{ display:grid; grid-template-columns:repeat(3,1fr); gap:10px; }
.key{
  height:62px; border:3px solid var(--ink); border-radius:var(--radius-md);
  /* No double-tap zoom on the keypad, which also stops the browser holding a
     tap back to see whether a second one is coming. 100 needs the 0 key twice
     in a row, and that pair is what a zoom gesture looks like. */
  touch-action:manipulation;
  background:var(--white); color:var(--ink);
  font-size:var(--fs-2xl); font-weight:800; line-height:1;
  box-shadow:0 5px 0 var(--ink), 3px 6px 0 rgba(10,20,50,.4);
  transition:transform var(--transition-fast),box-shadow var(--transition-fast);
}
.key:active{ transform:translateY(5px); box-shadow:0 0 0 var(--ink), 3px 1px 0 rgba(10,20,50,.4); }
.key-clear{ background:var(--forest-green); }
.key-clear svg{ width:28px; height:28px; margin:0 auto; }
.key-ok{ background:var(--success); color:var(--white); font-size:var(--fs-base); letter-spacing:.03em; }

/* ============================================================
   SCREEN 3: TABLICA, the album page
   ============================================================ */
.s-grid{ background:var(--gray-100); padding-left:var(--sp-2); padding-right:var(--sp-2); }
.s-grid .page-head{ padding:0 var(--sp-1); }
/* Ten columns at a real tap size do not fit a phone: at 390px the old
   repeat(10,1fr) gave 32px-wide cells against this project's own 48px floor,
   and the floor was only ever met vertically. The table scrolls sideways
   instead, and the row header stays behind so a cell keeps its row.
   The scroll lives on an inner element with no padding, so nothing shows in a
   gap between the album's border and the pinned column. */
.album{
  margin-top:var(--sp-3); padding:4px;
  background:var(--forest-light); border:3px solid var(--ink); border-radius:var(--radius-lg);
  box-shadow:var(--card-shadow-lg);
}
.grid-scroll{ overflow-x:auto; overscroll-behavior-x:contain; }
.gh-corner,.gh-row{ position:sticky; left:0; z-index:2; }
.grid-intro{
  margin:var(--sp-2) var(--sp-1) 0;
  font-size:var(--fs-sm); font-weight:700; color:var(--gray-700);
}
.perf{ height:2px; margin:0 4px var(--sp-2);
  background:repeating-linear-gradient(90deg,var(--gray-400) 0 3px,transparent 3px 9px); }
.grid{
  display:grid; grid-template-columns:34px repeat(10,48px);
  grid-template-rows:22px repeat(10,48px); gap:2px; width:max-content;
}
.gh{
  display:grid; place-items:center;
  font-size:var(--fs-sm); font-weight:800; letter-spacing:-.04em; color:var(--white);
  background:var(--gray-800); border-radius:var(--radius-sm);
}
.gh-row{ font-size:var(--fs-base); }
.gh-corner{ background:var(--primary); color:var(--ink); font-size:var(--fs-sm); }
.cell{
  position:relative; display:grid; place-items:center;
  border-radius:5px; font-size:16px; font-weight:800; letter-spacing:-.03em;
}
.cell span{ position:relative; z-index:1; }

/* empty slot: aligned, dashed, unprinted. The only cell type that is not a sticker,
   so "never seen" reads as a form difference, not just a colour difference. */
.m0{ background:var(--gray-200); border:2px dashed var(--mastery-0); color:var(--mastery-0); }

/* Not yet hers to answer. Flatter and quieter than .m0, with no dashed outline,
   because a dash says "a sticker goes here" and this is a slot the album has
   not reached. Sixty of these at level 1, and drawing them as .m0 is what made
   an album two thirds full of waiting look two thirds full of skipped. */
.cell.locked, .legend .leg i.locked{ background:var(--gray-100); border:2px solid var(--gray-200); }

/* stuck-in sticker: die-cut paper edge, ink outline, sitting crooked */
.stuck{
  border:2px solid var(--white);
  box-shadow:0 0 0 1.5px var(--edge), 1px 2px 0 rgba(33,27,19,.3);
  transform:rotate(var(--rot));
}
.r0{--rot:-2.4deg;} .r1{--rot:1.9deg;} .r2{--rot:-1.2deg;}
.r3{--rot:2.7deg;} .r4{--rot:-3.1deg;} .r5{--rot:1.1deg;}

.m1{ --edge:#7E0F09; background:var(--mastery-1); color:var(--white); }
.m2{ --edge:#8C3A05; background:var(--mastery-2); color:var(--ink); }
.m3{ --edge:#B79800; background:var(--mastery-3); color:var(--ink); }
.m4{ --edge:#004C2D; background:var(--mastery-4); color:var(--white); }

/* foil, the rare sticker. Hue now does the m3-to-m4 separating on its own, so
   the foil is charm rather than load-bearing: hard riso stripes instead of the
   old 32%-white sweep that vanished at this size, and a gold star. */
.m4::after{
  content:''; position:absolute; inset:0; border-radius:3px;
  background:repeating-linear-gradient(118deg,
    rgba(255,251,240,.26) 0 2.5px, rgba(255,251,240,0) 2.5px 8px);
}
.m4::before{
  content:'\2605'; position:absolute; top:0; right:1px; z-index:2;
  font-size:.78em; line-height:1.1; color:#FFE434;
  text-shadow:0 1px 0 rgba(0,45,26,.9);
}
.m4.stuck{ box-shadow:0 0 0 1.5px var(--edge), 1px 3px 0 rgba(33,27,19,.38); }

.legend{ display:grid; grid-template-columns:repeat(3,1fr); gap:7px 6px;
  margin-top:var(--sp-3); padding:0 var(--sp-1); }
.leg{ display:flex; align-items:center; gap:5px; font-size:var(--fs-xs); font-weight:700; color:var(--gray-700); }
.leg .cell{ width:18px; height:22px; flex:none; }
.leg .m4::before{ font-size:.6em; }

.grid-cta{
  margin-top:auto; display:flex; align-items:center; justify-content:center; gap:10px;
  height:62px; border:3px solid var(--ink); border-radius:var(--radius-lg);
  background:var(--ocean-blue); color:var(--white);
  font-size:var(--fs-lg); font-weight:800; letter-spacing:.02em;
  box-shadow:4px 5px 0 var(--ink);
  transition:transform var(--transition-fast),box-shadow var(--transition-fast);
}
.grid-cta:active{ transform:translate(4px,5px); box-shadow:0 0 0 var(--ink); }
.grid-cta svg{ width:24px; height:24px; }

@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{ animation:none !important; transition:none !important; }
}

/* ============================================================
   LOGIN
   Not in the mockup, which had no login screen. Built from the
   print furniture above: the same .avatar, .back, .pad and .key.
   ============================================================ */
.s-login{ gap:var(--sp-4); }
.login-title{
  margin:0; font-size:var(--fs-3xl); font-weight:800; letter-spacing:-.01em;
}
.login-error{
  margin:0; padding:10px var(--sp-3);
  background:var(--error-light); border:3px solid var(--error);
  border-radius:var(--radius-md); box-shadow:3px 3px 0 rgba(33,27,19,.22);
  font-size:var(--fs-base); font-weight:700; transform:rotate(-.6deg);
}

.tiles{
  list-style:none; margin:var(--sp-2) 0 0; padding:0;
  display:grid; grid-template-columns:repeat(2,1fr); gap:var(--sp-3);
}
.tile{
  width:100%; min-height:132px;
  display:flex; flex-direction:column; align-items:center; justify-content:center; gap:var(--sp-2);
  background:var(--card-bg); border:3px solid var(--ink); border-radius:var(--radius-lg);
  box-shadow:var(--card-shadow-lg);
  transition:transform var(--transition-fast),box-shadow var(--transition-fast);
}
/* Each tile sits at its own angle, like a sticker pressed on by hand. Set per
   position rather than randomly, so it does not jump between page loads. */
.tiles li:nth-child(1) .tile{ transform:rotate(-1.6deg); }
.tiles li:nth-child(2) .tile{ transform:rotate(1.2deg); }
.tiles li:nth-child(3) .tile{ transform:rotate(1.5deg); }
.tiles li:nth-child(4) .tile{ transform:rotate(-1deg); }
.tile:active{ transform:translate(3px,4px) rotate(0deg); box-shadow:0 0 0 var(--ink); }
.tile .avatar{ width:66px; height:66px; }
.tile .avatar .ani{ width:56px; height:56px; }
.tile-name{ font-size:var(--fs-lg); font-weight:800; }

.s-login form{ flex:1; display:flex; flex-direction:column; }
.login-adults{ margin-top:auto; align-self:center; }
.pin-user{ flex:1; display:flex; flex-direction:column; gap:var(--sp-1); }
.pin-user input{
  min-height:48px; padding:0 var(--sp-3);
  font:inherit; font-weight:700; color:var(--ink);
  background:var(--white); border:3px solid var(--ink); border-radius:var(--radius-md);
}
.pinpad{ flex:1; display:flex; flex-direction:column; gap:var(--sp-4); }
.pin-who{ display:flex; align-items:center; gap:var(--sp-3); }
.pin-hello{ margin:0; font-size:var(--fs-xl); font-weight:800; line-height:1.25; }
.pin-hello .label{ display:block; margin-top:2px; }

/* Nine dots have to fit a 320px viewport: 9x22 + 8x8 = 262px inside the
   288px the screen padding leaves. */
.pin-dots{
  list-style:none; margin:0; padding:0; min-height:22px;
  display:flex; justify-content:center; gap:var(--sp-2);
}
.pin-dots li{
  width:22px; height:22px; border-radius:50%;
  border:3px solid var(--ink); background:var(--white);
  transition:background var(--transition-base),transform var(--transition-base);
}
.pin-dots li.is-filled{ background:var(--primary); transform:scale(1.14); }

/* The keypad owns the bottom third, which is the only comfortably reachable
   area one-handed. Everything above it can be reached or not; this cannot. */
.pinpad .pad{ margin-top:auto; }

/* ============================================================
   PLACEHOLDER AND ERROR SCREENS
   Enough that nothing renders unstyled. MNZ-022 builds the real
   home screen and MNZ-023 the parent view.
   ============================================================ */
.s-plain{ gap:var(--sp-4); }
.s-plain h1{ margin:0; font-size:var(--fs-3xl); font-weight:800; letter-spacing:-.01em; }
.s-plain p{ margin:0; font-size:var(--fs-base); }
.s-plain .card{ padding:var(--sp-4); }
.s-plain .who-line{ display:flex; align-items:center; gap:var(--sp-3); }
.btn{
  display:inline-flex; align-items:center; justify-content:center; gap:var(--sp-2);
  text-decoration:none;
  min-height:52px; padding:0 var(--sp-5);
  background:var(--primary); color:var(--ink);
  border:3px solid var(--ink); border-radius:var(--radius-md);
  font-size:var(--fs-lg); font-weight:800;
  box-shadow:0 5px 0 var(--ink);
  transition:transform var(--transition-fast),box-shadow var(--transition-fast);
}
.btn:active{ transform:translateY(5px); box-shadow:0 0 0 var(--ink); }
.btn-quiet{ background:var(--white); }

/* ============================================================
   OPERATOR PAGES
   Not for a child. Dense, plain, readable at a glance.
   ============================================================ */
.mig-list{ list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:var(--sp-2); }
.mig{
  display:grid; grid-template-columns:28px 1fr; gap:2px var(--sp-2); align-items:baseline;
  background:var(--card-bg); border:2px solid var(--ink); border-radius:var(--radius-md);
  padding:var(--sp-2) var(--sp-3); box-shadow:2px 2px 0 rgba(33,27,19,.2);
}
.mig__state{ font-weight:800; font-size:var(--fs-sm); text-align:center; }
.mig__name{ font-weight:700; font-size:var(--fs-base); word-break:break-all; }
.mig__when{ grid-column:2; font-size:var(--fs-xs); color:var(--gray-700); }
.mig--applied .mig__state{ color:var(--success); }
.mig--pending{ border-style:dashed; background:var(--gray-50); }
.mig--pending .mig__state{ color:var(--gray-500); }
.mig--missing{ border-color:var(--error); }
.mig--missing .mig__state{ color:var(--error); }

/* ============================================================
   ROUND, the screen that decides whether the app gets opened twice
   ============================================================ */
.s-round{ gap:var(--sp-2); }

/* Four large buttons, an alternative to the pad on the easy levels and in
   Trening. Same bottom-third placement, because that is the only part of a
   phone a child can reach one-handed. */
.choices{
  margin-top:auto;
  display:grid; grid-template-columns:1fr 1fr; gap:10px;
}
.choice{
  min-height:76px; padding:0 var(--sp-2); touch-action:manipulation;
  background:var(--white); color:var(--ink);
  border:3px solid var(--ink); border-radius:var(--radius-md);
  font-size:var(--fs-2xl); font-weight:800; line-height:1;
  box-shadow:0 5px 0 var(--ink), 3px 6px 0 rgba(10,20,50,.4);
  transition:transform var(--transition-fast),box-shadow var(--transition-fast);
}
.choice:active{ transform:translateY(5px); box-shadow:0 0 0 var(--ink), 3px 1px 0 rgba(10,20,50,.4); }

.s-round .pad{ margin-top:auto; }

/* The worked steps arrive as one string with newlines in it, so white-space has
   to keep them. Deliberately not a column layout: the app's face is not
   monospaced, and digits that almost line up teach worse than lines that never
   pretended to. */
.hint{
  white-space:pre-line;
  margin:var(--sp-3) 0 0; padding:var(--sp-2) var(--sp-3);
  background:var(--forest-light); border:2px dashed var(--ink); border-radius:var(--radius-md);
  font-size:var(--fs-lg); font-weight:700; text-align:center;
}
/* Sits with the card in the markup, not under the answer controls. Down there
   it competes for the one part of the screen a thumb can reach, and it is the
   control she needs least often. */
.hint-btn{
  align-self:center; min-height:48px; padding:0 var(--sp-4);
  background:var(--forest-green); font-size:var(--fs-base);
}

/* Feedback. Correct and wrong have to be told apart without reading, because
   the next question is already coming. */
.qcard.is-correct{ animation:correctPulse 420ms ease-out; border-color:var(--success); }
.qcard.is-wrong{ animation:shake 420ms ease-out; border-color:var(--error); }
.qcard.is-correct .answer{ color:var(--success); }
.qcard.is-wrong .answer{ color:var(--error); }

.timer i{ transition:width 100ms linear; }
.timer.is-low i{ background:var(--error); }

/* Sprint generates far more questions than anyone finishes, so it shows no dot
   row at all: forty dots is both a lie about how many she has to answer and
   wide enough to push the page sideways. */
.s-round .dots[hidden]{ display:none !important; }

/* Result */
.s-result{ gap:var(--sp-4); }
.result-score{ text-align:center; }
.result-score b{ display:block; font-size:var(--fs-5xl); line-height:1; font-weight:800; }
.result-score span{ font-size:var(--fs-lg); font-weight:700; color:var(--gray-700); }
.result-score .result-total{ margin-left:8px; font-size:.42em; }
.result-rows{ list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:var(--sp-2); }
.result-rows li{
  display:flex; justify-content:space-between; align-items:baseline; gap:var(--sp-3);
  background:var(--card-bg); border:2px solid var(--ink); border-radius:var(--radius-md);
  padding:var(--sp-2) var(--sp-3); box-shadow:2px 2px 0 rgba(33,27,19,.2);
  font-weight:700;
}
.result-rows b{ font-size:var(--fs-xl); }
/* A value that is a number never breaks. "Naučeno na otvorenim razinama" is
   long enough to squeeze the value column until "65 od 100" wrapped to "65 od"
   and "100", which reads as two numbers rather than one. The label wraps
   instead, which is what a label is for. Scoped to .num because the written
   tier rows put words in the same slot ("još nije probala") and those should
   still wrap. */
.result-rows li > span{ min-width:0; }
.result-rows b.num{ flex:none; white-space:nowrap; }
/* Why the points do not match the score. Stated rather than left as a number
   that quietly does not add up. */
.result-note{ margin:0; font-size:var(--fs-sm); font-weight:700; color:var(--gray-700); }
.fact-list{ display:flex; flex-wrap:wrap; gap:6px; }
.fact-list span{
  background:var(--mastery-3); border:2px solid var(--ink); border-radius:var(--radius-full);
  padding:2px 10px 4px; font-size:var(--fs-sm); font-weight:800;
  transform:rotate(-1.5deg);
}
.fact-list span:nth-child(even){ transform:rotate(1.5deg); background:var(--forest-light); }
.result-actions{ margin-top:auto; display:flex; flex-direction:column; gap:var(--sp-2); }
.result-flagged{
  background:var(--error-light); border:3px solid var(--error); border-radius:var(--radius-md);
  padding:var(--sp-3); font-weight:700; box-shadow:3px 3px 0 rgba(33,27,19,.22);
}

/* Sprint's overall clock, thinner than the per-question one so the two are
   never mistaken for each other at a glance. */
.timer--overall{ height:8px; opacity:.85; }
.timer--overall i{ background:var(--forest-green); }
.timer--overall.is-low i{ background:var(--error); }

/* ============================================================
   GRID
   The mockup styled the cells as spans. They are links now, because
   tapping one drills that fact, so they need the link resets and a
   focus ring a keyboard can see.
   ============================================================ */
.cell{ text-decoration:none; -webkit-tap-highlight-color:transparent; }
.cell:focus-visible, .back:focus-visible, .btn:focus-visible, .key:focus-visible, .choice:focus-visible, .tile:focus-visible, .help-shortcut:focus-visible{
  outline:3px solid var(--ocean-blue); outline-offset:2px;
}
/* The page title, as opposed to a section label inside it. Every screen that
   names itself uses this; .row-label with a .label is for the strips within a
   screen, and the duel page using it for both left the page with no top. */
.page-head{ display:flex; align-items:center; gap:var(--sp-3); }
.page-head h1{ margin:0; font-size:var(--fs-2xl); font-weight:800; letter-spacing:-.01em; }
.page-head .count{ margin-left:auto; }

.s-grid .album{ width:100%; }

/* Keep every playable cell at the project's 48px target floor. On a short
   phone the screen scrolls vertically rather than shrinking the tap area. */
.s-grid .grid{ grid-template-rows:22px repeat(10,48px); }

/* The legend swatches came out 4px, because the mockup sized them through a
   rule my markup does not match. They are the key to the whole grid, so they
   get an explicit size and the same treatment the cells have.
   
   position:relative is load-bearing, not tidiness. .m4 carries two absolutely
   positioned pseudo-elements for the foil and the star. Inside a .cell they are
   contained because .cell is positioned; on a bare element they escape to the
   nearest positioned ancestor, which here is the whole screen, and the foil
   layer then covers the page and swallows every tap. That is exactly what
   happened: nothing on the grid was clickable, including the back button. */
.legend .leg i{
  position:relative; overflow:hidden;
  width:20px; height:20px; flex:none; border-radius:5px;
  border:2px solid var(--ink);
}
.legend .leg{ display:flex; align-items:center; gap:6px; }

/* Duel rows. .duel already exists from the mockup's home screen; these are the
   parts the list page adds. */
.s-plain .duel{ display:flex; align-items:center; gap:var(--sp-3); }
.duel-who{ flex:1; min-width:0; display:flex; flex-direction:column; align-items:flex-start; gap:1px; font-size:var(--fs-base); font-weight:700; line-height:1.25; }
.duel-who b{ font-size:var(--fs-lg); }
.duel-state{ font-size:var(--fs-sm); font-weight:700; color:var(--gray-600); }
/* The level belongs to the duel, not to the girl standing above it. As a plain
   second line it read as her rank, which is a different number entirely. */
.duel-lvl{
  margin-top:3px; padding:1px 8px 3px;
  background:var(--gray-100); border:2px solid var(--ink); border-radius:var(--radius-full);
  font-size:var(--fs-xs); font-weight:800; letter-spacing:.04em;
}
.s-plain .duel .avatar{ width:44px; height:44px; }
.s-home .duel{ padding:var(--sp-2) var(--sp-3); }
.s-home .duel .face .ani{ width:34px; height:34px; }
.s-plain .duel .avatar .ani{ width:36px; height:36px; }

/* Challenge tiles. Same .tile as the login screen, one size down, because on
   this page the tile is one of several things rather than the whole screen. */
/* auto-fit, not the login screen's fixed two columns: with one sister left to
   challenge a half-width tile sits off to one side looking like a mistake. */
.s-plain .tiles{ margin:0; grid-template-columns:repeat(auto-fit,minmax(140px,1fr)); }
.s-plain .tile{ min-height:0; padding:var(--sp-3) var(--sp-2); gap:6px; }
.s-plain .tile .avatar{ width:56px; height:56px; }
.s-plain .tile .avatar .ani{ width:46px; height:46px; }
.tile-cta{
  padding:2px 12px 4px;
  background:var(--primary); color:var(--ink);
  border:2.5px solid var(--ink); border-radius:var(--radius-full);
  font-size:var(--fs-sm); font-weight:800; letter-spacing:.04em;
}

/* Settled duels. Her score first and always labelled "ti", so she never has to
   work out which of the two numbers is hers. */
.duel-vs{ flex:1; min-width:0; font-size:var(--fs-base); font-weight:700; color:var(--gray-700); }
.duel-vs b{ font-size:var(--fs-xl); color:var(--ink); }
.s-plain .result-rows .avatar{ width:36px; height:36px; flex:none; }
.s-plain .result-rows .avatar .ani{ width:30px; height:30px; }
.duel-tone{
  flex:none; padding:2px 10px 4px;
  border:2px solid var(--ink); border-radius:var(--radius-full);
  font-size:var(--fs-sm); font-weight:800; letter-spacing:.04em;
}
/* Recent form. Same three colours as the tags in the list below, so the dot
   and the word it stands for are never a translation apart. */
.form-summary{ margin-left:auto; display:flex; flex-direction:column; align-items:flex-end; gap:2px; }
.form-label{ font-size:var(--fs-xs); font-weight:800; color:var(--gray-700); }
.form-dots{ display:flex; gap:5px; align-items:center; }
.form-dots i{ width:15px; height:15px; border-radius:50%; border:2px solid var(--ink); }

.t-win{ background:var(--mastery-3); }
.t-draw{ background:var(--gray-200); }
/* At 15px on kraft paper the tag's grey is indistinguishable from the loss
   dot's white. The pill behind dark text cannot go this dark, so only the dot
   does. */
.form-dots .t-draw{ background:var(--gray-400); }
.t-loss{ background:var(--white); color:var(--gray-700); }

/* ============================================================
   HOME
   The mockup styled .hero, .week, .player, .modes and .to-grid for
   three players. These are the parts that make it work for however
   many there are, plus the pieces the mockup did not have.
   ============================================================ */
.s-home{ gap:var(--sp-2); }
.s-home .hero{ padding:var(--sp-2) var(--sp-3); }
.hero-top{ display:flex; align-items:center; gap:var(--sp-3); }
.hero-top h1{ margin:0; font-size:var(--fs-lg); flex:1; min-width:0;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.help-shortcut{
  min-height:48px; flex:none; display:inline-flex; align-items:center;
  padding:0 var(--sp-3); color:var(--ink); background:var(--white);
  border:2px solid var(--ink); border-radius:var(--radius-full);
  box-shadow:2px 2px 0 rgba(33,27,19,.28);
  font-size:var(--fs-sm); font-weight:800; text-decoration:none;
}
.s-home .hero .avatar{ width:40px; height:40px; }
.s-home .hero .avatar .ani{ width:32px; height:32px; }
.s-home .hero .badge{ margin-top:0; flex:none; }
.s-home .level-track{ margin-top:var(--sp-2); }

/* The weekly row is a score comparison, not a profile. Names and numbers only. */
/* .me is scale(1.04) + rotate + a 4px shadow, which reaches ~7px below its own
   box. The 8px flex gap left 1px, so the card sat on the next section label. */
.s-home .week{ margin-bottom:6px; }
.s-home .player{ padding:5px 4px; }
.s-home .player .face .ani{ width:26px; height:26px; }

/* However many players there are. The account list is edited in a config file
   and has already gone from three to two once, so this cannot assume a count. */
.week{ display:flex; gap:var(--sp-2); }
.player{ flex:1 1 0; min-width:0; }
.player .who{ display:block; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.player .pts{ display:block; font-size:var(--fs-xl); font-weight:800; line-height:1.1; }
.pts-unit{ display:block; font-size:var(--fs-xs); font-weight:700; }

/* The three mode buttons are the reason she opened the app, so they sit low on
   the screen and have to be reachable without scrolling on a 640-tall phone.
   Spare height is spent by the two .gap spacers in the markup rather than one
   margin-top:auto on this box: the auto margin pooled every leftover pixel in
   a single void between the week row and TABLICA, which on a tall phone read
   as the page having failed to load its middle. The spacers split it 1:1.5
   around the week row, and their min-height keeps the seams visible on a short
   phone where flex leaves them nothing to grow with. */
/* The 5px shadow under the last button is layout-invisible and the next
   section's label sits right against it, so it clipped the word PISANO. Same
   trap as the play button on the tier screen. The margin also has to clear it,
   which is why it stays even now that the gap between sections is wider. */
.modes{ display:flex; flex-direction:column; gap:10px; margin-bottom:14px; }
.mode{
  display:flex; align-items:center; gap:var(--sp-3);
  min-height:56px; padding:var(--sp-2) var(--sp-4);
  border:3px solid var(--ink); border-radius:var(--radius-lg);
  box-shadow:0 5px 0 var(--ink); text-decoration:none; color:var(--ink);
  transition:transform var(--transition-fast),box-shadow var(--transition-fast);
}
.mode:active{ transform:translateY(5px); box-shadow:0 0 0 var(--ink); }
.mode b{ font-size:var(--fs-xl); font-weight:800; }
/* The name and the line under it travel together; the size tag is pushed to the
   far edge. There is no bare `.mode span` rule any more, because with the name
   nested the auto margin it carried would have thrown every inner span right. */
.mode-main{ flex:1; min-width:0; display:flex; flex-direction:column; gap:1px; }
.mode-desc{ font-size:var(--fs-sm); font-weight:700; }
.mode-meta{ flex:none; margin-left:auto; font-size:var(--fs-sm); font-weight:700; }
.mode .mode-tag{
  flex:none; align-self:center;
  background:var(--ink); color:var(--white); border-radius:var(--radius-full);
  padding:2px 10px 4px; font-size:var(--fs-sm); font-weight:700; letter-spacing:.03em;
}
.mode-1{ background:var(--forest-light); }
.mode-2{ background:var(--ocean-light); }
.mode-3{ background:var(--primary); }
.mode-4{ background:var(--mastery-3); }

/* A mode she can see and cannot open yet. Flat rather than raised, because the
   shadow is what makes the other three read as pressable, and greyed rather
   than hidden: the six table levels lead here and nowhere else, and she cannot
   work toward something she has never seen. */
.mode.is-locked{
  background:var(--gray-100); color:var(--gray-600);
  border-style:dashed; box-shadow:none;
}

/* Where each ladder stands, sitting directly under the heading it belongs to
   and above the buttons that move it. Tapping one opens that ladder's own
   screen: the album for the tables, the tier list for written. Replaces the
   single .to-grid row, which was the album's doorway back when the album was
   the only thing there was to make progress in. */
.track-card{
  display:flex; flex-direction:column; gap:6px;
  /* The 10px keeps the card off the first mode button; flush, the whole play
     stack read as one solid block. Same beat as the gaps between the buttons. */
  margin-bottom:10px;
  padding:var(--sp-2) var(--sp-3);
  background:var(--card-bg); border:3px dashed var(--ink); border-radius:var(--radius-lg);
  text-decoration:none; color:var(--ink);
}
.track-line{ display:flex; align-items:baseline; justify-content:space-between; gap:var(--sp-3); }
.track-line b{ font-size:var(--fs-base); font-weight:800; }
.track-line span{ font-size:var(--fs-sm); font-weight:700; color:var(--gray-700); }
.next-level{ margin:0; font-size:var(--fs-sm); font-weight:700; color:var(--gray-700); text-align:center; }
.home-out{ display:flex; justify-content:center; }
.link-out{
  min-height:48px; padding:0 var(--sp-4);
  background:none; border:0; color:var(--gray-700);
  font-family:inherit; font-size:var(--fs-sm); font-weight:800;
  letter-spacing:.06em; text-transform:uppercase;
  text-decoration:underline; text-underline-offset:3px;
}

/* ============================================================
   PISANO, the written-multiplication tiers
   Not a grid. Ten thousand problems met once each say nothing
   as a picture; what matters is whether the method still holds,
   which is the last ten attempts and nothing else.
   ============================================================ */
.tier{
  padding:var(--sp-3);
  background:var(--card-bg); border:3px solid var(--ink); border-radius:var(--radius-lg);
  box-shadow:var(--card-shadow-lg);
}
.tier.is-locked{
  background:var(--gray-100); border-style:dashed; box-shadow:none; color:var(--gray-600);
}
/* .btn paints a 5px shadow below its own box, which takes no space in layout
   and lands on whatever comes next. Measured at 390px: the button ends at the
   exact pixel the tier name begins, so the shadow ate the top of the text. The
   row reserves the space itself rather than every element below it having to
   know a button is up there. */
.tier-top{ display:flex; align-items:center; gap:var(--sp-3); margin-bottom:9px; }
.tier-top b{ flex:1; min-width:0; font-size:var(--fs-2xl); font-weight:800; letter-spacing:-.01em; }
.tier-lock{
  flex:none; padding:2px 12px 4px;
  border:2px solid var(--gray-400); border-radius:var(--radius-full);
  font-size:var(--fs-sm); font-weight:800; letter-spacing:.04em;
}
.tier-name{ margin:0 0 var(--sp-2); font-size:var(--fs-sm); font-weight:700; color:var(--gray-700); }
.tier-stat{ margin:6px 0 0; font-size:var(--fs-sm); font-weight:700; color:var(--gray-700); }
/* The tenth dot is drawn whether or not she has reached it, so the window has
   a visible size and 6 of 6 does not look like 6 of 10. */
.t-none{ background:transparent; border-style:dashed; }

/* Said once at the top of a paper round rather than discovered on the first
   question, which is a question spent looking for a pencil. */
.paper-note{
  margin:0; padding:var(--sp-2) var(--sp-3);
  background:var(--white); color:var(--ink);
  border:2.5px solid var(--ink); border-radius:var(--radius-md);
  font-size:var(--fs-sm); font-weight:700;
}

/* ============================================================
   PARENT VIEW
   One child at a time. Three 10x10 grids will not fit on a phone,
   and the parent is looking at this on the same phone as everyone.
   ============================================================ */
.kids{ display:flex; gap:var(--sp-2); }
.kid{
  flex:1 1 0; min-width:0; display:flex; flex-direction:column; align-items:center; gap:2px;
  padding:var(--sp-2) 4px; text-decoration:none; color:var(--ink);
  background:var(--white); border:2px solid var(--ink); border-radius:var(--radius-md);
  font-size:var(--fs-sm); font-weight:700;
  box-shadow:2px 2px 0 rgba(33,27,19,.2);
}
.kid.is-on{ background:var(--forest-green); box-shadow:2px 3px 0 var(--ink); }
.kid .ani{ width:30px; height:30px; }
.kid span:last-child{ max-width:100%; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }

/* Seven days, every one of them, so a gap reads as a gap. */
.activity{ list-style:none; margin:0; padding:0; display:flex; gap:var(--sp-2); align-items:flex-end; }
.activity li{ flex:1; display:flex; flex-direction:column; align-items:center; gap:3px; }
.activity .bar{
  width:100%; height:56px; display:flex; align-items:flex-end;
  background:var(--gray-100); border:2px solid var(--ink); border-radius:var(--radius-sm);
}
.activity .bar i{ width:100%; background:var(--ocean-blue); border-radius:2px; }
.activity .day{ font-size:var(--fs-xs); font-weight:700; }
.activity .n{ font-size:var(--fs-xs); font-weight:800; color:var(--gray-700); }
/* The home's grouped variant: every player in the same day box, and the pink
   bar is you, same pink as your card above the chart. */
.activity-grouped .bar i{ flex:1 1 0; width:auto; }
.activity .bar i.me{ background:var(--primary); }

/* Same trap as the legend swatches: these carry the m0 to m4 classes, and m4's
   absolutely positioned foil would escape and cover the page. */
/* Her weakest ten, and the only place in the app where one mastery colour
   repeats ten times down a column. As filled m1 pills reading "Pogrešno: 3"
   this section read as a list of failures, under a heading that says ZA
   VJEŽBU. Same swatch and same word as the legend above it, so the colour
   still carries the state and nothing shouts. */
.result-rows b.weak-pill{
  display:flex; align-items:center; gap:6px;
  font-size:var(--fs-sm); font-weight:800; color:var(--gray-700);
}
.weak-pill i{
  position:relative; overflow:hidden;
  width:18px; height:18px; flex:none; border-radius:5px;
  border:2px solid var(--ink);
}
.weak-pill .m4::before{ font-size:.6em; }

/* No .s-parent grid overrides on purpose. The parent view renders the same
   album at the same size, inside the same .grid-scroll, so there is one grid
   design in this codebase rather than two that drift apart. The shrunk copy
   that used to live here is what got clipped when the child's grid went to
   fixed 48px columns, because an override of the rows alone still inherited
   the new columns. */

/* One child at a glance, tappable through to her detail. */
.kid-card{
  display:flex; align-items:center; gap:var(--sp-3);
  padding:var(--sp-3); text-decoration:none; color:var(--ink);
  background:var(--card-bg); border:3px solid var(--ink); border-radius:var(--radius-lg);
  box-shadow:var(--card-shadow-lg);
  transition:transform var(--transition-fast),box-shadow var(--transition-fast);
}
.kid-card{ flex-direction:column; align-items:stretch; gap:var(--sp-2); }
.kid-card:active{ transform:translate(3px,4px); box-shadow:0 0 0 var(--ink); }
.kid-row{ display:flex; align-items:center; gap:var(--sp-3); }
/* Two to one, so that six table stages and three written ones come out near
   enough the same width per segment. Equal halves made a table segment half the
   size of a written one, which reads as the tables mattering less.
   
   Wrapping, because a child with written work open has three ladders and three
   do not fit across a phone. Unwrapped, the labels are flex:none so the bars
   collapsed to nothing instead and the row read as "Tablica
   dijeljenja▮▯▯Pisano množenje" with the last label off the edge.
   
   The 2:1 ratio only buys per-segment parity while the tracks share a row, and
   a wrapped track sits at half a row on its own, so its segments come out wider
   than the tables above it. Approximate rather than exact, and the cost of the
   row fitting at all. Exact parity would need the stage count to reach the CSS
   from track.php as a custom property, which is more machinery than three
   ladders on one card is worth. */
.kid-tracks{ display:flex; flex-wrap:wrap; align-items:center; gap:var(--sp-2) var(--sp-3); }
.kid-track{ flex:0 1 calc(50% - var(--sp-3)); display:flex; align-items:center; gap:6px; min-width:0; }
.kid-track-wide{ flex:2 1 calc(50% - var(--sp-3)); }
.kid-track-label{ flex:none; font-size:var(--fs-xs); font-weight:800; color:var(--gray-700); }
.kid-track .level-track{ flex:1; min-width:0; }
.kid-card .avatar{ width:52px; height:52px; flex:none; }
.kid-card .avatar .ani{ width:42px; height:42px; }
/* A real basis, not flex:1 (basis 0). .kid-stats sits at its content width, so
   a name block starting from zero lost every pixel to it: measured on the live
   page, .kid-main came out 0px at 320 and 12.5px at 360, and since .kid-sub is
   now nowrap + overflow:hidden that turned an ugly three-line wrap into an
   invisible one. 40% competes, and .kid-stats being shrinkable is what lets it
   give the room back. */
.kid-main{ flex:1 1 40%; min-width:0; display:flex; flex-direction:column; gap:1px; }
.kid-main b{ font-size:var(--fs-xl); font-weight:800; }
/* nowrap and shrinkable, the pair. .kid-stats was flex:none, so its two labels
   held their full width and squeezed the name block until "zadnji put danas"
   came out on three lines. */
.kid-sub{ font-size:var(--fs-sm); font-weight:700; color:var(--gray-700);
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.kid-stats{ display:flex; gap:var(--sp-3); flex:0 1 auto; min-width:0; text-align:right; }
.kid-stat{ display:flex; flex-direction:column; }
.kid-stat b{ font-size:var(--fs-xl); font-weight:800; line-height:1.05; }
.kid-stat b span{ font-size:.55em; font-weight:700; color:var(--gray-600); }
.kid-stat > span{ font-size:var(--fs-xs); font-weight:700; color:var(--gray-700); }
.parent-hint{ margin:0; font-size:var(--fs-sm); font-weight:700; color:var(--gray-600); text-align:center; }
.parent-tools{ margin-top:auto; padding-top:var(--sp-4); display:flex; gap:var(--sp-2); }
.parent-tools > *{ flex:1; }
.parent-tools .btn{ width:100%; min-height:48px; font-size:var(--fs-base); }

/* ============================================================
   HELP
   One route, with the part each role needs. The main path answers immediate
   questions; exact rules stay in disclosure controls with the app's 48px floor.
   ============================================================ */
.s-help{ gap:var(--sp-4); }
.help-lead{
  padding:var(--sp-3); background:var(--forest-light);
  border:3px solid var(--ink); border-radius:var(--radius-lg);
  box-shadow:3px 3px 0 rgba(33,27,19,.22);
  font-size:var(--fs-lg) !important; font-weight:800;
}
.help-section{ display:flex; flex-direction:column; gap:var(--sp-2); }
.help-section .row-label{ margin:0; }
.help-section .label{ font-size:var(--fs-sm); }
.help-section p{ line-height:1.45; }
.help-list{ margin:0; display:flex; flex-direction:column; gap:var(--sp-2); }
.help-item{
  padding:var(--sp-3); background:var(--card-bg);
  border:2px solid var(--ink); border-radius:var(--radius-md);
  box-shadow:2px 2px 0 rgba(33,27,19,.18);
}
.help-item dt{ margin:0 0 3px; font-size:var(--fs-lg); font-weight:800; }
.help-item dd{ margin:0; color:var(--gray-700); font-size:var(--fs-base); font-weight:700; line-height:1.4; }
.help-training{ background:var(--forest-light); }
.help-sprint{ background:var(--ocean-light); }
.help-duel{ background:var(--primary-light); }
.help-written{ background:var(--mastery-3); }
.help-definitions .help-item{ background:var(--white); }
.help-steps{ list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:var(--sp-2); }
.help-step{
  display:grid; grid-template-columns:36px minmax(0,1fr); align-items:center; gap:var(--sp-3);
  padding:var(--sp-3); background:var(--white);
  border:2px solid var(--ink); border-radius:var(--radius-md);
  box-shadow:2px 2px 0 rgba(33,27,19,.18);
  font-size:var(--fs-base); font-weight:700; line-height:1.4;
}
.help-step-number{
  width:36px; height:36px; display:grid; place-items:center;
  background:var(--primary); border:2px solid var(--ink); border-radius:50%;
  font-size:var(--fs-lg); font-weight:800;
}
.help-mastery{ display:grid; grid-template-columns:repeat(2,1fr); gap:var(--sp-2); }
.help-mastery span{ display:flex; align-items:center; gap:var(--sp-2); font-size:var(--fs-base); font-weight:800; }
.help-mastery i{
  position:relative; overflow:hidden; width:28px; height:28px; flex:none;
  border-radius:6px; border:2px solid var(--ink);
}
.help-mastery i.locked{ background:var(--gray-100); border-color:var(--gray-200); }
.help-mastery .m4::before{ font-size:.65em; }
.help-details{
  overflow:hidden; background:var(--white);
  border:2px solid var(--ink); border-radius:var(--radius-md);
  box-shadow:2px 2px 0 rgba(33,27,19,.18);
}
.help-details summary{
  min-height:48px; display:flex; align-items:center; gap:var(--sp-2);
  padding:var(--sp-2) var(--sp-3); cursor:pointer; list-style:none;
  font-size:var(--fs-base); font-weight:800; line-height:1.3;
}
.help-details summary::-webkit-details-marker{ display:none; }
.help-details summary::after{ content:'+'; margin-left:auto; font-size:var(--fs-xl); line-height:1; }
.help-details[open] summary::after{ content:'−'; }
.help-details summary:focus-visible{ outline:4px solid var(--ocean-blue); outline-offset:-4px; }
.help-details-body{ padding:var(--sp-3); border-top:2px dashed var(--gray-300); }
.help-details-body > :first-child{ margin-top:0; }
.help-details-body > :last-child{ margin-bottom:0; }
.help-details-body .help-list{ margin:0; }
.help-note{
  margin:0; padding:var(--sp-3); background:var(--gray-100);
  border-left:4px solid var(--gray-500); border-radius:var(--radius-sm);
  color:var(--gray-700); font-size:var(--fs-base); font-weight:700;
}
.help-back{ margin-top:var(--sp-2); }

@media (max-width:370px){
  .help-shortcut{ padding-inline:var(--sp-2); font-size:var(--fs-xs); }
  .parent-tools{ flex-wrap:wrap; }
  .parent-tools > *{ flex:1 1 45%; }
  .s-help{ padding-inline:var(--sp-3); }
  .help-mastery{ grid-template-columns:1fr; }

  /* One ladder per row on the summary card. Two to a row, a track on a 320px
     screen gets 30px of bar, and six segments cannot go under 39: each is 4px
     of border under border-box with content at zero, plus five 3px gaps. So it
     overflowed the card rather than shrinking, and no min-width can help
     because the floor is the borders. Shrinking the gap instead would make it
     fit as a 29px smear that says nothing. Full width gives each ladder the
     whole card and the segments become legible again. */
  .kid-track, .kid-track-wide{ flex-basis:100%; }
}
