/*
 * Breadwinner v3 floating objects (Step 8 move 5): bread-slice and pack-sticker cutouts,
 * plus an optional wrap for the existing ingredient marks, that settle into place once and
 * then (full tier only) idle-bob and nudge toward the pointer.
 *
 * Owned by F1 (docs/redesign-v3/steps/step8-motion-spec.md). Never edits v3.css,
 * v3-motion.js, home.css, process.css, products.css, ink.css, doodles.css or any page HTML.
 * Reuses existing tokens (--ease-quart, --ease-expo, --shadow-held) and the EXISTING shared
 * reveal latch ([data-once]/.is-waiting/.is-seen, set by v3-motion.js). Adds no
 * IntersectionObserver and no scroll listener -- the idle bob is a plain CSS keyframe, not
 * scroll-linked (the fresh capture in step8-motion-spec.md found the reference's jitter is
 * continuous and proximity-independent once a bill is visible, not scroll-proportional).
 *
 * Markup F2 places (copy verbatim; invents no float CSS -- only these classes/attrs):
 *
 *   <div class="float float--slice" data-once aria-hidden="true" data-depth="0.04"
 *        style="--float-size:clamp(4rem,9vw,7rem); --float-rot:8deg; top:4%; right:6%;">
 *     <span class="float__drift"></span>
 *   </div>
 *
 * Swap float--slice for float--sticker-round or float--sticker-inside. Position each
 * instance with plain inline top/left/right/bottom (same one-off discipline the existing
 * .float-field ingredients already use in their own page CSS -- this file owns no layout
 * grid). --float-rot: one fixed value per instance, -18deg to +18deg (Things on a Table).
 * --float-size: clamp(2.5rem,6vw,4.5rem) for a sprinkle prop, clamp(4rem,9vw,7rem) for the
 * one hero-scale prop per page. data-depth: 0.02-0.06, read by float.js for the full-tier
 * pointer parallax only (ignored on every other tier).
 *
 * To give an existing .ingredient mark the same reveal/bob/parallax (optional, additive,
 * never replaces .float-field/.ingredient-row, which stay exactly as they are):
 *
 *   <div class="float float--ingredient" data-once aria-hidden="true" data-depth="0.03"
 *        style="--float-rot:-6deg; top:10%; left:20%;">
 *     <span class="float__drift"><span class="ingredient ingredient--wheat"></span></span>
 *   </div>
 *
 * Include lines (exact, per the contract):
 *   <link rel="stylesheet" href="/v3/float.css">      in <head>, after v3.css
 *   <script src="/v3/float.js" defer></script>        end of body, after v3-page.js
 */

/* ---- Base: sizing + the reveal. Only translate, scale and opacity animate here (DESIGN.md
   Motion's animatable-properties list) -- rotate is a fixed, non-transitioning tilt. ---- */
/* A float is positioned against its host. Hosts that set no position of their own become the containing
   block here; :where() keeps this at zero specificity, so a sticky or absolute host keeps its own rule. */
:where(:has(> .float)) { position: relative; }

.float {
  position: absolute;
  width: var(--float-size, 4rem);
  aspect-ratio: 1;
  pointer-events: none;
  rotate: var(--float-rot, 0deg);
  translate: 0 0;
  scale: 1;
  opacity: 1;
}
.float[data-once] {
  transition:
    translate 600ms var(--ease-expo),
    scale 600ms var(--ease-expo),
    opacity 600ms var(--ease-expo);
}
.float[data-once].is-waiting {
  translate: 0 0.75rem;   /* 12px */
  scale: 0.96;
  opacity: 0;
  transition: none;
}

/* ---- Hidden entirely under none, lean and no-JS -- zero bytes, zero layout, same rule the
   existing .ingredient-row already follows. Only lite and full ever show a float. ---- */
html:not([data-motion="lite"]):not([data-motion="full"]) .float { display: none; }

/* ---- The inner layer paints the picture and carries the idle bob + pointer parallax, kept
   on a separate element so neither ever competes with the outer reveal's `translate`. ---- */
.float__drift {
  display: block;
  position: absolute;
  inset: 0;
  background-repeat: no-repeat;
  background-position: center;
  background-size: contain;
}

