Files
Samraaj BathandClaude Fable 5 199359d0a6 Wave 4: reveal settling + replay, 206/magic-byte media fix, hidden-geometry banding, bare-zero utility gating
Capture (4a):
- stabilize: dwell-scroll settling pass + known-library pre-reveal
  neutralization after lazy promotion, so every viewport snapshot records
  the post-reveal steady state (about-page sections no longer captured
  blanket-hidden: 24 base `invisible` wrappers -> 0)
- probeReveals: extended to the visibility + entrance-animation family;
  DittoMotion replays them (JS re-hide on mount, IntersectionObserver
  reveal, 4s force-reveal failsafe) so SSR/non-JS still shows content
- assets: 206 range-response bodies are no longer stored as full assets
  (full-fetch fallback) + video magic-byte validation - the about-page
  hero video is now a valid 6.0MB ftyp mp4 instead of 18.9KB of garbage

Generate (4b):
- css: hidden-node geometry policy - a visibility:hidden box with a 0x0
  captured bbox at a band becomes display:none there; an OCCUPYING hidden
  box gets the captured per-viewport geometry instead of the baked
  canonical one. Also covers the ancestor-hidden-at-base case (the
  cropin.com/cotton slider arrow: hide inherited from an elementor-widget
  ancestor at every width parked a desktop left:548px box inside a 375px
  viewport -> body.scrollWidth 585; now 375 on both verified pages)
- tailwind: bare-zero utility gating - font-size:0 -> text-[0px] and
  letter-spacing:0 -> tracking-[0px] (text-0/tracking-0 are silently
  invalid in Tailwind v4; fixes permanently-visible map labels on the
  cotton Global-presence section)

Verified: 87/87 compiler tests green, full-workspace suite green,
regenerated cropin.com + /cotton/ builds measure 375px scrollWidth at a
375px viewport, and a fresh /about/ clone renders all reveal sections
with scroll-replay against the live site.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 18:33:10 -07:00

50 lines
1.9 KiB
HTML

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Reveal fixture</title>
<style>
body { margin: 0; font-family: sans-serif; }
.spacer { height: 2200px; background: linear-gradient(#eee, #ddd); }
/* Elementor pattern: content wrappers hidden at load, revealed by a waypoint class
swap that applies an entrance keyframe animation. */
.elementor-invisible { visibility: hidden; }
@keyframes fadeInUp {
from { opacity: 0; transform: translate3d(0, 40px, 0); }
to { opacity: 1; transform: none; }
}
.animated { animation-duration: 0.4s; animation-fill-mode: both; }
.fadeInUp { animation-name: fadeInUp; }
.reveal-box { height: 220px; background: #2e8b57; color: #fff; padding: 20px; }
#never { visibility: hidden; height: 60px; background: #999; }
</style>
</head>
<body>
<h1>Reveal fixture</h1>
<div class="spacer"></div>
<!-- Waypoint reveal: hidden at load, class-swapped when scrolled into view. -->
<div id="reveal-io" class="reveal-box elementor-invisible" data-io-reveal data-settings='{"_animation":"fadeInUp"}'>
<p>Scroll-revealed content (IntersectionObserver)</p>
</div>
<div class="spacer"></div>
<!-- Same pre-reveal marker but keyed to a trigger the dwell walk cannot fire:
only the known-library neutralization reveals it. -->
<div id="reveal-far" class="reveal-box elementor-invisible" data-settings='{"_animation":"fadeInUp"}'>
<p>Reveal keyed to a non-scroll trigger</p>
</div>
<!-- Genuinely hidden, non-library content: must STAY hidden in the capture. -->
<div id="never">permanently hidden</div>
<script>
const io = new IntersectionObserver((entries) => {
for (const e of entries) {
if (!e.isIntersecting) continue;
e.target.classList.remove("elementor-invisible");
e.target.classList.add("animated", "fadeInUp");
io.unobserve(e.target);
}
}, { threshold: 0.1 });
for (const el of document.querySelectorAll("[data-io-reveal]")) io.observe(el);
</script>
</body>
</html>