/* mws-site-kit v0.2.0 | patterns.css
   ==========================================================================
   The reusable structural and motion patterns.
   Depends on tokens.css + base.css. Pairs with patterns.js.

   Four things live here:
     1. Torn edge   - the structural signature
     2. Reveal      - fade/rise on scroll
     3. Card entrance - the gravity "slam"
     4. Mode hooks  - featured item, weather canvas
   ========================================================================== */


/* ==========================================================================
   1. TORN EDGE

   A band with a ragged bottom edge. The mechanism is generic; the SVG path
   is generated per site by tear.js (see js/tear.js), so every client gets a
   different edge from the same six lines of CSS.

   Usage:
     <section class="band band--tear">
       ...content...
       <div class="tear" data-tear="torn" style="--tear-fill:var(--paper)"></div>
     </section>

   --tear-fill must be the colour of the section BELOW, because the shape is
   cut out of the next section and laid over this one. Getting this backwards
   is the single most common mistake with this pattern.
   ========================================================================== */
.band{position:relative}

/* Reserve room so the edge never eats the band's own content. */
.band--tear{padding-bottom:calc(var(--sp-6) + var(--tear-h))}

.tear{
  position:absolute;left:0;right:0;bottom:-1px;   /* -1px kills the hairline
                                                     seam that subpixel
                                                     rounding leaves at some
                                                     zoom levels */
  height:var(--tear-h);
  line-height:0;
  pointer-events:none;
}
.tear svg{width:100%;height:100%;display:block}
.tear path{fill:var(--tear-fill,var(--paper))}

/* Mirror the same generated edge for visual variety without a second path. */
.tear--flip svg{transform:scaleX(-1)}


/* ==========================================================================
   2. REVEAL ON SCROLL

   Add data-rv to anything that should fade and rise into view.
   Add data-rv-d="1".."6" to stagger siblings.

     <p class="eyebrow" data-rv>Troy, Ohio</p>
     <h1 data-rv data-rv-d="1">If it's in the way, it's gone.</h1>

   patterns.js adds .in when the element intersects.

   REVEALS ARE VISIBLE BY DEFAULT, and the hidden state is applied rather
   than released - the same inversion the card entrance got in v0.2.0, for
   the same reason. In v0.1.0 this block hid every [data-rv] from CSS and
   waited for JS to let it go, so a blocked or 404ing patterns.js left the
   content invisible forever. That is the defect that shipped twice with
   cards; reveals only escaped it because no real build used them.

   THE ARMING IS DIFFERENT FROM THE CARD'S, and it has to be. Cards sit
   below the fold, so patterns.js can hide them at DOMContentLoaded without
   anyone seeing it. Reveals are for hero copy at the top of the page, where
   hiding after first paint would flash the text in and straight back out.
   So the arming is done by a tiny inline script in <head>, which runs
   before the first paint:

     <script>...adds .rv-armed to <html>, removes it if the kit
     never loads...</script>

   build-site.mjs emits it. See RV_ARM in tools/build-site.mjs for the exact
   source and the failsafe. Three failure modes, all safe:

     inline script never runs  -> nothing is ever hidden
     inline runs, kit 404s     -> failsafe un-arms after 1200ms
     both run                  -> normal reveal

   Never write .rv-armed into a page by hand: it is a promise that the kit
   is loaded, and the inline script is what makes that promise true.
   ========================================================================== */
html.rv-armed [data-rv]{
  opacity:0;
  transform:translate3d(0,26px,0);
  transition:opacity .55s var(--ease-out),
             transform .55s var(--ease-out);
}
html.rv-armed [data-rv].in{opacity:1;transform:none}

[data-rv-d="1"]{transition-delay:70ms}
[data-rv-d="2"]{transition-delay:140ms}
[data-rv-d="3"]{transition-delay:210ms}
[data-rv-d="4"]{transition-delay:280ms}
[data-rv-d="5"]{transition-delay:350ms}
[data-rv-d="6"]{transition-delay:420ms}

