/* Doorway Number Table — activity styles.
   1em = 1/50th of activity width (set by doorway.js onResize).
   All sizing in em; no px except borders.

   Flash stage: 800 × 600 px, background #ffffff (white).
   Coordinate conversion: 1em = 16px (800/50); y positions scaled ×0.75
   before converting (Flash y → CSS top: flash_y × 0.75 / 16).

   Key Flash measurements (range 10, the default view):
     Grid row: y=140px → 6.56em from top; button size 66px → 4.125em;
               column spacing 72px → 4.5em; 10 buttons per row (all ranges).
     Chip holding area: y≈301px → 14.11em from top; chip size same as slot.
     Action buttons: y≈404px → 18.94em from top.
     Stage height: 600px → 28.12em.

   The HTML5 layout mirrors Flash by using CSS grid with 10 fixed columns
   for the number grid — all four ranges (10/20/50/100) place exactly 10
   numbers per row in Flash, so repeat(10, 1fr) reproduces this faithfully.
   Slots are square (aspect-ratio: 1) to match the Flash button shape.
   Chip size tracks slot size via the same 1fr column unit.
*/

/* --- Options form ---
   Scan-panel layout comes from shared .dw-scan-onoff / .dw-scan-switches-cell /
   .dw-scan-delay-cell / .dw-scan-back-group in doorway.css — no per-activity
   overrides needed here. */

/* Welcome illustration sizing for Number Table.
   Flash: the illustration spans ~75% of the stage width. In flow mode the
   parent is no longer letterboxed, so we cap with max-width/max-height to
   prevent the SVG growing unboundedly on tall viewports. */
body.dw-screen-welcome .welcome_image {
    width: 80%;
    max-width: 32em;
    max-height: 18em;
    margin: var(--dw-space-loose) auto;
}

body.dw-screen-welcome .welcome_image img {
    width: 100%;
    height: auto;
    max-height: 18em;
    object-fit: contain;
}

/* --- Screen layout --- */

/* Flex-column skeleton comes from .dw-activity-screen in shared/doorway.css. */
.nt-screen {
    /* `overflow: visible` (not auto) prevents a scrollbar from appearing when
       the absolute-positioned hand pointer animates near or just beyond the
       layout's bottom edge. The activity stage is fixed-size (800×450) and
       the hand is anchored from a transform-offset grasp point that can
       briefly hang below the layout box — without `visible`, that triggered
       a vertical scrollbar which stole horizontal space and resized the
       number grid mid-animation. Letting the hand overflow the activity
       layout is fine because the parent aspect-div clips at the stage
       boundary. */
    overflow: visible;
    padding: 1.5em var(--dw-space-loose) var(--dw-space-loose);
    gap: var(--dw-space-default);
    background: #ffffff;
}

.nt-screen-title {
    font-size: 2em;
    margin: 0 0 var(--dw-space-default) 0;
    text-align: center;
}

/* --- Activity screen layout --- */

/* "Complete the table" heading — Flash shows this as static text at the top of
   the activity screen (y≈15px → 0.7em). Styled to match Flash proportions. */
.nt-activity-title {
    font-size: var(--dw-font-size-heading);
    margin: 0;
    text-align: center;
    color: var(--dw-color-text-strong);
}

.nt-activity-layout {
    display: flex;
    flex-direction: column;
    height: 100%;
    gap: var(--dw-space-default);
    position: relative;
    background: #ffffff;
}

/* --- Table counter label ---
   Flash: "Completing table X of Y" text at top-right (x≈193, y≈15).
   Aligned right to echo Flash position. */

.nt-table-counter {
    font-size: var(--dw-font-size-subheading);
    text-align: right;
    margin: 0;
    color: var(--dw-color-text-muted);
    min-height: 1.5em;
}

/* --- Number grid ---
   Flash: 10 buttons per row for all four number ranges (10/20/50/100).
   CSS grid with 10 equal columns reproduces this directly.
   Slot width = (available width − 9×gap) / 10 ≈ 4.5em at 800px stage,
   matching Flash's 66px button with 72px column pitch. */

.nt-grid {
    display: grid;
    grid-template-columns: repeat(10, var(--nt-slot-size, 4.5em));
    /* JS sets --nt-grid-rows on the grid element in renderGrid, matching the
       row count (1 for range 10, 2 for range 20, 5 for range 50, 10 for
       range 100). Slot size is derived from the row count via min(): each
       slot stays square (columns and rows both use --nt-slot-size), and the
       row-count budget (13em / rows) clamps the slot size so a 10-row grid
       fits the ~13em vertical budget between the header and the holding
       area. Sparse layouts (range 10, 1 row) stay at the Flash-faithful
       4.5em square. */
    --nt-slot-size: min(4.5em, calc((13em - 0.1em * (var(--nt-grid-rows, 1) - 1)) / var(--nt-grid-rows, 1)));
    grid-template-rows: repeat(var(--nt-grid-rows, 1), var(--nt-slot-size));
    gap: 0.1em;
    /* min-height:0 lets flex letterbox the grid cleanly when the parent
       is shorter than the max-content grid — the --nt-slot-size clamp
       above sizes the grid to fit, so this is only exercised at very
       constrained viewports. */
    min-height: 0;
    flex: 0 0 auto;
    margin: 0 auto;
    align-self: center;
    justify-content: center;
    /* Per-range grid shift (translateY) and scale live in the .nt-layout-N
       overrides at the bottom of the file. transform-origin pins the
       origin so scaling doesn't drift the grid down. */
    transform-origin: top center;
}

/* Individual grid slots.
   Slot shape is driven by the grid cell (see grid-template-rows above),
   which lets many-row layouts (range 50/100) squash vertically to fit the
   height budget while single-row layouts stay square via the .nt-grid
   max-width/max-height caps. No border-radius — Flash redButton/blueButton
   are sharp rectangles. */
