/* =====================================================================
   Languify — motion system v3 (preview, 2026-10-09)

   Rules this file obeys
     1. Content is visible by default. Every hiding rule is scoped under
        html.mo, a class that motion.js adds only when JS runs AND the
        visitor has not asked for reduced motion. No JS → nothing hides.
     2. Nothing moves while it is being read: entrances run once, finish
        in under half a second, and never re-run on scroll-back.
     3. Scroll is never hijacked, nothing autoplays with sound, nothing
        loops forever.
     4. Two memorable moments (the hero correction, the method panel),
        quiet entrances everywhere else.

   Vocabulary
     data-reveal            fade + 12px rise when it first enters view
     data-reveal="panel"    a slightly larger rise, for whole panels
     data-reveal="mark"     the marker stroke draws itself under a word
     --d                    entrance delay, e.g. style="--d:.08s"
     data-stagger           children of this element reveal in sequence
   ===================================================================== */

/* ---- entrances ---------------------------------------------------- */
html.mo [data-reveal]{opacity:0;transform:translateY(12px);
  transition:opacity .5s cubic-bezier(.2,.7,.2,1),transform .5s cubic-bezier(.2,.7,.2,1);
  transition-delay:var(--d,0s);will-change:opacity,transform}
html.mo [data-reveal="panel"]{transform:translateY(22px)}
html.mo [data-reveal].in{opacity:1;transform:none}

/* the marker stroke draws under the word instead of just appearing */
html.mo [data-reveal="mark"]{opacity:1;transform:none;background-size:0% .42em;
  transition:background-size .62s cubic-bezier(.2,.7,.2,1) var(--d,0s)}
html.mo [data-reveal="mark"].in{background-size:100% .42em}

/* ---- moment 1 · the hero correction ------------------------------
   A real teaching example, the kind that goes on our posts: the wrong
   sentence is struck through, the right one is written under it, and the
   marker lands on the word that changed. Plays once, has a replay
   button, and reads correctly as static text if it never plays.        */
.demo{display:flex;flex-direction:column;gap:14px}
.demo-row{display:flex;align-items:center;gap:12px}
.demo-tag{font-size:13px;font-weight:700;color:var(--quiet-ink);flex:0 0 auto;min-width:4.5ch}
.demo-s{font-family:var(--en);direction:ltr;unicode-bidi:isolate;font-weight:700;
  font-size:clamp(21px,3vw,30px);line-height:1.35;position:relative;display:inline-block}
