/* ============================================================================
 * effects.css — the CSS half of the effects library (effects.js is the registry).
 *
 * Effects read their params from --fx-* custom properties the runtime writes per
 * element. A "reveal" element starts hidden ([data-fx] with .fx-armed) and plays
 * when the runtime adds .fx-in on scroll. Everything respects reduced-motion.
 *
 * ⚠ CASCADE RULE (the 08-17 "blur-in doesn't animate" bug): the per-effect armed
 * poses below come AFTER the generic .fx-in rule and carry the SAME specificity,
 * so without a guard they beat it and the element never resolves — which is
 * exactly why blur-in sat permanently blurred and fade-up sat permanently 22px
 * low (only opacity animated, because that pair happens to be ordered the right
 * way round). Every armed pose is therefore written `.fx-armed:not(.fx-in)`.
 * Keep that guard on anything new.
 * ========================================================================== */

/* Reveal base: armed = pre-animation state; .fx-in = played. Only elements the
   runtime armed are hidden, so a no-JS / no-content.json page shows everything. */
[data-fx].fx-armed:not(.fx-in) { opacity: 0; }
[data-fx].fx-armed { will-change: opacity, transform, filter, clip-path; }
[data-fx].fx-in {
  opacity: 1; transform: none; filter: none; clip-path: none;
  transition: opacity var(--fx-dur, 700ms) var(--fx-ease, cubic-bezier(.22,.8,.3,1)),
              transform var(--fx-dur, 700ms) var(--fx-ease, cubic-bezier(.22,.8,.3,1)),
              filter var(--fx-dur, 700ms) var(--fx-ease, cubic-bezier(.22,.8,.3,1)),
              clip-path var(--fx-dur, 700ms) var(--fx-ease, cubic-bezier(.22,.8,.3,1));
  transition-delay: var(--fx-delay, 0ms);
}

/* Per-effect armed states (the "from" pose). */
[data-fx="fade-up"].fx-armed:not(.fx-in)     { transform: translateY(var(--fx-dist, 22px)); }
[data-fx="slide-left"].fx-armed:not(.fx-in)  { transform: translateX(var(--fx-dist, 48px)); }
[data-fx="slide-right"].fx-armed:not(.fx-in) { transform: translateX(calc(-1 * var(--fx-dist, 48px))); }
[data-fx="pop-in"].fx-armed:not(.fx-in)      { transform: scale(calc(var(--fx-scale, 88%) / 100)); }
[data-fx="blur-in"].fx-armed:not(.fx-in)     { filter: blur(var(--fx-blur, 14px)); }

/* Fade to blur — soft + slightly pushed in, resolving to sharp. Hero-scale entrance. */
[data-fx="fade-to-blur"].fx-armed:not(.fx-in) {
  filter: blur(var(--fx-blur, 24px));
  transform: scale(calc(var(--fx-scale, 108%) / 100));
}

/* Reveal wipe — direction is a clip-path inset chosen by --fx-dir (l/r/u/d). */
[data-fx="mask-wipe"].fx-armed:not(.fx-in)                    { clip-path: inset(0 100% 0 0); }
[data-fx="mask-wipe"][data-fx-dir="r"].fx-armed:not(.fx-in)   { clip-path: inset(0 0 0 100%); }
[data-fx="mask-wipe"][data-fx-dir="u"].fx-armed:not(.fx-in)   { clip-path: inset(100% 0 0 0); }
[data-fx="mask-wipe"][data-fx-dir="d"].fx-armed:not(.fx-in)   { clip-path: inset(0 0 100% 0); }
[data-fx="mask-wipe"].fx-in                                   { clip-path: inset(0 0 0 0); }

/* BG effects — the runtime drives parallax via transform in JS; here's the zoom. */
.fx-ken-burns { animation: fx-kenburns var(--fx-dur, 60s) var(--fx-ease, ease-in-out) infinite alternate; }
@keyframes fx-kenburns { from { transform: scale(1); } to { transform: scale(calc(var(--fx-zoom, 112%) / 100)); } }