.nt-slot {
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1em;
    font-weight: bold;
    cursor: pointer;
    border: 2px solid transparent;
    box-sizing: border-box;
    transition: box-shadow 0.1s;
    position: relative;
    overflow: hidden;
    min-width: 0;
    min-height: 0;
}

/* Odd slots: pastel pink rectangle (Flash redButton #ffa4a4). */
.nt-slot.nt-odd {
    background: #ffa4a4;
    color: #000;
}

/* Even slots: pastel blue rectangle (Flash blueButton #a4e1ff). */
.nt-slot.nt-even {
    background: #a4e1ff;
    color: #000;
}

/* Empty/gap slot — waiting for a chip.
   Flash: button goes to frame 2 (empty appearance); the numberBox text fades
   to alpha 0 but the coloured tile remains visible with a darker outline so
   the player can see where the gap is. HTML5: keep odd/even fill but hide
   the number and add a thick darker border in the matching hue. */
.nt-slot.nt-empty {
    cursor: default;
}

.nt-slot.nt-empty .nt-slot-number {
    visibility: hidden;
}

/* "Just-emptied" cells (round start): the outline appears immediately while
   the number itself fades out — mirrors Flash's `fadeOut(boxName)` easeInOut
   tween. The forwards fill leaves the number at opacity 0 so any subsequent
   re-render of the same cell behaves as a normal empty cell. */
.nt-slot.nt-empty .nt-slot-number.nt-slot-number-fading {
    visibility: visible;
    animation: nt-slot-fade-out 1.1s ease-in-out forwards;
}

@keyframes nt-slot-fade-out {
    0%   { opacity: 1; }
    100% { opacity: 0; }
}

/* Empty-slot border width scales with slot size so range 50/100 doesn't
   have the border eating most of the cell (0.35em × 2 = 0.7em is fine on a
   4.5em slot but drowns a 1.2em slot). Cap at 0.35em for Flash-parity on
   sparse layouts and shrink proportionally when the slot is smaller. */
.nt-slot.nt-odd.nt-empty {
    border-style: solid;
    border-color: #c44040;
    border-width: min(0.35em, calc(var(--nt-slot-size, 4.5em) * 0.08));
}

.nt-slot.nt-even.nt-empty {
    border-style: solid;
    border-color: #2f87b8;
    border-width: min(0.35em, calc(var(--nt-slot-size, 4.5em) * 0.08));
}

/* Selected-state glossy radial highlight — shown on empty slots that are valid
   drop targets while the user is holding a chip (drag-target indicator).
   Mirrors the yellow→transparent radial glow on the Flash redButton/blueButton
   sprites (#ffff00 35% → #ffffff 0% from a central radius). */
.nt-slot.nt-empty.nt-drop-target::before {
    content: "";
    position: absolute;
    inset: 0;
    background: radial-gradient(circle at 50% 50%,
        rgba(255, 255, 0, 0.85) 0%,
        rgba(255, 255, 0, 0.5) 35%,
        rgba(255, 255, 200, 0.0) 75%);
    pointer-events: none;
}

/* Highlighted slot (valid drop target for held chip).
   Flash signals this via the radial yellow glow on the button sprite (handled
   by nt-drop-target above) — no extra outline is added. We keep the class so
   the slot remains clickable. */
.nt-slot.nt-highlight {
    z-index: 1;
    cursor: pointer;
}

/* Scan highlight — tighten the shared ring to 0.4em for the grid cells and
   lift the scanned slot above its neighbours so the ring reads fully. */
.nt-slot.scan_highlight {
    box-shadow: 0 0 0 0.4em gold;
    z-index: 1;
}

/* In scan mode the .scan_highlight gold ring is the sole "here is the current
   target" cue: the mouse-mode radial yellow glow on every valid drop target
   competes with the ring and turns the whole grid into a broad "all valid"
   halo. Suppress the grid drop-target glow and the holding-area ghost
   drop-target glow so only the scanned slot lights up. Ghosts are also inert
   in scan mode (no click handler; not added to the scan tree), so they should
   never render as glowing targets there. */
.nt-activity-layout.nt-scan-mode .nt-slot.nt-empty.nt-drop-target::before,
.nt-activity-layout.nt-scan-mode .nt-chip.nt-ghost.nt-drop-target::before {
    display: none;
}

/* Tick animation on correct placement.
   The tick covers the green chip overlay so the player gets a clear
   "well done!" mark. Rendered via ::after centred on the slot. The
   21-frame Flash reveal (`tickDelayed`/`tick` movie clips) is mirrored
   by the `nt-mark-pop` scale-in keyframe. */
.nt-slot.nt-tick::after {
    content: "";
    display: block;
    position: absolute;
    inset: -4%;
    background: url('pictures/tick.svg') no-repeat center/contain;
    transform-origin: center;
    animation: nt-mark-pop 0.4s ease-out;
    z-index: 3;
    pointer-events: none;
}

/* Re-rendered tick after the first reveal — skip the pop keyframe so
   ticking elsewhere (e.g. picking a chip up and putting it back) doesn't
   re-trigger the scale-in animation across every existing tick. */
.nt-slot.nt-tick.nt-tick-no-anim::after {
    animation: none;
}

/* Cross overlay on an incorrectly-placed chip after Check is pressed.
   Same big centred treatment as the tick so the feedback reads at a
   glance. */
.nt-slot.nt-cross::after {
    content: "";
    display: block;
    position: absolute;
    inset: 10%;
    background: url('pictures/cross.svg') no-repeat center/contain;
    transform-origin: center;
    animation: nt-mark-pop 0.4s ease-out;
    z-index: 3;
    pointer-events: none;
}

