/* =====================================================================
   THE LESSON KIT — shared by every exercise in the standard lesson.

   Load order in an exercise:   chassis.css  →  lesson-kit.css  →  its own <style>
   The kit may override the chassis. An exercise may override the kit, but if it
   needs to, ask first whether the kit is wrong.

   Everything here is proven on at least two screens. Read
   ../deliverable/design-principles.md for the why, and COMPONENTS.md for the
   values and their reasoning.
   ===================================================================== */


/* =====================================================================
   0 · THE TOP BAR IS THE LESSON'S, NOT THE SCREEN'S
   ===================================================================== */

/* Inside the harness the X and the progress bar are drawn by the SHELL, over the frame,
   so they hold still while the screen beneath them slides. The bar in here becomes a
   SPACER — hidden, but still holding the content exactly where it was.

   `visibility`, not `display`: the geometry has to survive, and this is the one thing
   keeping the content off the status bar. It is not `opacity` either, because the X
   would still take taps through a bar the rep cannot see.

   `.framed` is set by lesson-kit.js when the document has a parent, so opening any
   exercise as a file on its own still gives you a working bar. */
.framed .top-bar { visibility:hidden; }


/* =====================================================================
   1 · TOKENS
   ===================================================================== */

:root {
    /* ---- the one green: "you got it right", everywhere it appears ----
       #17E06B is built to sit beside the mic button's #38BDF8. The blue reads vivid
       because its saturation is 93%; the green it replaced was 66%, which is why that
       one read as a mint. This is 82%.
       It is NOT the blue's hue rotated to green — green carries 0.72 of perceived
       luminance against blue's 0.07, so the literal rotation (hsl 145 93% 60%) lands at
       0.70 relative luminance and becomes the loudest thing on a dark screen. Dropping
       lightness instead holds luminance at 0.546, a hair under the old green's 0.573. */
    --ok:#17E06B;
    --ok-core:#12BA5E;      /* orbit specks: a core darker than its glow reads as lit from within */

    /* the press ramp for each key face — see section 2 for how these are derived */
    --ok-lip:#11A754;       /* --ok at L−12 */
    --ok-press:#0E9049;     /* --ok at L−17: PAST the lip, so a pressed face reads as in shadow */
    --yellow-press:#B8B100; /* --yellow at L−14, past --lip #D3CB00 */

    /* ---- the pick: what YOU chose ----
       Selection is BLUE, not the house yellow. Yellow is the ACTION colour — the Check
       button, the primary CTA — and a chosen card is not an action, it is your answer
       sitting there waiting. Blue already means "yours" everywhere in this lesson: the
       mic button, the words filling in as you speak, your own chat bubble. A picked card
       is the same claim in a different surface, so it wears the same colour.
       This is also what makes the onboarding nudge legible: the pulse wears the selection
       colour, so it reads as "this is what tapping does". */
    --pick:#38BDF8;                          /* the mic button's face */
    --pick-fill:rgba(56,189,248,0.10);       /* the same fill as ex1's .say-line.mine */
    --pick-lip:#0E6F9E;

    /* a wrong answer is RED, not the amber --warn. --warn is a caution colour and on a
       dark ground it reads as "careful", not as "no". */
    --no:#FF4B4B;
    --no-fill:rgba(255,75,75,0.12);
    --no-lip:#A32C2C;
}


/* =====================================================================
   2 · THE PRESS
   -----------------------------------------------------------------------
   Every button in the lesson presses like exercise 1's mic button, because that
   is the one that felt right. What makes it feel physical is THREE THINGS, and
   all three are light, not travel:

     a lit bevel at rest          inset 0 2px 0 rgba(255,255,255,0.42)
     a ground shadow that tightens as the key seats
     a highlight that catches the rim on the way down

   A lip that merely shrinks only says *it moved*.

   TIMING is asymmetric, and that is the whole difference between force and a blink:

     down   81ms   cubic-bezier(0.12,0.85,0.25,1)   leaves immediately, settles into the bottom
     up    312ms   cubic-bezier(0.22,1,0.36,1)      easeOutQuint, NO overshoot

   A springy bounce on release is the cartoon tell that was rejected on the path-lab
   coin. The asymmetry is done by putting the release timing on the base rule and the
   press timing inside :active — the browser owns the state, so there is no timer to
   leak and the failure mode from ex1's stuck button cannot occur here.

   THE FACE DARKENS FASTER THAN IT TRAVELS — 80ms against 81ms — so the darkening
   arrives fractionally before the key bottoms out, which reads as cause rather than
   consequence. And the pressed face is darker than the LIP, not merely darker than
   the face, so it reads as being in shadow rather than as a different colour.

   THE LIP NEVER FULLY FLATTENS. Two of its pixels stay standing, so the button keeps
   a body under pressure instead of collapsing to a sticker.
   ===================================================================== */

