/* ============================================================
   Nova FX · blogfx3.css — card-quality pass for the Learn grid
   Mount: AFTER blogfx2.css  (b-learn → blogfx → blogfx2 → this)
   Runtime: blogfx3.js (ES5 IIFE · window.BL3={reduced} only global)
   ------------------------------------------------------------
   0  .bl-meta row + .bl-go arrow chip (double-sweep arrow pair)
      — .bl-go fills the affordance gap left by the dead
      selector `.bl-post .bl-read svg` (blogfx2.css:37-38 —
      .bl-read does not exist inside .bl-post markup)
   1  tilt-spring — OWNS the card transform (neutralizes the
      legacy transform lift on .bl-post/.bl-feat, see WHY)
   2  read-line draw under the post excerpt
   3  .bl-read underline draw + arrow nudge (featured card)
   4  art parallax (--px/--py from the same pointermove)
   5  .bl-art-glow conic sweep (posts)
   6  cat chip pop + press states
   7  ripple tint (reuses the interact-pack ripple — see JS note)
   8  generated cover art .bl-gen (seeded per title, JS-built)
   9  search pill .bl3-search + .bl3-hide filter state
   10 pointer-state FX gated to (hover:hover)+(pointer:fine)
   11 mobile ≤860px: tilt/parallax/glow off, search narrower
   12 master prefers-reduced-motion static block (LAST — must
      override every transition/animation this file sets)
   Per-card category colour flows as --blc (blogfx.css:26-29)
   and is consumed everywhere below. Motion is limited to
   transform/translate/scale/opacity.
   ============================================================ */

/* ============================================================
   0 · .bl-meta row + .bl-go arrow chip
   .bl-meta gains a 10px gap (the "·" separators from
   blogfx.css:212 stay); .bl-go takes the right pin (auto
   margin) so .bl-lvl keeps its natural inline spot.
   ============================================================ */
.bl-meta{display:flex;align-items:center;gap:10px}
.bl-meta .bl-lvl{margin-left:0}
.bl-meta .bl-go{margin-left:auto}
/* no "·" separator in front of the arrow chip (.bl-meta>span+span
   would add one — same !important kill .bl-lvl::before uses) */
.bl-meta>.bl-go::before{content:none!important}

.bl-go{position:relative;display:inline-flex;align-items:center;
 justify-content:center;flex:none;width:30px;height:30px;
 border-radius:10px;overflow:hidden;color:var(--dim);
 border:1px solid var(--line);background:transparent;
 /* entrance for the injected node (JS adds .bl3-go-in ~120ms
    after the card's own reveal — cards themselves are already
    animated by .rv/.bl2, never double-animated here) */
 opacity:0;transform:translateX(8px);
 transition:opacity .35s var(--bl3-ease,cubic-bezier(.2,.8,.2,1)) .2s,
   transform .35s var(--bl3-ease,cubic-bezier(.2,.8,.2,1)) .2s}
.bl-go.bl3-go-in{opacity:1;transform:none}
.bl-go svg{width:14px;height:14px;display:block;flex:none;
 transition:transform .3s cubic-bezier(.3,.7,.3,1)}
/* duplicate arrow (2nd svg injected by JS) parks outside-left,
   enters to center while the first exits right (see §10) */
.bl-go svg:last-child{position:absolute;left:50%;top:50%;
 margin:-7px 0 0 -7px;transform:translateX(-220%)}