/* Re-rendered cross — skip the pop keyframe so picking up the chip from
   underneath doesn't re-trigger the scale-in on the existing mark. */
.nt-slot.nt-cross.nt-cross-no-anim::after {
    animation: none;
}

/* Cross on a chip in the holding area (Check button incorrect feedback).
   Animated the same way as slot marks for visual consistency. */
.nt-chip.nt-cross::after {
    content: "";
    display: block;
    position: absolute;
    top: -0.2em;
    right: -0.2em;
    width: 1.2em;
    height: 1.2em;
    background: url('pictures/cross.svg') no-repeat center/contain;
    transform-origin: top right;
    animation: nt-mark-pop 0.4s ease-out;
}

/* Shared pop-in keyframe for tick/cross marks.
   A small over-shoot at 60% mimics the Flash 21-frame bounce reveal. */
@keyframes nt-mark-pop {
    0%   { opacity: 0; transform: scale(0.3); }
    60%  { opacity: 1; transform: scale(1.15); }
    100% { opacity: 1; transform: scale(1); }
}

/* Slot number text. Flash renders the slot number in a chunky bold black face
   that fills roughly half the slot. Sized in `em` so it scales with the grid.
   The number is capped by the slot's derived --nt-slot-size (~55% of it) so
   dense grids (range 50/100) still fit the number inside their squashed cell;
   sparse grids (range 10/20) keep the Flash-chunky 1.95em. */
.nt-slot-number {
    font-size: min(1.95em, calc(var(--nt-slot-size, 4.5em) * 0.55));
    font-weight: bold;
    color: #000;
    line-height: 1;
    pointer-events: none;
}

/* Green chip overlay rendered inside a slot once a chip has been placed
   there. Visually identical to a holding-area chip, sized to nestle
   inside the slot. The wrapping .nt-slot still carries the odd/even
   pastel fill, which shows as a thin frame around the chip.
   Width/height clamp: the cap matches the holding-chip visible diameter
   (holding-area chips use `font-size: 1.1em` on a `width: 4em` base, so
   their visible diameter is 4.4em in the activity's base font metric).
   `inset: 0` + `margin: auto` centres the chip in the slot at any size.
   At ranges 10/20 the slot is 4.5em wide, so the slot-size fraction
   caps at 4.4em (holding-chip parity). At ranges 50/100 the slot is
   much smaller, and the slot-size fraction shrinks the chip below the
   cap while leaving a small visible frame. */
.nt-slot-chip {
    position: absolute;
    inset: 0;
    margin: auto;
    /* Cap of 4em (matching the 4em holding-chip diameter) with a 0.92
       multiplier of the slot below that: `.nt-slot` has a 2px transparent
       base border and `overflow: hidden`, so the visible content box is
       ~slot-size - 4px. `slot-size * 0.92` leaves clearance inside that
       content area at every range, avoiding a "squared off" clip on the
       chip's tangents. Range 50 halves this via a per-range override on
       .nt-layout-50 .nt-slot-chip (chip diameter scaled 0.72). */
    width:  min(4em, calc(var(--nt-slot-size, 4.5em) * 0.92));
    height: min(4em, calc(var(--nt-slot-size, 4.5em) * 0.92));
    border-radius: 50%;
    background: #51f769;
    border: 0.18em solid #009900;
    color: #000;
    display: flex;
    align-items: center;
    justify-content: center;
    box-sizing: border-box;
    z-index: 1;
    pointer-events: none;
    transition: transform 0.4s ease-out;
}

/* Mid-flight slot-chip — used when the user picks an already-placed chip
   up and chooses a new destination. The chip slides from its old slot to
   the new one, then the state-change re-render lands it cleanly. The
   parent slot is also raised + un-clipped so the chip can travel beyond
   the slot's clip rectangle (otherwise `.nt-slot { overflow: hidden }`
   chops the chip off as soon as it leaves the source slot). */
/* Fly-anim vars (--nt-fly-x, --nt-fly-y, --nt-fly-scale) are set by JS
   just before .nt-flying is added — see onSlotClick / onOriginClick in
   number-table-render.js. Fallbacks resolve to identity so any stray
   class add without JS setup is inert. */
.nt-slot-chip.nt-flying {
    transform: translate(var(--nt-fly-x, 0), var(--nt-fly-y, 0)) scale(var(--nt-fly-scale, 1));
    z-index: 4;
}

.nt-slot.nt-slot-has-flying-chip {
    overflow: visible;
    z-index: 4;
}

/* Picking-up affordance — incorrectly-placed chips invite a re-click.
   Hovered/keyboard-focused slots highlight the slot frame to signal the
   chip can be moved. */
.nt-slot.nt-pickup-target {
    cursor: pointer;
}

.nt-slot.nt-pickup-target:focus-visible {
    outline: 0.18em solid var(--dw-color-border-muted);
    outline-offset: -0.18em;
}
@media (hover: hover) {
    .nt-slot.nt-pickup-target:hover {
        outline: 0.18em solid var(--dw-color-border-muted);
        outline-offset: -0.18em;
    }
}

/* --- Chips (numbered counters) ---
   Flash: "chip" movie clip — a circular counter with a number text field.
   Size in Flash: 66 × 66 px = 4.125em. Displayed in "lowButton" holding area.
   HTML5: yellow circles in a flex row below the grid.
   Chip diameter matches slot size via the same 1fr column width concept;
   here expressed as a fixed em since chips live outside the grid. */