/* The timing lives on the specific selectors rather than on a shared class, because
   an exercise addresses Check by its id (#check) and an id beats a class — a .k-press
   utility would silently lose its transition wherever that happened. Any exercise rule
   that declares `transition` on these elements must therefore be DELETED, not layered:
   the shorthand replaces the whole list. */

.coach-check {
    transition: opacity 300ms ease,
                transform 312ms cubic-bezier(0.22,1,0.36,1),
                box-shadow 312ms cubic-bezier(0.22,1,0.36,1),
                background-color 160ms linear;
}
.coach-check.ready:active {
    transition: opacity 300ms ease,
                transform 81ms cubic-bezier(0.12,0.85,0.25,1),
                box-shadow 81ms cubic-bezier(0.12,0.85,0.25,1),
                background-color 80ms linear;
}

/* ---- Check: the chassis gives it --yellow and a 6px lip. The kit adds the ground
   shadow, the bevel, the seat and the darkened face. ---- */
.coach-check.ready {
    box-shadow: 0 8px 0 var(--lip),
                0 14px 20px -8px rgba(0,0,0,0.62),
                inset 0 2px 0 rgba(255,255,255,0.42),
                inset 0 -1px 0 rgba(0,0,0,0.10);
}
.coach-check.ready:active {
    transform: translateY(6px);               /* 8px lip → 2px: the mic button's own throw */
    background: var(--yellow-press);
    box-shadow: 0 2px 0 var(--lip),
                0 5px 9px -5px rgba(0,0,0,0.75),
                inset 0 3px 9px rgba(255,255,255,0.34);
}

/* ---- Continue: it had no lip at all — a flat slab that nudged 2px, which is the
   press that prompted all of this. Same key, in the HOUSE YELLOW (Vin, Sep 22) — so its
   lip and its pressed face come off the yellow ramp, not the green one. ---- */
.fb-cta {
    box-shadow: 0 8px 0 var(--lip),
                0 14px 20px -8px rgba(0,0,0,0.62),
                inset 0 2px 0 rgba(255,255,255,0.42),
                inset 0 -1px 0 rgba(0,0,0,0.10);
    transition: opacity 227ms cubic-bezier(0.2,0.8,0.2,1),
                transform 312ms cubic-bezier(0.22,1,0.36,1),
                box-shadow 312ms cubic-bezier(0.22,1,0.36,1),
                background-color 160ms linear;
}
.fb-cta:active {
    transform: translateY(6px);
    background: var(--yellow-press);
    box-shadow: 0 2px 0 var(--lip),
                0 5px 9px -5px rgba(0,0,0,0.75),
                inset 0 3px 9px rgba(255,255,255,0.34);
    transition: transform 81ms cubic-bezier(0.12,0.85,0.25,1),
                box-shadow 81ms cubic-bezier(0.12,0.85,0.25,1),
                background-color 80ms linear;
}
/* The miss sheet's CTA was the one yellow button on this panel and carried its own ramp.
   Now that every Continue is yellow the base rule already says all of this, so the
   override is gone rather than left to say the same thing twice. */

/* ---- Option chips: the same physics, a quieter body.
   NO resting lip. A lip would make a chip a key, and a chip is not a key — it is one
   of several alternatives being compared, and three raised keys in a column read as
   three CTAs. So the chip stays flat at rest and answers the finger with depth and
   light only while it is held. ---- */
.opt {
    transition: opacity 280ms ease,
                transform 312ms cubic-bezier(0.22,1,0.36,1),
                box-shadow 312ms cubic-bezier(0.22,1,0.36,1),
                background-color 160ms linear,
                border-color 150ms linear;
}
.opt:active:not(.correct):not(.wrong) {
    transform: translateY(2px);
    background: rgba(255,255,255,0.06);
    border-color: #4A4A4C;
    box-shadow: inset 0 2px 7px rgba(0,0,0,0.30);
    transition: transform 81ms cubic-bezier(0.12,0.85,0.25,1),
                box-shadow 81ms cubic-bezier(0.12,0.85,0.25,1),
                background-color 80ms linear,
                border-color 80ms linear;
}
/* an answered chip is no longer a button and must not answer a press at all */
.opt.correct, .opt.wrong { pointer-events:none; cursor:default; }