/* ============================================================
   1 · tilt-spring — transform owner + legacy-lift neutralizer
   --------------------------------------------------------------
   WHY this neutralizer exists (source order + specificity):
   legacy transform-lift rules on these cards —
     nova.css   .card{transition:.2s}                         (0,1,0)
     nova.css   .card:hover{transform:translateY(-4px)...}     (0,2,0)
     blogfx.css .bl-post{transition:transform .2s...}         (0,1,0)
     blogfx2    .bl-post{transition:transform .4s...}          (0,1,0)
     blogfx2    .bl-post:hover{transform:translateY(-4px);
                 box-shadow:...}                               (0,2,0)
     blogfx2    .bl-grid .card{transition:transform .55s,
                 opacity .55s,...}                             (0,2,0)
   This file loads LAST. `a.bl-post.bl3-tilt` = (0,2,1), which
   is ≥ every legacy selector above, and the identical
   transform stack is pinned in the base, :hover,
   :focus-visible AND .bl3-live states — so the legacy hover
   lift can never win again and tilt owns the transform.
   Deliberately kept intact:
     · border/box-shadow hover glow (nova.css .card:hover +
       blogfx.css .bl-post:hover + blogfx2's hover ring —
       blogfx2:40 only ever set transform+box-shadow, and the
       transform half is what this family replaces)
     · entrance — blogfx2 `.bl-grid.bl2-armed
       .card:not(.bl2-in)` is (0,4,0) and still owns the
       pre-reveal transform/opacity; `opacity .55s` is carried
       inside the transition lists below so the staggered
       entrance (and the .bl3-hide dim) keeps its opacity
       transition. JS clears the stale inline
       transitionDelay (blogfx2.js:68) on first pointerenter.
   JS (fine pointer only) writes --tx/--ty (±4° posts, ±2.5°
   feat) and --lift:-4px while hovered, then removes them on
   leave → the .5s spring cubic-bezier(.25,1.45,.35,1) returns
   the card. .bl3-live (pointerenter) switches tracking to
   .12s ease-out. Without JS/no pointer the vars default to 0
   → cards sit flat (legacy lift intentionally gone).
   -------------------------------------------------------------- */
/* neutralize legacy .card lift (nova.css .card:hover + blogfx2 .bl-post:hover) so tilt owns transform */
a.bl-post.bl3-tilt,a.bl-post.bl3-tilt:hover,
a.bl-post.bl3-tilt:focus-visible,a.bl-post.bl3-tilt.bl3-live,
a.bl-feat.bl3-tilt,a.bl-feat.bl3-tilt:hover,
a.bl-feat.bl3-tilt:focus-visible,a.bl-feat.bl3-tilt.bl3-live{
 transform:perspective(900px) translate3d(0,var(--lift,0px),0)
   rotateX(var(--tx,0deg)) rotateY(var(--ty,0deg))}
a.bl-post.bl3-tilt,a.bl-feat.bl3-tilt{
 transition:transform .5s cubic-bezier(.25,1.45,.35,1),
   opacity .55s cubic-bezier(.2,.8,.2,1),scale .16s ease,
   border-color .3s,box-shadow .35s}
a.bl-post.bl3-tilt.bl3-live,a.bl-feat.bl3-tilt.bl3-live{
 transition:transform .12s ease-out,
   opacity .55s cubic-bezier(.2,.8,.2,1),scale .16s ease,
   border-color .3s,box-shadow .35s}

/* ============================================================
   2 · read-line draw under the excerpt (posts)
   .bl-post p keeps its line-clamp box (blogfx.css:166); the
   underline sits at the padding-box bottom, inside the clip
   region → always visible, never clipped.
   ============================================================ */
.bl-post p{position:relative;padding-bottom:12px}
.bl-post p::after{content:'';position:absolute;left:0;bottom:0;
 height:2px;width:100%;border-radius:2px;
 transform:scaleX(0);transform-origin:left;
 background:linear-gradient(90deg,var(--blc),transparent 85%);
 box-shadow:0 0 8px color-mix(in srgb,var(--blc) 45%,transparent);
 transition:transform .5s cubic-bezier(.2,.8,.2,1)}
.bl-post:hover p::after,.bl-post:focus-visible p::after{
 transform:scaleX(1)}

/* ============================================================
   3 · .bl-read underline draw + arrow nudge (featured card)
   The existing arrow nudge (blogfx.css:134 + blogfx2:38,
   translateX(4px), hover-only) is kept; this adds the
   underline draw and focus-visible mirrors of both.
   ============================================================ */