.nt-holding-area {
    display: flex;
    flex-wrap: wrap;
    /* Baseline 0.6em ≈ 15% of a 4em chip. Range 50 overrides with a
       proportionally smaller gap to match its 0.72 chip scale. */
    gap: 0.6em;
    justify-content: center;
    padding: var(--dw-space-default);
    /* Flash had no holding-area background — chips sit loose on the white
       stage. No bg, no border. */
    background: transparent;
    border: none;
    min-height: 4em;
    position: relative;
    /* Lift above highlighted grid slots (z-index: 1) so a chip flying out
       of a ghost toward the grid paints above the coloured squares. Stays
       below `.nt-slot.nt-slot-has-flying-chip` (z: 4) so the reverse flight
       — a placed chip travelling back to its ghost — still paints above. */
    z-index: 2;
    /* Only the chip children are interactive; the empty padding must not
       swallow clicks aimed at whatever the per-range translates park
       underneath (Go button in range 50 sits inside the holding-area's
       box). Chips re-enable hits via `pointer-events: auto` below. */
    pointer-events: none;
    /* Per-range translate lives in the .nt-layout-N overrides. */
}

/* Two-row brick-pattern layout used when more than 5 counters are in play.
   Top row is offset right by half a chip-width; bottom row starts flush-
   left with a trailing whole-chip gap on the right so the hand pointer
   has room to rest without overlapping a chip. Grid runs on 11 half-chip
   columns (2em each): row 1 spans cols 2-3, 4-5, ..., 10-11 (leading
   col 3 gives the half-chip indent); row 2 spans cols 1-2, 3-4, ...,
   9-10 (cols 10-11 empty on the right for the hand-rest gap). Chip
   position is applied via JS-set classes: `nt-chip-row-2` (any bottom-
   row chip), plus `nt-chip-row-1-start` / `nt-chip-row-2-start` on the
   leading chip of each row. Range 50 overrides half-col + gap width
   proportionally to its 0.72 chip scale. */
.nt-holding-area.nt-holding-area-offset {
    display: grid;
    grid-auto-flow: row;
    grid-template-columns: repeat(11, 2em);
    /* column-gap makes the spanned chip's area (2 half-cols + 1 gap)
       exceed the chip's own 4em width — chip sits centred with the
       surplus becoming visible whitespace between chips. */
    column-gap: 0.6em;
    row-gap:    0.6em;
    justify-content: center;
}

/* Chips span two half-columns so their visible width matches the 4em
   chip diameter (with the between-track gap absorbed as internal
   padding — the chip's own fixed width leaves it centred in the area). */
.nt-holding-area.nt-holding-area-offset .nt-chip {
    grid-column: span 2;
}

.nt-holding-area.nt-holding-area-offset .nt-chip.nt-chip-row-2 {
    grid-row: 2;
}

/* Per-row leading-chip placement. Row 1 leading lives in cols 2-3
   (half-chip indent left); row 2 leading lives in cols 1-2 (flush left,
   whole-chip trail right becomes the hand-rest gap). */
.nt-holding-area.nt-holding-area-offset .nt-chip.nt-chip-row-1-start {
    grid-column: 2 / span 2;
}

.nt-holding-area.nt-holding-area-offset .nt-chip.nt-chip-row-2-start {
    grid-column: 1 / span 2;
}

/* Each chip: circle matching approximate Flash chip size (66px ≈ 4.125em).
   Flash chip.svg: green (#51f769) inner with a darker green (#009900) outline.
   font-size stays at 1em so a chip's `4em` width equals 4 parent-em — the
   `.nt-slot-chip` overlay in the grid can then match this exactly and still
   fit inside a 4.5em slot's content box (~4.25em after the 2px transparent
   base border) without being clipped by `.nt-slot { overflow: hidden }`.
   The number label sets its own font-size below, so the chip's font-size
   doesn't drive label sizing.
   position: relative anchors the ::after cross mark within the chip. */
.nt-chip {
    display: flex;
    align-items: center;
    justify-content: center;
    /* Chip diameter is the layout box (width/height) rather than a
       transform: scale, so the flex container's `gap` contracts with
       the chip if a range's override shrinks it. Range 50 does exactly
       this — see .nt-layout-50 .nt-chip. */
    width:  4em;
    height: 4em;
    border-radius: 50%;
    background: #51f769;
    color: #000;
    font-size: 1em;
    font-weight: bold;
    border: 0.18em solid #009900;
    cursor: pointer;
    transition: box-shadow 0.1s, transform 0.4s ease-out;
    box-sizing: border-box;
    flex-shrink: 0;
    position: relative;
    /* Re-enable pointer hits (parent `.nt-holding-area` sets none). */
    pointer-events: auto;
}

/* Chip number label */
.nt-chip-number {
    font-size: var(--dw-font-size-large);
    font-weight: bold;
    color: #000;
    pointer-events: none;
}

/* When a chip is nested inside a slot (placed chip), the label must fit the
   slot rather than the holding-area chip. Slot-derived --nt-slot-size caps
   the number so range 50/100 doesn't overflow the squashed slot. */
.nt-slot-chip .nt-chip-number {
    font-size: min(var(--dw-font-size-large), calc(var(--nt-slot-size, 4.5em) * 0.5));
}

/* Round-start chip fade-in. Triggered when the holding area transitions from
   empty to populated (ready→playing). The 0.9s delay overlaps the tail of the
   cell-number fade-out, mirroring Flash's tween chain: fadeOut(boxName) →
   onMotionFinished → fadeInChip(chipName). `both` keeps the chip invisible
   during the delay and at full opacity afterwards. */
.nt-chip.nt-just-appeared {
    animation: nt-chip-fade-in 0.55s ease-in 0.9s both;
}

@keyframes nt-chip-fade-in {
    0%   { opacity: 0; transform: translateY(0.35em); }
    100% { opacity: 1; transform: translateY(0); }
}

/* Source ghost — pale-green "home" silhouette left behind when a counter is
   moved into the table. Inert unless a chip is currently held; when held,
   the ghost becomes a drop target and gets the same yellow glow as an empty
   grid slot (matches Flash `lowButton.glow._visible = true` at
   frame_04.as:586). Sized the same as a live chip so the holding row keeps
   stable layout positions across the round. */