/* ---- HOVER: lift & tilt --------------------------------------------------
   The runtime writes --fx-rx / --fx-ry from the pointer position, so the tilt
   follows the cursor instead of being a fixed canned rotation. With JS off (or
   reduced motion) the :hover rule alone still gives the lift + shadow, which is
   the part that reads as "the card came toward me".                          */
.fx-tilt-hover {
  transform-style: preserve-3d;
  transition: transform var(--fx-dur, 260ms) cubic-bezier(.22,.8,.3,1),
              box-shadow var(--fx-dur, 260ms) ease;
  will-change: transform;
}
/* Hover state comes from JS (.fx-hovered) whenever the runtime has wired this
   element (.fx-js). CSS :hover CANNOT drive a transform that MOVES the element:
   the card grows out from under the cursor, :hover drops, the transform reverts,
   the cursor is over it again — measured at 22 enter/leave cycles per second.
   The runtime holds the element's pre-transform layout rect and decides hover
   against that, so the hit area never moves. Plain :hover stays as the no-JS path. */
.fx-tilt-hover.fx-hovered,
.fx-tilt-hover:not(.fx-js):hover {
  transform: perspective(900px)
             translateY(calc(-1 * var(--fx-lift, 8px)))
             rotateX(var(--fx-rx, 0deg))
             rotateY(var(--fx-ry, 0deg))
             scale(calc(var(--fx-scale, 102%) / 100));
  box-shadow:
    0 calc(var(--fx-lift, 8px) * 1.2) calc(var(--fx-lift, 8px) * 3) rgba(0,0,0, calc(var(--fx-shadow, 55) / 140)),
    0 calc(var(--fx-lift, 8px) * 0.3) calc(var(--fx-lift, 8px) * 1) rgba(0,0,0, calc(var(--fx-shadow, 55) / 260));
}
/* While the pointer is actually tracking, drop the transition so the tilt is
   1:1 with the cursor rather than lagging a quarter second behind it. */
.fx-tilt-hover.fx-tracking { transition: box-shadow var(--fx-dur, 260ms) ease; }

.fx-glow-pulse { animation: fx-glow var(--fx-speed, 2.6s) ease-in-out infinite; }
@keyframes fx-glow {
  0%,100% { box-shadow: 0 0 0 0 hsla(var(--fx-hue,265) 70% 60% / 0); }
  50%     { box-shadow: 0 0 var(--fx-size, 26px) 2px hsla(var(--fx-hue,265) 70% 60% / .55); }
}

/* ---- AMBIENT: falling blossom -------------------------------------------
   PORTED VERBATIM from the demo Eric sent Jackson (the nature design pass) and
   then parameterised. What makes this one work and the old CMS version not:

   1. A real petal SILHOUETTE as an SVG data-URI, with the notched tip. The old
      version was the "✿" glyph, which reads as a dingbat, not blossom.
   2. Depth classes. `.near` petals are large and slightly out of focus, `.far`
      ones small, sharp and dimmer — parallax-by-focus is what sells them as
      falling THROUGH the scene rather than being stuck to the glass.
   3. Rotation on TWO axes (rotate3d(1,1,.2,...)) so they flutter edge-on
      instead of spinning like a coin.
   4. Fall and sway are SEPARATE animations with unrelated durations per petal,
      so the field never visibly loops.
   No dark drop-shadow on purpose: at partial opacity over a dark base a shadow
   composites the pink down into brown and the petal reads as dirt on the lens. */
/* NOTE: no `position` rule on .fx-petals-host. effects.css loads after the page's
   own <style>, so a blanket `position:relative` here BEAT the page's
   `.petals{position:absolute;inset:0}` — the host collapsed to zero height and the
   whole field rendered off in the corner. The runtime sets position:relative
   inline, and only when the host actually computes to static. */