@media(prefers-reduced-motion:reduce){
  /* Matches the arming selector exactly. Unscoped this is (0,1,0) against
     the arming rule's (0,2,0) and loses, which would leave reduced-motion
     users staring at hidden text - the same specificity trap the card's
     .landed rule hit. */
  html.rv-armed [data-rv]{opacity:1;transform:none}
}


/* ==========================================================================
   3. CARD ENTRANCE - "the slam"

   Cards fall under gravity, squash on impact, rebound, and settle. The
   per-keyframe easing is what makes it read as WEIGHT rather than as a
   fade with extra steps - accelerate in, decelerate out of the bounce.

   Tunable per site, on the grid or on an individual card:
     --slam-drop      how far above its resting place the card starts
     --slam-dur       total duration
     --tilt           per-card rotation while falling (set inline, varied)
     --d              start delay (patterns.js sets this per column)
   ========================================================================== */
.card{
  --slam-drop:-230px;
  --slam-dur:.95s;

  position:relative;
  background:var(--white);
  border:1px solid var(--ink-100);
  display:flex;flex-direction:column;
  transition:border-color .22s,box-shadow .26s var(--ease-out);
}

/* THE HIDDEN STATE IS APPLIED BY JAVASCRIPT, NOT RELEASED BY IT.

   patterns.js adds .slam-armed to a [data-slam] container, and it does so
   only after it has decided it is genuinely going to animate - i.e. not
   under reduced motion and not without IntersectionObserver. So if the
   script is blocked, fails, or never loads, this rule never matches and
   the cards are simply visible.

   This is the inverse of how the pattern shipped in v0.1.0, where .card
   itself carried opacity:0 and JS released it. That contract required a
   <noscript> fallback, a data-slam wrapper around every card including a
   lone one, and produced a blank gap with no error in the console when any
   of it was missed. It caused the same production bug twice. A card outside
   a [data-slam] container is now just a card that does not animate. */
[data-slam].slam-armed .card{
  opacity:0;
  transform:translate3d(0,var(--slam-drop),0) rotate(var(--tilt,0deg)) scale(1.06);
}
.card:hover{
  border-color:var(--brand-400);
  box-shadow:0 14px 34px rgba(20,23,11,.10);
}

.card.slam{
  animation:slam var(--slam-dur) var(--d,0ms) forwards;
  will-change:transform,opacity;
}

/* THE CLEANUP, AND IT IS LOAD-BEARING. Once a card has landed, drop the
   animation, the will-change hint and the blurred dust child.

   This is not tidiness. Leaving several animated composited layers alive
   causes stale paint tiles in Chromium - cards render half-drawn until
   something forces a repaint. Found once on the Limitless build; kept here
   so it is never rediscovered per site. patterns.js adds .landed on
   animationend. Do not remove either half. */
.card.landed{
  animation:none;
  will-change:auto;
}
.card.landed .card__dust{display:none}

/* Return a landed card to its resting appearance. Scoped to match the
   arming rule above: .card.landed alone is specificity (0,2,0) and loses to
   [data-slam].slam-armed .card at (0,2,1), which would leave every card
   invisible at the exact moment it finished falling. */
[data-slam].slam-armed .card.landed{
  opacity:1;
  transform:none;
}

@keyframes slam{
  0%{
    opacity:0;
    transform:translate3d(0,var(--slam-drop),0) rotate(var(--tilt,0deg)) scale(1.06);
    animation-timing-function:cubic-bezier(.5,0,.75,.2);   /* accelerate: gravity */
  }
  14%{
    opacity:1;
    transform:translate3d(0,calc(var(--slam-drop) * .83),0) rotate(calc(var(--tilt,0deg) * .85)) scale(1.05);
    animation-timing-function:cubic-bezier(.5,0,.75,.2);
  }
  56%{                                                      /* touchdown */
    opacity:1;
    transform:translate3d(0,0,0) rotate(calc(var(--tilt,0deg) * .12)) scale(1);
    animation-timing-function:cubic-bezier(.2,.8,.3,1);
  }
  64%{                                                      /* squash */
    transform:translate3d(0,9px,0) rotate(0deg) scale(1.025,.955);
    animation-timing-function:cubic-bezier(.3,.7,.4,1);
  }
  76%{                                                      /* rebound + stretch */
    transform:translate3d(0,-8px,0) scale(.99,1.018);
  }
  87%{transform:translate3d(0,2.5px,0) scale(1.004,.995)}
  94%{transform:translate3d(0,-1px,0) scale(1)}
  100%{opacity:1;transform:none}
}