.nt-chip.nt-ghost {
    background: rgba(81, 247, 105, 0.22);
    border-color: rgba(0, 153, 0, 0.45);
    color: transparent;
    cursor: default;
    box-shadow: none;
}

.nt-chip.nt-ghost.nt-drop-target {
    cursor: pointer;
}

.nt-chip.nt-ghost.nt-drop-target::before {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: 50%;
    background: radial-gradient(circle at 50% 50%,
        rgba(255, 255, 0, 0.85) 0%,
        rgba(255, 255, 0, 0.5) 35%,
        rgba(255, 255, 200, 0.0) 75%);
    pointer-events: none;
}

.nt-chip.nt-ghost.nt-drop-target:focus-visible {
    background: rgba(81, 247, 105, 0.4);
    outline: 0.18em solid var(--dw-color-border-muted);
    outline-offset: -0.18em;
}
@media (hover: hover) {
    .nt-chip.nt-ghost.nt-drop-target:hover {
        background: rgba(81, 247, 105, 0.4);
        outline: 0.18em solid var(--dw-color-border-muted);
        outline-offset: -0.18em;
    }
}

/* Chip selected / currently held.
   Flash: hand movie clip animates to chip — no extra outline is added to the
   chip itself. The hand sprite alone signals selection. */
.nt-chip.nt-selected {
    cursor: pointer;
}

/* Chip mid-flight to a slot (set by JS just before applying placement).
   Pairs with --nt-fly-x / --nt-fly-y custom properties for the destination. */
.nt-chip.nt-flying {
    transform: translate(var(--nt-fly-x, 0), var(--nt-fly-y, 0)) scale(var(--nt-fly-scale, 1));
    z-index: 4;
    pointer-events: none;
}

/* Chip scan highlight — tighten the shared ring to 0.35em to hug the chip
   border-radius closely. */
.nt-chip.scan_highlight {
    box-shadow: 0 0 0 0.35em gold;
}

/* --- Hand pointer ---
   Flash: a hand movie clip is attached at (handStartX 630, handStartY 440) at
   75% scale. It tweens between the selected chip and the destination slot so
   the user can see what they have selected and where it is going. The hand is
   anchored from the top-right corner of the chip (the thumb wraps around the
   chip), so we offset the SVG accordingly. */
.nt-hand {
    position: absolute;
    width: 5.6em;
    height: auto;
    pointer-events: none;
    /* The raw Flash hand sprite has its grasp point (where the thumb pad
       sits) roughly 28% from the left and 30% from the top of its bounding
       box. Offset the sprite so left/top represent that grasp point — that
       way we can simply position the hand at the chip centre. The trailing
       translateY(4em) drops the whole hand by one chip-height so the thumb
       wraps the top of the counter rather than hovering above it. */
    transform: translate(-28%, -30%) translateY(4em) translate(0.5em, -0.5em);
    opacity: 0;
    z-index: 5;
    left: 0;
    top: 0;
}

/* `.nt-hand-visible` reveals the sprite. The slide transition is enabled
   separately via `.nt-hand-animating`, which JS adds one frame later so
   the very first position-set (when the activity starts) does not slide
   in from the origin. Subsequent moves (chip selection, fly-to-slot) do
   transition. */
.nt-hand.nt-hand-visible {
    opacity: 1;
}

.nt-hand.nt-hand-animating {
    /* Duration and easing match the chip's `transition: transform 0.4s
       ease-out` on both `.nt-chip` and `.nt-slot-chip` so the hand's
       grasp point tracks the chip pixel-for-pixel through the fly-to-
       slot / fly-to-ghost animation. Split durations (0.45s left,
       0.55s top) let the hand and chip drift out of alignment mid-
       flight and made the chip appear to slip out of the hand's grip. */
    transition: left 0.4s ease-out, top 0.4s ease-out;
}

/* Round-start hand fade-in — matches the chip fade-in delay so the hand
   appears in sync with the chips, not before them. Only applied on the
   ready→playing transition (cleared on round end). */
.nt-hand.nt-hand-just-appeared {
    animation: nt-hand-fade-in 0.55s ease-in 0.9s both;
}

@keyframes nt-hand-fade-in {
    0%   { opacity: 0; }
    100% { opacity: 1; }
}

/* --- Overlays ---
   Flash: scanError (~4 s, 105 frames), answerMessage / answerMessageDelayed
   ("Well Done"). Positioned inside .nt-activity-layout via absolute positioning. */

.nt-overlay {
    position: absolute;
    top: 10%;
    left: 10%;
    right: 10%;
    background: rgba(200, 60, 60, 0.92);
    color: #fff;
    border-radius: 0.8em;
    padding: var(--dw-space-loose) 2em;
    text-align: center;
    font-size: 1.6em;
    font-weight: bold;
    z-index: 10;
    pointer-events: none;
}

/* Overlay reveal — fade + slight downward slide.
   Lifecycle split across two keyframes: `nt-overlay-show` plays once on
   reveal; JS listens for animationend and switches to `nt-overlay-hide`
   to fade it out. */
.nt-overlay.nt-overlay-show {
    animation: nt-overlay-show 1.6s ease-out forwards;
}

.nt-overlay.nt-overlay-hide {
    animation: nt-overlay-hide 0.3s ease-in forwards;
}

@keyframes nt-overlay-show {
    0%   { opacity: 0; transform: translateY(-0.4em); }
    18%  { opacity: 1; transform: translateY(0); }
    82%  { opacity: 1; transform: translateY(0); }
    100% { opacity: 1; transform: translateY(0); }
}

@keyframes nt-overlay-hide {
    0%   { opacity: 1; }
    100% { opacity: 0; }
}