.bl-feat .bl-read{position:relative}
.bl-feat .bl-read::after{content:'';position:absolute;left:0;
 bottom:-5px;height:2px;width:100%;border-radius:2px;
 transform:scaleX(0);transform-origin:left;
 background:linear-gradient(90deg,var(--blc),transparent 85%);
 box-shadow:0 0 8px color-mix(in srgb,var(--blc) 40%,transparent);
 transition:transform .45s cubic-bezier(.2,.8,.2,1)}

/* ============================================================
   4 · art parallax — --px/--py (-1..1) from the same
   pointermove that drives the tilt. NON-COLLIDING with
   blogfx.css by design: parallax uses the independent
   `translate` property while the legacy chip/watermark hover
   rules (blogfx.css:186,200 = `transform`) keep working —
   both compose. The legacy transition lists are carried
   forward (same specificity, later source) and `translate`
   is added to them.
   ============================================================ */
.bl-art-chip{translate:calc(var(--px,0)*7px) calc(var(--py,0)*5px);
 transition:transform .28s cubic-bezier(.2,.8,.3,1.2),
   translate .18s ease-out}
.bl-art-bg{translate:calc(var(--px,0)*-12px) calc(var(--py,0)*-8px);
 transition:transform .4s ease,translate .18s ease-out}

/* ============================================================
   5 · art glow — conic sweep layer, JS-injected into .bl-art
   (posts only; the featured card already has the ifx-beam).
   Layering inside .bl-art (paint = DOM order): dots ::before →
   .bl-gen → watermark .bl-art-bg → .bl-art-chip → .bl-art-glow
   (last child = light layer sweeping OVER the art content) →
   ::after sheen. Clipped by .bl-art{overflow:hidden}.
   ============================================================ */
.bl-art-glow{position:absolute;inset:-60%;pointer-events:none;
 opacity:0;transition:opacity .4s;
 background:conic-gradient(from 0deg,transparent,
   color-mix(in srgb,var(--blc) 20%,transparent) 22%,transparent 45%,
   color-mix(in srgb,var(--blc) 12%,transparent) 70%,transparent)}
@keyframes bl3-spin{to{transform:rotate(1turn)}}

/* ============================================================
   6 · cat chip pop + press states
   ============================================================ */
.bl-cat{transition:scale .3s cubic-bezier(.2,.8,.3,1.5),
  background .25s,border-color .25s}
.bl-chip{transition:color .25s,border-color .25s,scale .16s ease}
.bl-chip:active{scale:.94}
.bl-post:active,.bl-feat:active{scale:.985}

/* ============================================================
   7 · ripple tint
   Ripple comes from the interact pack: initRipple
   (interact.js:174-189 / b-learn.js bundle) is a DOCUMENT-level
   delegated pointerdown that walks up from e.target and checks
   the classList at PRESS time — so .ifx-ripple added by
   blogfx3.js post-load is picked up (no per-element binding,
   no double ripple). Only the tint is declared here; host
   clipping (.card overflow:hidden) and the .55s burst are the
   interact pack's. Under RM the bundle skips init AND
   interact.css freezes the dot → inert, which is correct.
   ============================================================ */
.bl-post .ifx-ripple-dot,.bl-feat .ifx-ripple-dot{
 background:radial-gradient(circle,
   color-mix(in srgb,var(--blc) 40%,transparent) 0%,
   transparent 72%)}

/* ============================================================
   8 · generated cover art .bl-gen (JS-built, seeded by the
   post title; motif = seed%3 → constellation / noise rings /
   gradient mesh). Injected as the first child of .bl-art
   (featured: .bl-feat-art, viewBox 300 236). Base opacity 0 →
   .bl3-art-in fades to .9 in .6s (JS, own IO). Parallax uses
   the same --px/--py via the independent `translate`.
   ============================================================ */
