/* Shared design tokens + base resets. Extracted from
   core/_design_tokens.html; that template now emits the <link> tags.
   Colours, type, focus styles and motion behaviour for every page. */
/* Shared design tokens + base resets - included inside <head> on every page so
   colours, type, focus styles, and motion behaviour stay consistent. */

:root {
    /* ── Brand ───────────────────────────────────────────────────────────── */
    --brand:            #22c55e;   /* bright green — decorative fills / success  */
    --brand-strong:     #15803d;   /* accessible green — links, text-on-white (AA) */
    --brand-strong-hover:#166534;
    --brand-tint:       #f0fdf4;   /* pale green surface (hover/selected)        */

    /* Top bar — the bright welcome-page green (#22c55e) with white text, to
       match home.html. Change these two tokens to restyle every top bar.
       (For WCAG AA on this exact green, set --topbar-text to #06281a instead.) */
    --topbar-bg:        #22c55e;
    --topbar-text:      #ffffff;

    /* ── Neutrals ────────────────────────────────────────────────────────── */
    --bg:        #f8f8f8;
    --surface:   #ffffff;
    --text:      #1e293b;
    --muted:     #64748b;
    --border:    #cbd5e1;

    /* ── Semantic accents ────────────────────────────────────────────────────
       One source of truth for the non-brand accents used across the chrome
       (drawers, badges, buttons). Each has a saturated base, a darker "strong"
       for hover/text, and a pale tint surface. Prefer these over one-off hexes
       so the whole UI stays coherent and themable from one place.
         info    — blue   : classes, links, neutral emphasis
         danger  — red    : reviews due, destructive actions
         warning — amber  : timeouts, caution
         accent  — indigo : quizzes / "next" actions  */
    --info:          #2563eb;  --info-strong:   #1d4ed8;  --info-tint:    #eff6ff;
    --danger:        #ef4444;  --danger-strong: #dc2626;  --danger-tint:  #fee2e2;
    --warning:       #f59e0b;  --warning-strong:#b45309;  --warning-tint: #fffbeb;
    --accent:        #6366f1;  --accent-strong: #4f46e5;  --accent-tint:  #eef2ff;

    /* ── Shape & depth ───────────────────────────────────────────────────── */
    --radius-sm: 8px;
    --radius:    12px;
    --radius-lg: 20px;
    --radius-pill: 999px;
    --shadow-sm: 0 2px 8px rgba(0,0,0,.08);
    --shadow:    0 4px 16px rgba(0,0,0,.10);

    /* ── Type ────────────────────────────────────────────────────────────── */
    --font: 'Nunito', system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif;

    /* Lesson-content text-size multiplier, controlled by the A−/A/A+ widget and
       persisted per learner. 1 = default; the widget clamps roughly 0.8–1.6. */
    --content-scale: 1;

    /* ── Focus ───────────────────────────────────────────────────────────────
       TWO-TONE keyboard-focus ring. Every focus-visible ring in the app points
       at these tokens (base.html, signup, dashboard, graph) so they can never
       drift.

       Why two tones rather than one colour: WCAG 2.2 SC 2.4.11 wants the
       indicator to reach 3:1 against what sits next to it, and this app puts
       focusable controls on backgrounds that span the whole luminance range —
       a white card, a saturated subject banner (green/yellow/red/indigo), a
       dark toast, the walnut bookshelf. The previous single blue (#1d4ed8)
       managed 6.7:1 on white but only 2.94:1 on the green top bar and 1.34:1
       on the green primary button, i.e. invisible exactly where the primary
       call to action lives.

       No single colour clears 3:1 everywhere, so the ring carries one dark
       band and one light band: whichever background it lands on, one of the
       two contrasts. Measured for --focus-ring #0b1220:
         vs white 18.7 · page 17.6 · topbar green 8.2 · brand button 3.7 ·
         computers yellow 9.8 · math blue 5.1 · music 4.4 · language 5.0
       and on the dark surfaces where that band goes quiet (#1e293b toast,
       walnut) the white halo carries it instead.

       Geometry: halo occupies 0–2px outside the border box (box-shadow spread),
       the dark band 2–5px (outline at offset 2px), so the two abut exactly. */
    --focus-ring: #0b1220;
    --focus-ring-halo: #ffffff;
}

*, *::before, *::after { box-sizing: border-box; }

/* Body text defaults to a regular weight — bold is reserved for headings and
   buttons so hierarchy reads clearly and long passages stay easy on the eyes. */
body { font-family: var(--font); color: var(--text); font-weight: 400; }

/* Keyboard focus visible everywhere (mouse clicks stay clean via :focus-visible).
   The box-shadow halo deliberately replaces any decorative shadow the control
   carries for the moment it holds keyboard focus — a flat, high-contrast ring
   reads better than a soft drop shadow, and it only applies while focused. */