/* Well Done overlay (answerMessage / answerMessageDelayed in Flash).
   Flash answerMessage.svg: white bg (rgba 80%), black text, ~6px solid orange
   border (#ff6600). HTML5: same palette, sized to content (not the stage
   width) and centred via left:50% + translateX so the box reads as a
   crisp rectangular badge rather than a wide banner. */
.nt-well-done {
    position: absolute;
    top: 20%;
    left: 50%;
    transform: translateX(-50%);
    width: max-content;
    max-width: 80%;
    background: rgba(255, 255, 255, 0.95);
    color: #000;
    border: 5px solid #ff6600;
    border-radius: 0;
    padding: 0.5em 1.4em;
    text-align: center;
    font-size: 2.3em;
    font-weight: bold;
    z-index: 10;
    pointer-events: none;
}

.nt-well-done.nt-well-done-show {
    animation: nt-well-done-show 0.5s ease-out forwards;
}

/* Keyframes preserve the translateX(-50%) centring so the scale-in
   doesn't teleport the box to the left edge mid-animation. */
@keyframes nt-well-done-show {
    0%   { opacity: 0; transform: translateX(-50%) scale(0.85); }
    100% { opacity: 1; transform: translateX(-50%) scale(1); }
}

/* --- Action buttons ---
   Flash: startButton (~352,404), nextButton (~532,405), checkButton (~655,404),
   back (~7,8) — arranged at bottom of stage and top-left corner.
   HTML5: flex row, centred, at the bottom of .nt-activity-layout. */

.nt-action-buttons {
    display: flex;
    gap: 0.8em;
    justify-content: center;
    flex-wrap: wrap;
    margin-top: var(--dw-space-default);
    position: relative;
}

/* When the Check button is visible (tick-as-you-go OFF mode), it sits at
   the right-hand side under the number line — anchored absolutely so the
   other action buttons (Go / Next) remain centred independently of its
   presence. */
.nt-action-buttons .nt-check-btn:not(.hidden_panel) {
    position: absolute;
    right: 1em;
    top: 0;
}

/* Tick-off layout — Check button lifts up to sit level with the top of
   the counter holding area (right-hand side), extended a little to make
   room for a green tick icon on its left end. `.nt-activity-layout` is
   `position: relative`, so this absolute anchor is scoped to the activity
   stage. `nt-layout-tick-off` is toggled on the layout in render() from
   `options.tick_mode === false`. */
.nt-activity-layout.nt-layout-tick-off .nt-action-buttons .nt-check-btn:not(.hidden_panel) {
    /* Anchor to the top-right corner of the holding area. From the bottom
       of `.nt-activity-layout`, budget for the action-bar row (~2.6em) +
       its margin-top gap (~0.75em) + the holding-area height (min 4em
       + padding ~1.5em) + a small nudge places the button top level with
       the top edge of the chip row. */
    top: auto;
    right: 1em;
    bottom: 3.2em;
    /* Chrome (padding, tick badge geometry, colours) is identical across
       every range. Per-range shifts of the whole button live in the
       .nt-layout-N .nt-check-btn overrides at the bottom of the file. */
    padding-left:  1.25em;
    padding-right: 0.35em;
    background: #e0ecfe;
    border: 0.18em solid #509efe;
    text-transform: lowercase;
}

.nt-activity-layout.nt-layout-tick-off .nt-action-buttons .nt-check-btn:not(.hidden_panel)::before {
    content: "";
    position: absolute;
    width: 2.6em;
    height: 2.6em;
    left: -0.5em;
    top:  -0.95em;
    background: url('pictures/tick.svg') no-repeat center/contain;
    pointer-events: none;
}

/* Interactive feedback for the tick-off check button. The tick-off rule
   above sets an explicit background/border that outranks the generic
   `button:hover` cascade, so hover and active states have to be spelled
   out here. Palette matches `.nt-go-btn` for consistency across the
   action-buttons row. */
@media (hover: hover) {
    .nt-activity-layout.nt-layout-tick-off .nt-action-buttons .nt-check-btn:not(.hidden_panel):hover {
        background: #d0e2fe;
    }
}
.nt-activity-layout.nt-layout-tick-off .nt-action-buttons .nt-check-btn:not(.hidden_panel):active {
    background: #c0d6fe;
}
.nt-activity-layout.nt-layout-tick-off .nt-action-buttons .nt-check-btn:not(.hidden_panel):focus-visible {
    outline: 0.18em solid #1a5fde;
    outline-offset: 0.12em;
}

/* --- Go button ---
   Flash startButton (sprite_304): a 104×49 pill with a pale-blue fill, medium
   blue 3px outline, and a green `mcGoFlash` morph circle sitting inside on
   the left of the word "go". */
.nt-go-btn {
    background: #e0ecfe;
    border: 0.18em solid #509efe;
    border-radius: 1.5em;
    color: #000;
    font-family: 'Jotter Primary', sans-serif;
    font-size: var(--dw-font-size-heading);
    line-height: 1;
    height: 2.2em;
    min-width: 4.6em;
    padding: 0 0.95em 0 2.35em;
    cursor: pointer;
    position: relative;
    box-sizing: border-box;
    text-align: center;
    align-self: center;
    flex-shrink: 0;
    /* Per-range translate lives in the .nt-layout-N .nt-go-btn overrides. */
}

.nt-go-btn::before {
    content: "";
    position: absolute;
    left: 0.45em;
    top: 50%;
    width: 1.35em;
    height: 1.35em;
    transform: translateY(-50%);
    border-radius: 50%;
    background: #82ff82;
    border: 0.13em solid var(--dw-color-text-strong);
    box-sizing: border-box;
}

@media (hover: hover) {
    .nt-go-btn:hover {
        background: #d0e2fe;
    }
}