.bl-gen{position:absolute;inset:0;width:100%;height:100%;
 opacity:0;pointer-events:none;
 translate:calc(var(--px,0)*4px) calc(var(--py,0)*3px);
 transition:opacity .6s ease,translate .18s ease-out}
.bl-gen.bl3-art-in{opacity:.9}
/* motif 0 — two desynced node groups (±4px, 7s/11s alt) */
.bl-gen-a{animation:bl3-drift var(--gd,7s) ease-in-out infinite alternate}
.bl-gen-b{animation:bl3-drift2 var(--gd,11s) ease-in-out infinite alternate}
@keyframes bl3-drift{from{transform:translate(-4px,-3px)}
 to{transform:translate(4px,3px)}}
@keyframes bl3-drift2{from{transform:translate(3px,4px)}
 to{transform:translate(-3px,-4px)}}
/* motif 1 — one seeded dasharray ring rotates (26-44s);
   fill-box origin = its own center → the dashes travel */
.bl-gen-rot{transform-box:fill-box;transform-origin:center;
 animation:bl3-rot var(--gd,32s) linear infinite}
@keyframes bl3-rot{to{transform:rotate(1turn)}}
/* motif 2 — blurred mesh circles, 3 shared drift keyframes,
   seeded durations + negative phase delays */
.bl-gen-m1{animation:bl3-m1 var(--gd,12s) ease-in-out infinite alternate}
.bl-gen-m2{animation:bl3-m2 var(--gd,12s) ease-in-out infinite alternate}
.bl-gen-m3{animation:bl3-m3 var(--gd,12s) ease-in-out infinite alternate}
@keyframes bl3-m1{from{transform:translate(-9px,-6px)}
 to{transform:translate(9px,6px)}}
@keyframes bl3-m2{from{transform:translate(7px,-8px)}
 to{transform:translate(-7px,8px)}}
@keyframes bl3-m3{from{transform:translate(-6px,9px)}
 to{transform:translate(6px,-9px)}}

/* ============================================================
   9 · search pill (JS-injected into .bl-bar before .bl-count)
   + .bl3-hide filter state (JS-toggled; transitions come from
   the §1 tilt-family transition list).
   ============================================================ */
.bl3-search{display:inline-flex;align-items:center;gap:8px;
 flex:none;padding:8px 14px;border-radius:999px;
 background:rgba(4,9,26,.6);border:1px solid var(--line);
 transition:border-color .25s,box-shadow .25s}
.bl3-search:focus-within{border-color:rgba(120,170,255,.5);
 box-shadow:0 0 0 3px rgba(47,107,255,.15)}
.bl3-search svg{width:14px;height:14px;color:var(--dim);flex:none}
.bl3-search input{background:none;border:0;outline:none;
 color:var(--mut);font:12.5px Inter,sans-serif;width:170px}
.bl3-search input::placeholder{color:var(--dim)}
.bl3-search input::-webkit-search-cancel-button{
 -webkit-appearance:none;appearance:none} /* custom × only */
.bl3-x{background:none;border:0;color:var(--dim);cursor:pointer;
 font-size:14px;padding:0 2px;line-height:1}