/* ---- The three photographic props. CSS background only under lite/full, so none/lean/no-JS
   never fetch them (the exact trick img/ingredients.svg already uses). Aspect ratios are the
   trimmed source photos' own -- nothing stretches. Filter is DESIGN.md's Held Object shadow,
   already themed for day/night by --shadow-held; it never changes under reduced motion. ---- */
.float--slice { aspect-ratio: 448 / 464; }
.float--sticker-round { aspect-ratio: 1 / 1; }
.float--sticker-inside { aspect-ratio: 1 / 1; }
.float--pack { aspect-ratio: 448 / 856; }
html:is([data-motion="lite"], [data-motion="full"]) .float--slice > .float__drift {
  background-image: url(media/float/v2/slice.webp);
}
html:is([data-motion="lite"], [data-motion="full"]) .float--sticker-round > .float__drift {
  background-image: url(media/float/v2/sticker-round.webp);
}
html:is([data-motion="lite"], [data-motion="full"]) .float--pack > .float__drift {
  background-image: url(media/float/v2/pack-standing.webp);
}
html:is([data-motion="lite"], [data-motion="full"]) .float--sticker-inside > .float__drift {
  background-image: url(media/float/v2/sticker-inside.webp);
}
.float--slice > .float__drift,
.float--sticker-round > .float__drift,
.float--sticker-inside > .float__drift {
  filter: var(--shadow-held);
}

/* ---- Optional wrap around an existing .ingredient mark: no size or background of its own,
   the nested .ingredient child keeps painting itself exactly as it already does; this class
   only adds the reveal/bob/parallax on top. ---- */
.float--ingredient { width: auto; aspect-ratio: auto; }
.float--ingredient > .float__drift { position: static; inset: auto; }

/* ---- Idle bob: full tier, continuous and NOT scroll-linked. Gated on :not(.is-waiting)
   rather than .is-seen, because v3-motion.js only ever adds is-waiting/is-seen to a
   [data-once] element that starts BELOW the fold (CONTRACT.md 2; its own querySelectorAll
   skips anything already in view at load) -- a hero-scale float placed above the fold would
   otherwise never reach .is-seen and would never bob. :not(.is-waiting) covers both: already-
   visible floats bob immediately, below-fold floats wait and then bob once revealed. Lives on
   `translate` of the INNER layer, so it can never fight the outer reveal's own `translate`.
   Durations vary 4200-5600ms by prop (step8-motion-spec.md's range) so instances don't bob in
   lockstep; a small negative delay on two of them offsets phase further. ---- */
@keyframes float-bob {
  0%, 100% { translate: 0 0; }
  50% { translate: 0 -0.5rem; }  /* 8px */
}
html[data-motion="full"] .float:not(.is-waiting) > .float__drift {
  animation: float-bob 4800ms ease-in-out infinite;
}
html[data-motion="full"] .float--slice:not(.is-waiting) > .float__drift { animation-duration: 4200ms; }
html[data-motion="full"] .float--sticker-round:not(.is-waiting) > .float__drift { animation-duration: 4900ms; animation-delay: -800ms; }
html[data-motion="full"] .float--sticker-inside:not(.is-waiting) > .float__drift { animation-duration: 5600ms; animation-delay: -1400ms; }
html[data-motion="full"] .float--pack:not(.is-waiting) > .float__drift { animation-duration: 6300ms; animation-delay: -2100ms; }
html[data-motion="full"] .float--ingredient:not(.is-waiting) > .float__drift { animation-duration: 5200ms; animation-delay: -2600ms; }

/* ---- Pointer parallax: float.js writes `transform` (a different property from the bob's
   `translate`, so the two compose instead of competing) on this same inner layer, full tier
   only. The transition is the whole "snap back" -- float.js never animates, it just retargets
   a new value every rAF while the pointer moves, and this easing takes over the instant it
   stops (contract: anything under 300ms uses --ease-quart). ---- */
html[data-motion="full"] .float__drift { transition: transform 240ms var(--ease-quart); }

@media print {
  .float { display: none !important; }
}

/* Phones: no floats. Their positions are set for a wide page; on a narrow one they land on text
   and push the page wider than the screen (the reference keeps one calm object at the edge, we keep none). */
@media (max-width: 899px) { .float:not([data-phone]) { display: none !important; } }