.nt-go-btn:active {
    background: #c0d6fe;
}

.nt-go-btn:focus-visible {
    outline: 0.18em solid #1a5fde;
    outline-offset: 0.12em;
}

/* --- Range-100 layout override ---
   At range 100 the grid is 10x10 — twice as tall as any other range. The
   default flex-column layout (grid on top, chips row below, action-buttons
   row at the bottom) overflows the 450px stage. Range-100 re-slots the
   same DOM children of .nt-activity-layout into a 2-column CSS grid:
   the number grid fills the left column top-to-bottom, and the Go/Next
   action buttons + chip counters stack in the right column.
   The HTML tree is unchanged; only positional CSS differs from the
   default layout, so scan order, hand-pointer maths, and fly-to-slot
   maths (which use getBoundingClientRect on absolutely-positioned
   parents) all continue to work.
*/
.nt-activity-layout.nt-layout-100 {
    display: grid;
    grid-template-columns: 1fr 7em;
    grid-template-rows: auto auto 1fr;
    grid-template-areas:
        "counter counter"
        "grid    actions"
        "grid    chips";
    column-gap: var(--dw-space-default);
    row-gap: var(--dw-space-tight);
}

.nt-activity-layout.nt-layout-100 .nt-table-counter {
    grid-area: counter;
}

.nt-activity-layout.nt-layout-100 .nt-grid {
    grid-area: grid;
    align-self: center;
    justify-self: center;
    /* Fixed 2.2em slot at range 100 sizes the 10x10 grid to fill the
       stage height (10 * 2.2em + 9 * 0.1em ≈ 23em) with the counter
       row and inter-row gaps within the ~28em budget. Overrides the
       shared .nt-grid --nt-slot-size clamp so range 100 doesn't fall
       under the 13em/rows squash the shared rule applies to sparser
       ranges. */
    --nt-slot-size: 2.2em;
}

.nt-activity-layout.nt-layout-100 .nt-action-buttons {
    grid-area: actions;
    margin-top: 0;
    justify-content: center;
    align-self: center;
}

/* Tick-on check button at layout-100 flows in-place inside the
   `actions` grid area (which owns the right column). Scoped away from
   .nt-layout-tick-off so that variant keeps the shared floating-badge
   anchor (bottom: 3.2em; right: 1em) at max-content width across
   every range. */
.nt-activity-layout.nt-layout-100:not(.nt-layout-tick-off) .nt-action-buttons .nt-check-btn:not(.hidden_panel) {
    position: static;
    right: auto;
    top: auto;
}

/* Right-column holding area: 2-column grid of chips (up to 5 rows × 2 =
   10 chips, matching the max Number Missing option). No brick-pattern
   offset — layout-100 is a strict rectangular grid. */
.nt-activity-layout.nt-layout-100 .nt-holding-area {
    grid-area: chips;
    align-self: start;
    justify-self: center;
    display: grid;
    grid-template-columns: repeat(2, auto);
    column-gap: 0.6em;
    row-gap:    0.6em;
    padding: 0;
    flex-wrap: initial;
    min-height: 0;
}

/* renderHoldingArea() skips the .nt-holding-area-offset class + the
   nt-chip-row-* markers when options.number_range === 100, so no
   layout-100 chip carries the brick-pattern classes. */

/* Chip diameter shrunk so up to 5 rows of 2 chips fit alongside the
   10-row grid. */
.nt-activity-layout.nt-layout-100 .nt-chip {
    width:  2.2em;
    height: 2.2em;
    font-size: 1em;
    border-width: 0.12em;
}

.nt-activity-layout.nt-layout-100 .nt-chip .nt-chip-number {
    font-size: 1.1em;
}

/* -----------------------------------------------------------------
   Per-range activity-screen overrides.
   Ranges 10 and 20 share a compact holding-area offset + check button
   anchor over the counters row. Range 50 also scales the grid up and
   the chips down. Range 100 shifts the counter column and buttons
   into the 2-col layout (see .nt-layout-100 rules above for the
   structural grid) and lifts the tick-off check button as an overlay.
   ----------------------------------------------------------------- */

/* Ranges 10 and 20 */
.nt-activity-layout.nt-layout-10 .nt-holding-area,
.nt-activity-layout.nt-layout-20 .nt-holding-area {
    transform: translateY(0.7em);
}
.nt-activity-layout.nt-layout-10.nt-layout-tick-off .nt-check-btn:not(.hidden_panel),
.nt-activity-layout.nt-layout-20.nt-layout-tick-off .nt-check-btn:not(.hidden_panel) {
    transform: translate(-1.8em, 0em);
}

/* Range 50 — chip diameter, borders, holding-area gap, and offset-grid
   half-col widths all scale with --nt-r50-scale so a single edit tunes
   the whole family without drift. Chosen to keep the chip / grid-width
   ratio at range 50 close to the range-10 baseline (see the
   calculation in the earlier commit landing this scale). */