/* Dust kicks out at the moment of touchdown. Optional child:
     <span class="card__dust" aria-hidden="true"></span> */
.card__dust{
  position:absolute;left:4%;right:4%;bottom:-11px;height:22px;
  border-radius:50%;
  background:rgba(20,23,11,.30);
  filter:blur(9px);
  opacity:0;transform:scaleX(.35);
  pointer-events:none;
}
.card.slam .card__dust{animation:thud var(--slam-dur) var(--d,0ms) forwards}

@keyframes thud{
  0%,50%{opacity:0;transform:scaleX(.3) scaleY(.6)}
  58%   {opacity:.85;transform:scaleX(1.14) scaleY(1.1)}
  72%   {opacity:.45;transform:scaleX(1.02) scaleY(.9)}
  100%  {opacity:0;transform:scaleX(.92) scaleY(.8)}
}

/* If motion is reduced, cards are simply present. No fall, no dust. */
@media(prefers-reduced-motion:reduce){
  /* Belt and braces. patterns.js already refuses to arm a grid under
     reduced motion, so .slam-armed should never appear here at all. This
     matches the arming selector exactly so that if it somehow does - a
     preference toggled after load, a future caller arming by hand - source
     order puts the cards back rather than leaving them hidden. */
  [data-slam].slam-armed .card{opacity:1;transform:none}
  .card__dust{display:none}
}

/* ---------- minimal card shell ----------
   Enough structure for the entrance to have something to animate. Style
   the interior per site; these are sane defaults, not a design system. */
.card__media{
  position:relative;aspect-ratio:4/3;overflow:hidden;
  background:var(--brand-050);
  border-bottom:1px solid var(--ink-100);
}
.card__media img{
  width:100%;height:100%;object-fit:cover;
  transition:transform .6s var(--ease-out);
}
.card:hover .card__media img{transform:scale(1.045)}

.card__body{padding:clamp(1.1rem,2.2vw,1.5rem);flex:1}
.card h3{margin-bottom:.45rem}
.card p{font-size:var(--step--1);color:var(--ink-500);margin:0}

/* Accent rule that wipes across on hover. */
.card__rule{height:3px;background:var(--accent);width:0;transition:width .34s var(--ease-out)}
.card:hover .card__rule{width:100%}


/* ==========================================================================
   4. MODE HOOKS

   A "featured" item, promoted by the current mode. Pure CSS ordering, so
   promoting a card is one class and needs no DOM reshuffling.
   ========================================================================== */
.card{order:0}
.card.is-feat{
  order:-1;
  border-color:var(--accent);
  box-shadow:0 10px 30px rgba(20,23,11,.10);
}
.card.is-feat .card__rule{width:100%}

.card__flag{
  position:absolute;top:10px;right:10px;z-index:3;
  background:var(--accent);color:var(--ink-950);
  font:700 .68rem/1 var(--font-util);
  letter-spacing:.14em;text-transform:uppercase;
  padding:.4rem .6rem;border-radius:2px;
  opacity:0;transform:translateY(-6px);
  transition:opacity .35s var(--ease-out),transform .35s var(--ease-out);
}
.card.is-feat .card__flag{opacity:1;transform:none}

/* ---------- weather canvas ----------
   Positioning only. The particle system is js/weather.js and is OFF unless
   a site explicitly initialises it. z-index sits above content but below
   the header, and it never takes pointer events. */
.wx-canvas{
  position:fixed;inset:0;
  width:100%;height:100%;
  z-index:40;
  pointer-events:none;
  opacity:0;
  transition:opacity .8s ease;
}
.wx-canvas.on{opacity:1}