:focus-visible {
    outline: 3px solid var(--focus-ring);
    outline-offset: 2px;
    box-shadow: 0 0 0 2px var(--focus-ring-halo);
    border-radius: 4px;
}

/* Forced-colours / Windows High Contrast: the system supplies its own focus
   colour, and a hard-coded one there is worse than none. */
@media (forced-colors: active) {
    :focus-visible { outline-color: Highlight; box-shadow: none; }
}

/* Respect users who ask for less motion (calms the pulsing/animated UI) */
@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: .001ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: .001ms !important;
        scroll-behavior: auto !important;
    }
}

/* ── Skeleton loaders (gentle pulse — no moving sweep) ───────────────────────
   Shared placeholders used while async content (the node graph, lesson images)
   is still arriving. Reduced-motion users get a static block automatically via
   the media query above. */
@keyframes vsPulse { 0%, 100% { opacity: 1; } 50% { opacity: .4; } }

.vs-skeleton {
    background: var(--border);
    border-radius: var(--radius-sm);
    animation: vsPulse 1.5s ease-in-out infinite;
}

/* Lesson-image placeholder: a pulsing block holds the image's space and the
   real image fades in once loaded (prevents pop-in / layout shift). */
.vs-img-skel { position: relative; overflow: hidden; }
/* The pulse stays hidden until JS adds .vs-show (after VS_SKELETON_DELAY_MS),
   so quick-loading images never flash a placeholder. The wrapper still reserves
   the image's space immediately, so revealing the pulse causes no layout shift. */
.vs-img-skel::before {
    content: "";
    position: absolute; inset: 0;
    background: var(--border);
    animation: vsPulse 1.5s ease-in-out infinite;
    display: none;
}
.vs-img-skel.vs-show::before { display: block; }
.vs-img-skel.vs-loaded::before { display: none; }   /* must follow .vs-show to win when both set */
.vs-img-skel img { display: block; width: 100%; opacity: 0; transition: opacity .4s ease; }
.vs-img-skel.vs-loaded img { opacity: 1; }

/* ── Page-transition progress bar (top of viewport) ─────────────────────────
   Thin bar that trickles forward while a navigation is in flight, then sweeps
   to full and fades on the next page's load. */
#vs-progress {
    position: fixed;
    top: 0; left: 0;
    height: 3px; width: 0;
    background: var(--brand);
    box-shadow: 0 0 8px rgba(34, 197, 94, .55);
    z-index: 99999;
    opacity: 0;
    pointer-events: none;
    transition: width .2s ease, opacity .35s ease;
}
#vs-progress.vs-active { opacity: 1; }

/* ── Shared top-bar actions ──────────────────────────────────────────────────
   Right-hand controls rendered by core/_topbar.html. The #top-bar container and
   #title styles live per-page (base.html / home.html / dashboard.html) because
   some pages tint the bar per subject; only the action elements are shared here
   so the partial looks identical wherever it is used. */
.tb-actions { display: flex; align-items: center; gap: 16px; }
.tb-user {
    color: var(--topbar-text); font-weight: 800; font-size: 15px;
    opacity: .95; max-width: 30vw; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.tb-link {
    color: var(--topbar-text); text-decoration: none; font-weight: 800; font-size: 15px;
    opacity: .92; transition: opacity .15s;
}
.tb-link:hover { opacity: 1; text-decoration: underline; }
.tb-logout {
    background: rgba(255,255,255,.18); border: none; color: var(--topbar-text);
    font-family: var(--font); font-size: 14px; font-weight: 800; cursor: pointer;
    padding: 7px 16px; border-radius: var(--radius-pill); transition: background .15s;
}
.tb-logout:hover { background: rgba(255,255,255,.30); }
.tb-cta {
    background: var(--surface); color: var(--brand-strong);
    text-decoration: none; font-weight: 900; font-size: 14px;
    padding: 8px 18px; border-radius: var(--radius-pill); transition: transform .1s, box-shadow .15s;
    box-shadow: 0 2px 8px rgba(0,0,0,.12);
}
.tb-cta:hover { box-shadow: 0 4px 14px rgba(0,0,0,.18); }
.tb-cta:active { transform: scale(.97); }

/* Supervisor "view as student" pill (node pages only). */
.tb-admin-toggle {
    background: rgba(255,255,255,.18); color: var(--topbar-text, #fff);
    border: 1.5px solid rgba(255,255,255,.55); border-radius: var(--radius-pill);
    padding: 6px 15px; font-family: inherit; font-size: 13px; font-weight: 800;
    letter-spacing: .3px; white-space: nowrap; cursor: pointer;
}
@media (max-width: 640px), (max-height: 520px) {
    /* The banner also carries Back and the review badge at this size, so the
       pill gives up the width it doesn't need. */
    .tb-admin-toggle { padding: 4px 9px; font-size: 11px; letter-spacing: 0; }
}