.demo-row.no .demo-s{color:#7B8794}
.demo-row.no .demo-s::after{content:"";position:absolute;inset-inline:-2px;top:52%;height:3px;
  border-radius:3px;background:#C96A6A;transform:scaleX(0);transform-origin:right center}
html.mo .demo-row.no .demo-s::after{transition:transform .42s cubic-bezier(.2,.7,.2,1)}
.demo.play .demo-row.no .demo-s::after,.demo.done .demo-row.no .demo-s::after{transform:scaleX(1)}
html:not(.mo) .demo-row.no .demo-s::after{transform:scaleX(1)}

html.mo .demo-row.yes{opacity:0;transform:translateY(8px);
  transition:opacity .4s ease .5s,transform .4s ease .5s}
.demo.play .demo-row.yes,.demo.done .demo-row.yes{opacity:1;transform:none}
.demo-row.yes .demo-s .u{background-repeat:no-repeat;background-position:center 92%;
  background-size:100% .34em;
  background-image:url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 13' preserveAspectRatio='none'%3E%3Cpath d='M4 9C40 3 68 12 104 7s62-5 92 1' fill='none' stroke='%233E7CA6' stroke-width='6' stroke-linecap='round'/%3E%3C/svg%3E")}
html.mo .demo-row.yes .demo-s .u{background-size:0% .34em;transition:background-size .5s cubic-bezier(.2,.7,.2,1) .95s}
.demo.play .demo-row.yes .demo-s .u,.demo.done .demo-row.yes .demo-s .u{background-size:100% .34em}

.demo-why{font-size:15px;color:var(--sec);line-height:1.7}
html.mo .demo-why{opacity:0;transition:opacity .4s ease 1.3s}
.demo.play .demo-why,.demo.done .demo-why{opacity:1}
.demo-replay{display:inline-flex;align-items:center;gap:8px;align-self:flex-start;min-height:44px;
  font-family:var(--ar);font-size:13.5px;font-weight:700;color:var(--sec);cursor:pointer;
  border-radius:999px;padding:6px 12px;transition:background .16s ease,color .16s ease}
.demo-replay:hover{background:var(--tint);color:var(--ink)}
html:not(.mo) .demo-replay{display:none}

/* ---- moment 2 · the method panel ---------------------------------
   The explanation arrives as a sheet that slides up over the section
   before it — the "next page opening" idea, done as a transition and
   not as a route change. Scroll-linked where the browser supports it
   (Chromium 115+), a plain entrance everywhere else, nothing at all
   under reduced motion. The sheet never covers text: the section above
   keeps its own bottom padding.                                       */
.sheet-panel{position:relative;z-index:1;background:var(--cream);
  border-radius:var(--r-lg) var(--r-lg) 0 0;border-top:1px solid var(--hair);
  margin-top:clamp(-56px,-4vw,-24px)}
.sheet-panel::before{content:"";position:absolute;inset-inline:0;top:-1px;height:1px;background:var(--hair)}
.sheet-grip{position:absolute;left:50%;top:14px;width:54px;height:4px;border-radius:4px;
  background:var(--hair);transform:translateX(-50%)}

@media (prefers-reduced-motion:no-preference){
  @supports (animation-timeline:view()){
    html.mo .sheet-panel{animation:sheetUp linear both;
      animation-timeline:view();animation-range:entry 0% entry 62%}
    @keyframes sheetUp{
      from{transform:translateY(46px);border-radius:40px 40px 0 0}
      to{transform:translateY(0);border-radius:var(--r-lg) var(--r-lg) 0 0}
    }
  }
}

/* ---- quiet interactions ------------------------------------------- */
.hoverable{transition:background .18s ease,border-color .18s ease,transform .18s ease}
.hoverable:hover{transform:translateY(-2px)}

/* a 21-day strip, the forgetting curve and the vocabulary grid keep the
   behaviour they already had; their triggers live in motion.js so that
   every scroll-driven thing on the page is started from one place.     */
html.mo .curve.armed{stroke-dasharray:2600;stroke-dashoffset:2600}
html.mo .curve.drawn{stroke-dashoffset:0;transition:stroke-dashoffset 2s ease-out}
html.mo .peaks.armed circle{transform-box:fill-box;transform-origin:center;transform:scale(0)}
html.mo .peaks.pop circle{animation:dotpop .4s ease-out forwards}
html.mo .peaks.pop circle:nth-child(2){animation-delay:.6s}
html.mo .peaks.pop circle:nth-child(3){animation-delay:1.2s}
@keyframes dotpop{to{transform:scale(1)}}

/* =====================================================================
   LIVELINESS — floating decoration, pointer response, card hover
   Everything here is decorative: it sits behind the content, never
   takes pointer events, never moves text, and is switched off entirely
   under prefers-reduced-motion.
   ===================================================================== */

/* ---- the floating field ------------------------------------------
   Soft tinted discs behind a section. The <i> carries the pointer
   offset, its ::before carries the slow drift, so the two never fight
   over one transform.                                                */
.decor{position:absolute;inset:0;overflow:hidden;pointer-events:none;z-index:0;contain:layout paint}
.decor i{position:absolute;display:block;border-radius:50%;
  transform:translate3d(calc(var(--mx,0) * var(--d,14px)),calc(var(--my,0) * var(--d,14px)),0);
  transition:transform .8s cubic-bezier(.2,.7,.2,1)}
.decor i::before{content:"";position:absolute;inset:0;border-radius:inherit;
  background:radial-gradient(circle at 32% 30%,var(--c1,var(--tint)) 0%,transparent 72%);
  opacity:var(--o,.85)}
.section>.wrap,.hero>.wrap,.final>.wrap{position:relative;z-index:1}

@media (prefers-reduced-motion:no-preference){
  html.mo .decor i::before{animation:drift var(--t,19s) ease-in-out infinite;will-change:transform}
  html.mo .decor i:nth-child(2)::before{animation-duration:var(--t,24s);animation-direction:reverse}
  html.mo .decor i:nth-child(3)::before{animation-duration:var(--t,28s)}
}
@keyframes drift{
  0%{transform:translate3d(0,0,0) scale(1)}
  50%{transform:translate3d(0,-16px,0) scale(1.04)}
  100%{transform:translate3d(0,0,0) scale(1)}
}
/* fewer, smaller shapes on a phone */
@media (max-width:700px){
  .decor i{--d:6px}
  .decor i:nth-child(3){display:none}
}

/* ---- cards that answer the cursor ---------------------------------
   A 1px lift, a soft shadow, and a very faint glow that follows the
   pointer across the card. Fine pointers only, so nothing sticks on a
   touch screen.                                                      */
[data-glow]{position:relative;isolation:isolate}
[data-glow]::after{content:"";position:absolute;inset:0;border-radius:inherit;pointer-events:none;
  z-index:-1;opacity:0;transition:opacity .35s ease;
  background:radial-gradient(260px circle at var(--cx,50%) var(--cy,50%),
             color-mix(in srgb,var(--blue) 14%,transparent) 0%,transparent 70%)}
@media (hover:hover) and (pointer:fine){
  html.mo [data-glow]:hover::after{opacity:1}
  html.mo .card.hoverable:hover,html.mo a.card:hover,html.mo .tool:hover,
  html.mo .rev:hover,html.mo .opt:hover{transform:translateY(-3px);box-shadow:var(--lift)}
  html.mo .chart-card:hover,html.mo .strip-wrap:hover,html.mo .vocab-wrap:hover{transform:none;box-shadow:none}
}