.fx-petal-layer { position: absolute; inset: 0; z-index: 1; pointer-events: none; overflow: hidden; }
.fx-petal {
  position: absolute; top: -8vh; opacity: 0; will-change: transform, opacity;
  background: no-repeat center/contain
    url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path d="M16 31C5 26 1 17 5 9 8 3 12.5 1 16 6c3.5-5 8-3 11 3 4 8 0 17-11 22z" fill="%23eab8c9"/><path d="M16 31c-3-6-4-13-2-21 1.2-4 1.6-4 2-4 .4 0 .8 0 2 4 2 8 1 15-2 21z" fill="%23c9849c" opacity=".55"/></svg>');
  animation: fx-petal-fall linear infinite, fx-petal-sway ease-in-out infinite alternate;
}
.fx-petal.near { filter: blur(1.4px) saturate(1.15); }
.fx-petal.far  { animation-name: fx-petal-fall-far, fx-petal-sway; }
@keyframes fx-petal-fall {
  0%   { transform: translate3d(0,-10vh,0) rotate3d(1,1,.2,0deg);   opacity: 0; }
  9%   { opacity: calc(var(--fx-opacity, 95) / 100); }
  86%  { opacity: calc(var(--fx-opacity, 95) / 100 * .9); }
  100% { transform: translate3d(0,110vh,0) rotate3d(1,1,.2,900deg); opacity: 0; }
}
@keyframes fx-petal-fall-far {
  0%   { transform: translate3d(0,-10vh,0) rotate3d(1,1,.2,0deg);   opacity: 0; }
  9%   { opacity: calc(var(--fx-opacity, 95) / 100 * .72); }
  86%  { opacity: calc(var(--fx-opacity, 95) / 100 * .62); }
  100% { transform: translate3d(0,110vh,0) rotate3d(1,1,.2,720deg); opacity: 0; }
}
@keyframes fx-petal-sway {
  from { margin-left: calc(-1 * var(--fx-sway, 34px)); }
  to   { margin-left: var(--fx-sway, 34px); }
}

/* ---- AMBIENT: blossom branch --------------------------------------------
   Tunes the branch artwork already in the page (.branch-deco > .branch-svg).
   Declared after the page's own <style> block so these win the specificity tie. */
.fx-branch-sway { opacity: calc(var(--fx-opacity, 85) / 100); }
.fx-branch-sway > .branch-svg,
.fx-branch-sway > svg {
  transform-origin: 0% 0%;
  animation: fx-branch-sway var(--fx-speed, 11s) ease-in-out infinite alternate;
}
@keyframes fx-branch-sway {
  from { transform: scale(calc(var(--fx-scale, 100%) / 100)) rotate(calc(-1 * var(--fx-sway, .6deg))); }
  to   { transform: scale(calc(var(--fx-scale, 100%) / 100)) rotate(var(--fx-sway, .6deg)); }
}

/* ---- EDITOR SELECTION MODE ----------------------------------------------
   Only active while the editor turns it on (html.cms-select). Every editable
   element gets a dashed hover outline; clicking one opens its controls in a
   modal. Nothing here ships to a visitor — the class is never set on the live
   page. See runtime.js `setSelectMode`.                                      */
html.cms-select [data-cms] { cursor: pointer; }
html.cms-select [data-cms]:hover {
  outline: 2px dashed #8a5fd0; outline-offset: 3px;
  background-color: rgba(138,95,208,.08);
}
html.cms-select [data-cms].cms-picked {
  outline: 2px solid #eab8c9; outline-offset: 3px;
  box-shadow: 0 0 0 6px rgba(234,184,201,.16);
}

@media (prefers-reduced-motion: reduce) {
  [data-fx].fx-armed:not(.fx-in) { opacity: 1 !important; transform: none !important; filter: none !important; clip-path: none !important; }
  .fx-ken-burns, .fx-glow-pulse, .fx-petal, .fx-branch-sway > svg { animation: none !important; }
  .fx-petal-layer { display: none; }
  .fx-tilt-hover.fx-hovered, .fx-tilt-hover:hover { transform: none; }
}