.nt-activity-layout.nt-layout-50 {
    --nt-r50-scale: 0.72;
}
.nt-activity-layout.nt-layout-50 .nt-grid {
    transform: translateY(-2.2em) scale(1.24);
}
.nt-activity-layout.nt-layout-50 .nt-chip {
    width:  calc(4em    * var(--nt-r50-scale));
    height: calc(4em    * var(--nt-r50-scale));
    border: calc(0.18em * var(--nt-r50-scale)) solid #009900;
}
.nt-activity-layout.nt-layout-50 .nt-slot-chip {
    /* Width/height inherited from the shared .nt-slot-chip rule:
       min(4em, --nt-slot-size * 0.92). Range 50 slot-size ≈ 2.52em, so
       the placed chip sizes itself to ~2.32em to fit the slot's
       content box. Multiplying by --nt-r50-scale double-shrinks it
       (the slot itself is already smaller at this range) and squeezes
       the number label — only the border is scaled to match the
       holding-chip border. */
    border: calc(0.18em * var(--nt-r50-scale)) solid #009900;
}
.nt-activity-layout.nt-layout-50 .nt-holding-area {
    transform: translateY(0.7em);
    gap: calc(0.6em * var(--nt-r50-scale));
}
.nt-activity-layout.nt-layout-50 .nt-holding-area.nt-holding-area-offset {
    grid-template-columns: repeat(11, calc(2em * var(--nt-r50-scale)));
    column-gap: calc(0.6em * var(--nt-r50-scale));
    row-gap:    calc(0.6em * var(--nt-r50-scale));
}
.nt-activity-layout.nt-layout-50 .nt-go-btn {
    transform: translateY(-2em);
}
.nt-activity-layout.nt-layout-50 .nt-next-btn {
    transform: translate(14em, -2.7em);
}
.nt-activity-layout.nt-layout-50.nt-layout-tick-off .nt-check-btn:not(.hidden_panel) {
    transform: translate(-5.3em, 1.7em);
}

/* Range 100 (structural 2-col grid lives in the .nt-layout-100 rules
   above; these are the per-range shifts of individual elements). */
.nt-activity-layout.nt-layout-100 .nt-grid {
    transform: translateY(-1.8em);
}
.nt-activity-layout.nt-layout-100 .nt-holding-area {
    transform: translate(-7.1em, 3.7em);
}
.nt-activity-layout.nt-layout-100 .nt-go-btn {
    transform: translate(-3.6em, 4.6em);
}
.nt-activity-layout.nt-layout-100 .nt-next-btn {
    transform: translate(-4.6em, 0.1em);
}
.nt-activity-layout.nt-layout-100.nt-layout-tick-off .nt-check-btn:not(.hidden_panel) {
    transform: translate(-5.4em, 5.6em);
}

/* --- Results screen --- */

/* Title bar: heading centred, Print button pinned to the top-right corner
   to match Flash sprite_358 (see 04_results_flash.png). The heading is
   centred in the full width by positioning the button absolutely so the
   heading's own text-align: center still lands on the stage midline. */
.nt-results-title-bar {
    position: relative;
    display: block;
    margin: 0 0 var(--dw-space-snug) 0;
}

.nt-results-title-bar .nt-screen-title {
    margin: 0;
}

.nt-print-btn {
    position: absolute;
    top: 0;
    right: 0;
    margin: 0;
}

/* Two-column block under the heading: existing Range/Removed summary on the
   left, printable Name/Date fields on the right (Flash sprite_358 layout —
   see screenshots/number-table/04_results_flash.png). */
.nt-results-topblock {
    display: flex;
    flex-direction: row;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--dw-space-loose);
    margin-bottom: var(--dw-space-snug);
}

.nt-results-summary {
    text-align: left;
    flex: 1 1 auto;
    margin: 0;
}

.nt-results-meta {
    font-size: 1.1em;
    margin: 0.15em 0;
    color: var(--dw-color-text-secondary);
}

.nt-results-fields {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: var(--dw-space-snug) var(--dw-space-default);
    align-items: center;
    flex: 0 0 auto;
    min-width: 14em;
}

.nt-field-label {
    font-size: 1em;
    white-space: nowrap;
}

.nt-text-input {
    border: 1px solid var(--dw-color-border-neutral);
    border-radius: 0.2em;
    padding: 0.15em var(--dw-space-snug);
    font-size: 1em;
    width: 12em;
    box-sizing: border-box;
}

.nt-date-input {
    width: 8em;
}

.nt-results-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 1em;
}

.nt-results-table th,
.nt-results-table td {
    border: 1px solid var(--dw-color-border-light);
    padding: 0.15em 0.8em;
    text-align: center;
    line-height: 1.3;
}

.nt-results-table th {
    background: #e0e8f0;
    font-weight: bold;
}

.nt-results-table tr:nth-child(even) {
    background: #f5f5f5;
}

.nt-score-asterisk {
    margin-left: 0.1em;
}

/* Footnote explaining the Score* column (Flash sprite_358 verbatim). */
.nt-results-footnote {
    font-size: 0.8em;
    color: var(--dw-color-text-secondary);
    margin: var(--dw-space-snug) 0 0 0;
    text-align: left;
    line-height: 1.25;
}

.nt-print-hint {
    font-size: 0.7em;
    color: var(--dw-color-text-muted);
    text-align: center;
    margin: var(--dw-space-tight) 0 0 0;
}

/* --- Print stylesheet — show only the scoreboard --- */
@media print {
    @page { margin: 1.5cm; }

    body { background: #fff; }

    #dwNavigationHeader,
    .dw-screen-header,
    .dw-app-footer,
    #dwWelcomeScreen,
    #dwUserOptions,
    #dwActivityScreen,
    .nt-action-buttons,
    .nt-print-hint {
        display: none !important;
    }

    .doorway_application,
    .doorway_fullscreen_div,
    .doorway_aspect_outer_div,
    .doorway_aspect_div,
    main {
        position: static !important;
        width: auto !important;
        height: auto !important;
        max-width: none !important;
        max-height: none !important;
        overflow: visible !important;
        transform: none !important;
        padding: 0 !important;
        margin: 0 !important;
    }

    #dwResultsScreen.nt-screen {
        position: static !important;
        display: block !important;
        width: 100% !important;
        height: auto !important;
        padding: 0 !important;
        overflow: visible !important;
        font-size: 12pt;
    }

    .nt-results-table {
        font-size: 1em;
    }
    .nt-results-table th { background: transparent; }
    .nt-results-table tr:nth-child(even) { background: transparent; }
}

/* --- Reduced motion --- */

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