.bl3-x[hidden]{display:none}
.bl3-x:hover{color:#fff}
.bl3-hide{opacity:.18;scale:.97;pointer-events:none}

/* ============================================================
   10 · pointer-state FX — gated to fine pointers (the JS side
   double-gates with a (hover:hover)+(pointer:fine) guard;
   without it the vars below are never written → rules inert).
   Base styling (.bl-go chip, underline at scaleX(0), etc.)
   stays ungated, so mobile keeps the static tap affordance.
   ============================================================ */
@media (hover:hover) and (pointer:fine){
 /* arrow chip hover + focus mirror */
 .bl-post:hover .bl-go,.bl-post:focus-visible .bl-go{
  color:#fff;
  border-color:color-mix(in srgb,var(--blc) 55%,transparent);
  background:color-mix(in srgb,var(--blc) 16%,transparent);
  box-shadow:0 0 16px color-mix(in srgb,var(--blc) 25%,transparent)}
 /* arrow double-sweep: first exits right, duplicate enters
    from the left .04s later; both retract on un-hover */
 .bl-post:hover .bl-go svg:first-child,
 .bl-post:focus-visible .bl-go svg:first-child{
  transform:translateX(220%)}
 .bl-post:hover .bl-go svg:last-child,
 .bl-post:focus-visible .bl-go svg:last-child{
  transform:translate(0,0);transition-delay:.04s}
 /* cat chip pop */
 .bl-post:hover .bl-cat,.bl-post:focus-visible .bl-cat{
  scale:1.07;
  background:color-mix(in srgb,var(--blc) 20%,transparent);
  border-color:color-mix(in srgb,var(--blc) 50%,transparent)}
 /* art glow sweep */
 .bl-post:hover .bl-art-glow{opacity:1;
  animation:bl3-spin 9s linear infinite}
 /* featured: underline draw + arrow nudge (nudge kept from
    blogfx/blogfx2, focus-visible mirrored) */
 .bl-feat:hover .bl-read::after,
 .bl-feat:focus-visible .bl-read::after{transform:scaleX(1)}
 .bl-feat:hover .bl-read svg,
 .bl-feat:focus-visible .bl-read svg{transform:translateX(4px)}
}

/* ============================================================
   11 · mobile ≤860px — tilt + parallax off (JS stops writing
   the vars; the family below re-pins them to 0 as the CSS-side
   belt), glow off, search narrower. .bl-go stays as the tap
   affordance; press scales and search keep working.
   ============================================================ */
@media (max-width:860px){
 a.bl-post.bl3-tilt,a.bl-feat.bl3-tilt{
  --tx:0deg;--ty:0deg;--lift:0px;--px:0;--py:0}
 .bl-art-glow{display:none}
 .bl3-search input{width:140px}
}
@media (max-width:680px){
 .bl3-search input{width:120px} /* chips scroll-strip breathing room */
}

/* ============================================================
   12 · MASTER prefers-reduced-motion static block (LAST in
   this file on purpose — it must also beat this file's own
   later-source rules, e.g. the §10 hover states, and the RM
   block in blogfx2.css (L31-34) which loses to its own L37-38
   transition rules). Everything jumps between static end
   states: drift/spin/glow/rot/mesh none, generated art stays
   visible (static), no glow, .bl-go/arrows/cat/underline/chip
   transitions none, search filter works with no animation
   (.bl3-hide snaps — hide classes are fine).
   ============================================================ */
@media (prefers-reduced-motion:reduce){
 .bl-go{transition:none}
 .bl-go.bl3-go-in{opacity:1;transform:none}
 .bl-go svg{transition:none}
 .bl-post p::after{transition:none}
 .bl-post:hover p::after,.bl-post:focus-visible p::after{
  transform:scaleX(1)}
 .bl-feat .bl-read::after{transition:none}
 .bl-feat .bl-read svg,.bl-feat:hover .bl-read svg,
 .bl-feat:focus-visible .bl-read svg{transition:none}
 .bl-feat:hover .bl-read::after,
 .bl-feat:focus-visible .bl-read::after{transform:scaleX(1)}
 .bl-cat{transition:none}
 .bl-chip{transition:none}
 .bl-art-chip,.bl-art-bg{transition:none}
 .bl-gen{transition:none}
 .bl-gen-a,.bl-gen-b,.bl-gen-rot,
 .bl-gen-m1,.bl-gen-m2,.bl-gen-m3{animation:none}
 .bl-art-glow{transition:none}
 .bl-post:hover .bl-art-glow{animation:none;opacity:0}
 .bl3-search{transition:none}
 a.bl-post.bl3-tilt,a.bl-feat.bl3-tilt,
 a.bl-post.bl3-tilt.bl3-live,a.bl-feat.bl3-tilt.bl3-live{
  transition:none}
}