/* Selection is blue across every exercise. The chassis paints .opt.selected yellow;
   yellow stays the action colour (Check, the CTAs) and blue becomes "the one you chose". */
.opt.selected { border-color:var(--pick) !important; background:var(--pick-fill) !important; }


/* =====================================================================
   3 · THE THREAD, THE BUBBLES AND THE FEEDBACK SHEET
   -----------------------------------------------------------------------
   Lifted verbatim from ex1.html / ex2.html on Sep 21, where it was byte-identical
   in both (7,987 characters, diffed before the move). Every value and its reasoning
   is in COMPONENTS.md §8 and §9 — do not change a number here without reading that.
   ===================================================================== */

    .coach-q.tight { margin-bottom:19px; }   /* chassis 16 + 3 */
    .thread { position:relative; }   /* so row.offsetTop is measured against the THREAD */
    .row { margin-bottom:22px; }
    .who { font-size:12px; font-weight:600; letter-spacing:0.06em; text-transform:uppercase;
        color:var(--muted-2); margin-bottom:6px; }
    .row.right .who { color:#E0E0E0; margin-right:4px; }
    .row.left .who { margin-left:4px; }
    .bub { padding:13px 17px; max-width:85%; font-size:16px; font-weight:500;
        line-height:1.45; letter-spacing:-0.01em; }
    .bub.them { background:#1F1F22; border:none; color:#E4E4E7;
        border-radius:18px 18px 18px 5px; box-shadow:0 4px 16px -4px rgba(0,0,0,0.35); }
    .bub.you { background:#0EA5E9; border:none; color:#FFFFFF;
        border-radius:18px 18px 5px 18px; box-shadow:0 6px 20px -6px rgba(14,165,233,0.25); }
    .bub.them.talking { animation:none; border:none;
        box-shadow:0 4px 16px -4px rgba(0,0,0,0.35), 0 0 0 1px rgba(255,255,255,0.10); }

    /* a spoken customer line: its words go grey -> full colour with the voice */
    .bub .cw { color:#5A5A5E; transition:color 0.18s ease; }
    .bub .cw.lit { color:#E4E4E7; }

    /* ---- the feedback panel ----
       No flood. The sheet is the customer bubble's own dark (#1F1F22), so the panel
       reads as part of the same surface rather than a coloured interruption. */
    /* overflow:hidden is load-bearing — every glow below is clipped to the panel, so
       nothing bleeds onto the lesson above it. */
    .fb { position:absolute; left:0; right:0; bottom:0; z-index:20; display:none;
        overflow:hidden;
        background:#1F1F22 !important; border:none !important;
        border-top:1px solid #2E2E32 !important;
        border-radius:0 !important; box-shadow:0 -10px 30px rgba(0,0,0,0.45) !important;
        padding:32px 26px 42px !important; }
    /* ---- the Jump page's press effect, in green, clipped to this panel ----
       .fb-pulse = .dk-jpulse — a 250px radial aura, scale 1 -> 10, opacity 1 -> 0 over
       1000ms on cubic-bezier(0.2,0.8,0.2,1). Transform and opacity only, so it never
       repaints the panel. The panel's overflow:hidden keeps it inside the toast.
       The companion border ring (.dk-jwow-glow) was tried and removed — a 12px inset
       band reads fine around a full screen and like a frame around a 250px sheet. */
    /* the panel slides while its children animate; promote it too, or every child's
       animation is re-composited against a moving, unpromoted parent */
    /* THE ENTRANCE RUNS AT 1.5x (Vin, Sep 22). Every duration and every delay in it is
       divided by 1.5, not just the panel's own rise — speeding one layer and leaving the
       rest turns a single gesture into several. The whole landing was ~1870ms and is now
       ~1247ms: panel 200 · cheer 573 after 120 · ripple 533 · orbit 800 · aura 667 ·
       body at 767 · button at 1020. */
    .fb.show { display:block; will-change:transform, opacity;
        animation:fbUp 0.2s cubic-bezier(0.2,0.8,0.2,1); }
    @keyframes fbUp { from { transform:translateY(34px); opacity:0; } to { transform:translateY(0); opacity:1; } }

    .fb-content { position:relative; z-index:2; }
    /* The affirmation is set in Druk Text Wide Medium — a wide display cut, so it needs no
       extra weight; asking Inter for the same presence meant 800 weight, which this replaces.
       23 -> 25.3px is Vin's 10%.
       Tracking -0.06em: ~1.5px out of each gap at 25.3px. Chosen off a rendered ladder —
       -0.075em begins to crowd "d it!" and -0.09em closes the bowls in "Nailed", so this is
       the last step before the cut starts fighting itself. Checked against the longest head
       ("Good work!"), which is the one that would collide first. */
    /* WHITE, not the green (Vin, Sep 22). --ok is still "correct" everywhere it carries
       that meaning — the answer chip's border, the aura, the ripple, the orbit — but the
       panel's own type is no longer one of those places: the green sheet was reading as a
       coloured interruption rather than as the lesson's own voice. COMPONENTS section 12
       says the head, the body and the button are --ok; on those three it is now wrong. */
    .fb-head { color:#FFFFFF; font-family:'Druk Text Wide Medium', 'Inter', sans-serif;
        font-weight:500; font-size:25.3px; letter-spacing:-0.06em; line-height:1.1;
        margin-bottom:14px; }
    /* will-change promotes the word to its own compositor layer BEFORE the cheer starts.
       yesCheer scales TEXT from 0.74 to 1.15, and unpromoted text is re-rasterised at every
       intermediate scale — glyph rasterisation on each frame, while the panel behind it is
       also animating a transform. That is the stutter as the head comes in. Promoted, it is
       rastered once and the compositor does the scaling. */
    #fb-title { display:inline-block; transform-origin:left bottom; position:relative; z-index:2;
        will-change:transform, opacity; }

    /* ---- orbiting particles, from learning-path-sep-26 (.particle-container / ringOrbit)
       Six specks on a ring that spins through 420 degrees over 1.2s while the ring itself
       swells and settles. The rotateX(35deg) is the reference's — it makes the ring an
       ellipse, so particles read as passing behind and in front of the word rather than
       sliding around a flat circle. Sized to "Yes!" and centred on it, nothing else. ---- */
    .yes-wrap { position:relative; display:inline-block; perspective:800px; }
    /* the ripple that bursts with them — .shockwave / rippleBurst. Scaled to the word:
       the coin is 70px with a 140px ring and a 200px orbit, so a ~55px word takes
       110px and 160px. */
    .yes-ripple { position:absolute; top:50%; left:50%; width:110px; height:110px;
        border:2px solid var(--ok); border-radius:50%; pointer-events:none; z-index:1;
        will-change:transform, opacity;
        transform:translate(-50%, -50%) rotateX(35deg) scale(0.8); opacity:0; }
    .yes-ripple.go { animation:rippleBurst 0.533s cubic-bezier(0.165, 0.84, 0.44, 1) 0.12s forwards; }
    @keyframes rippleBurst {
        0%   { transform:translate(-50%, -50%) rotateX(35deg) scale(0.8); opacity:0.8; border-width:8px; }
        40%  { opacity:0.4; }
        100% { transform:translate(-50%, -50%) rotateX(35deg) scale(2.8); opacity:0; border-width:1px; }
    }
    .yes-orbit { position:absolute; top:50%; left:50%; width:160px; height:160px;
        margin:-80px 0 0 -80px; pointer-events:none; z-index:1; opacity:0;
        will-change:transform, opacity;
        transform:scale(0.8) rotateX(35deg); }
    .yes-orbit.go { animation:ringOrbit 0.8s cubic-bezier(0.25, 1, 0.5, 1) 0.12s forwards; }
    .yes-orbit i { position:absolute; width:4px; height:4px; border-radius:50%;
        background:var(--ok-core); box-shadow:0 0 8px var(--ok); }
    .yes-orbit i:nth-child(1) { top:0;   left:50%; }
    .yes-orbit i:nth-child(2) { top:50%; right:0; }
    .yes-orbit i:nth-child(3) { bottom:0; left:50%; }
    .yes-orbit i:nth-child(4) { top:50%; left:0; }
    .yes-orbit i:nth-child(5) { top:15%; left:15%; }
    .yes-orbit i:nth-child(6) { top:15%; right:15%; }
    @keyframes ringOrbit {
        0%   { transform:scale(0.8) rotateX(35deg) rotateZ(0deg);   opacity:0; }
        15%  { transform:scale(1.6) rotateX(35deg) rotateZ(45deg);  opacity:1; }
        45%  { transform:scale(1.1) rotateX(35deg) rotateZ(180deg); }
        100% { transform:scale(1.3) rotateX(35deg) rotateZ(420deg); opacity:0; }
    }
    #fb-title.cheer { animation:yesCheer 0.573s linear 0.12s both; }
    @keyframes yesCheer {
        0%   { transform:translateY(14px) scale(0.74); opacity:0;
               animation-timing-function:cubic-bezier(0.12,0.85,0.22,1); }
        15%  { transform:translateY(-2px) scale(1.15); opacity:1;
               animation-timing-function:cubic-bezier(0.3,0,0.3,1); }
        34%  { transform:scale(0.985); }
        56%  { transform:scale(1.04); }
        78%  { transform:scale(0.997); }
        100% { transform:none; opacity:1; }
    }
    /* one weight throughout — only the opening clause is bold, and nothing after it */
    /* One green across the panel — --ok for the title, the body and the button, with
       the label cut out of the button in the sheet's own #1F1F22. The
       body separates itself by WEIGHT and SIZE rather than by a second green, which is
       what made the last pass read as two unrelated colours. */
    /* greyish white — one step under the head, so the two separate by TONE as well as by
       weight and size. #D4D4D8 is the same neutral family the path's receded type uses. */
    .fb-note { color:#D4D4D8; font-size:17px; font-weight:400; line-height:1.62;
        margin-bottom:30px; opacity:0; }
    .fb-note.in { animation:fbRise 0.227s cubic-bezier(0.2,0.8,0.2,1) both; }
    .fb-note b { font-weight:400; color:inherit; }   /* no bolding inside the body */
    @keyframes fbRise { from { opacity:0; transform:translateY(8px); } to { opacity:1; transform:none; } }
    .fb-cta { width:100%; padding:16px 24px; border:none; border-radius:12px;
        font-family:'Inter',sans-serif; font-size:17px; font-weight:700; letter-spacing:0;
        text-transform:none; color:#1F1F22; background:var(--yellow); cursor:pointer;
        opacity:0; }   /* the press and its transitions: lesson-kit.css §2 */
    /* `backwards`, NOT `both`. A `both` fill holds the final keyframe forever — and that
       keyframe is transform:none — and an animation outranks a normal declaration, so
       :active { transform } was overridden on every press and the button could not travel
       at all. `backwards` covers the pre-start frame and then hands the element back to its
       own styles, so the opacity below is what persists and the press is free to move it. */
    .fb-cta.in { opacity:1; animation:fbRise 0.227s cubic-bezier(0.2,0.8,0.2,1) backwards; }

    .fb-pulse { position:absolute; left:0; top:0; width:250px; height:250px; border-radius:9999px;
        pointer-events:none; z-index:1; opacity:0; will-change:transform, opacity;
        background:radial-gradient(circle, rgba(23,224,107,0.6) 0%, transparent 60%); }


/* =====================================================================
   4 · THE AUDIO GATE
   ---------------------------------------------------------------------
   A full-panel overlay: play glyph, "This lesson has sound", "Tap anywhere to start".
   Shown only when the browser is actually blocking audio — see audioAllowed() in
   lesson-kit.js. The exercise supplies the markup; this is its dress.
   ===================================================================== */

    .gate { position:absolute; inset:0; z-index:50; background:rgba(13,13,15,0.92);
        -webkit-backdrop-filter:blur(3px); backdrop-filter:blur(3px);
        display:none; flex-direction:column; align-items:center; justify-content:center; padding:0 28px; }
    .gate.show { display:flex; }
    .gate-icon { width:64px; height:64px; border-radius:50%; background:var(--yellow);
        display:flex; align-items:center; justify-content:center; margin-bottom:22px; }
    .gate-icon svg { width:28px; height:28px; fill:var(--ink); }
    .gate-t { font-size:20px; font-weight:700; letter-spacing:-0.015em; color:#fff; text-align:center; }
    .gate-s { font-size:15px; font-weight:400; line-height:23px; color:var(--muted); text-align:center; margin-top:8px; }
