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>
This commit is contained in:
co-authored by
Claude Fable 5
parent
9aed2540aa
commit
199359d0a6
@@ -100,3 +100,136 @@ describe("generateCss visibility", () => {
|
||||
assert.ok(!baseRule(css, "n2").includes("visibility"));
|
||||
});
|
||||
});
|
||||
|
||||
// Hidden-node banded geometry. A `visibility:hidden` box still PARTICIPATES in layout (unlike
|
||||
// `display:none`), so the emitter must not let the base rule's baked CANONICAL geometry stand at
|
||||
// widths where the capture measured something else — that is how a desktop `left:548px` slider
|
||||
// arrow ends up parked, invisibly, 210px past the right edge of a 375px viewport.
|
||||
describe("generateCss hidden-node banded geometry", () => {
|
||||
const HVPS = [375, 1280, 1920];
|
||||
type VpState = { cs?: StyleMap; bbox?: BBox; visible?: boolean };
|
||||
function nodeAt(id: string, tag: string, byVp: Record<number, VpState>, children: IRChild[] = []): IRNode {
|
||||
const computedByVp: Record<number, StyleMap> = {};
|
||||
const bboxByVp: Record<number, BBox> = {};
|
||||
const visibleByVp: Record<number, boolean> = {};
|
||||
for (const vp of HVPS) {
|
||||
const s = byVp[vp] ?? {};
|
||||
computedByVp[vp] = computed(s.cs);
|
||||
bboxByVp[vp] = s.bbox ?? { x: 0, y: 0, width: vp, height: 100 };
|
||||
visibleByVp[vp] = s.visible ?? true;
|
||||
}
|
||||
return { id, tag, attrs: {}, visibleByVp, bboxByVp, computedByVp, children };
|
||||
}
|
||||
function ir3(root: IRNode): IR {
|
||||
const ir = irWith(root);
|
||||
ir.doc.viewports = HVPS;
|
||||
ir.doc.sampleViewports = HVPS;
|
||||
ir.doc.perViewport = Object.fromEntries(HVPS.map((vp) => [vp, { scrollHeight: 800, scrollWidth: vp, htmlBg: "rgb(255, 255, 255)", bodyBg: "rgb(255, 255, 255)", bodyColor: "rgb(0, 0, 0)", bodyFont: "Arial" }]));
|
||||
return ir;
|
||||
}
|
||||
/** The `.c<id>{…}` body inside the first @media block whose query matches `mediaRe`. */
|
||||
function bandRule(css: string, mediaRe: RegExp, id: string): string {
|
||||
for (const m of css.matchAll(/@media ([^{]+) \{\n([\s\S]*?)\n\}/g)) {
|
||||
if (!mediaRe.test(m[1]!)) continue;
|
||||
const r = m[2]!.match(new RegExp(`\\.c${id}\\{([^}]*)\\}`));
|
||||
if (r) return r[1]!;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
const arrowCs = (left: string, hidden: boolean): StyleMap =>
|
||||
({ display: "flex", position: "absolute", left, ...(hidden ? { visibility: "hidden" } : {}) });
|
||||
|
||||
it("emits the captured per-band geometry for a box its own visibility:hidden leaves occupying space", () => {
|
||||
// Hidden at base AND at the mobile band with DIFFERENT lefts (the swiper-arrow shape): the
|
||||
// band must carry the mobile left, not inherit the baked canonical one.
|
||||
const arrow = nodeAt("n1", "div", {
|
||||
375: { cs: arrowCs("37px", true), bbox: { x: 37, y: 0, width: 46, height: 46 }, visible: false },
|
||||
1280: { cs: arrowCs("548px", true), bbox: { x: 548, y: 0, width: 46, height: 46 }, visible: false },
|
||||
1920: { cs: arrowCs("588px", false), bbox: { x: 588, y: 0, width: 46, height: 46 } },
|
||||
});
|
||||
const root = nodeAt("n0", "body", { 1280: { cs: { position: "relative" } } }, [arrow]);
|
||||
const css = generateCss(ir3(root), new Map());
|
||||
assert.ok(baseRule(css, "n1").includes("visibility:hidden"));
|
||||
assert.ok(baseRule(css, "n1").includes("left:548px"));
|
||||
const mobile = bandRule(css, /max-width/, "n1");
|
||||
assert.ok(mobile.includes("left:37px"), `mobile band should carry the captured left, got: ${mobile}`);
|
||||
assert.ok(!mobile.includes("display:none"), "an occupying hidden box must stay in layout");
|
||||
// The wide band where the node becomes visible keeps working: visibility restored + its left.
|
||||
const wide = bandRule(css, /min-width/, "n1");
|
||||
assert.ok(wide.includes("visibility:inherit"), `wide band should restore visibility, got: ${wide}`);
|
||||
assert.ok(wide.includes("left:588px"), `wide band should carry the 1920 left, got: ${wide}`);
|
||||
});
|
||||
|
||||
it("hides a visibility:hidden box whose captured bbox is 0x0 with display:none at that band", () => {
|
||||
// At 375 the hidden arrow occupied NOTHING (uninitialised swiper) — display:none reproduces
|
||||
// "renders nothing, takes no space" and cannot extend the scrollable area.
|
||||
const arrow = nodeAt("n1", "div", {
|
||||
375: { cs: arrowCs("calc(50% - 52px)", true), bbox: { x: 0, y: 0, width: 0, height: 0 }, visible: false },
|
||||
1280: { cs: arrowCs("548px", true), bbox: { x: 548, y: 0, width: 46, height: 46 }, visible: false },
|
||||
1920: { cs: arrowCs("588px", false), bbox: { x: 588, y: 0, width: 46, height: 46 } },
|
||||
});
|
||||
const root = nodeAt("n0", "body", { 1280: { cs: { position: "relative" } } }, [arrow]);
|
||||
const css = generateCss(ir3(root), new Map());
|
||||
const mobile = bandRule(css, /max-width/, "n1");
|
||||
assert.ok(mobile.includes("display:none"), `0x0 hidden band should be display:none, got: ${mobile}`);
|
||||
const wide = bandRule(css, /min-width/, "n1");
|
||||
assert.ok(wide.includes("visibility:inherit") && wide.includes("left:588px"));
|
||||
});
|
||||
|
||||
it("emits display:none at a band where the node turns display:none even when hidden at base", () => {
|
||||
// The base rule bakes an OCCUPYING visibility:hidden box (canonical geometry); without the
|
||||
// band that box would render at mobile widths where the source had display:none.
|
||||
const wrap = nodeAt("n1", "div", {
|
||||
375: { cs: { display: "none", visibility: "hidden" }, bbox: { x: 0, y: 0, width: 0, height: 0 }, visible: false },
|
||||
1280: { cs: { visibility: "hidden" }, bbox: { x: 40, y: 0, width: 1200, height: 574 }, visible: false },
|
||||
1920: { cs: {}, bbox: { x: 320, y: 0, width: 1280, height: 533 } },
|
||||
});
|
||||
const root = nodeAt("n0", "body", {}, [wrap]);
|
||||
const css = generateCss(ir3(root), new Map());
|
||||
assert.ok(baseRule(css, "n1").includes("visibility:hidden"));
|
||||
const mobile = bandRule(css, /max-width/, "n1");
|
||||
assert.ok(mobile.includes("display:none"), `own display:none band must emit even when hidden at base, got: ${mobile}`);
|
||||
});
|
||||
|
||||
it("emits per-band geometry for an occupying box an ancestor hides at base AND at the band", () => {
|
||||
// The cropin.com/cotton slider arrow: the elementor-widget ANCESTOR is visibility:hidden at
|
||||
// 375/1280 (so the arrow is never ownHidden and never shownAtBase) yet the arrow's absolute
|
||||
// box still occupies layout. Without a band the base's canonical left:548px parks it 210px
|
||||
// past the right edge of a 375px viewport — the band must carry the captured mobile left.
|
||||
const arrow = nodeAt("n2", "div", {
|
||||
375: { cs: arrowCs("120px", true), bbox: { x: 112, y: 0, width: 46, height: 46 }, visible: false },
|
||||
1280: { cs: arrowCs("548px", true), bbox: { x: 548, y: 0, width: 46, height: 46 }, visible: false },
|
||||
1920: { cs: arrowCs("588px", false), bbox: { x: 588, y: 0, width: 46, height: 46 } },
|
||||
});
|
||||
const parent = nodeAt("n1", "div", {
|
||||
375: { cs: { position: "relative", visibility: "hidden" }, visible: false },
|
||||
1280: { cs: { position: "relative", visibility: "hidden" }, visible: false },
|
||||
1920: { cs: { position: "relative" } },
|
||||
}, [arrow]);
|
||||
const root = nodeAt("n0", "body", {}, [parent]);
|
||||
const css = generateCss(ir3(root), new Map());
|
||||
const mobile = bandRule(css, /max-width/, "n2");
|
||||
assert.ok(mobile.includes("left:120px"), `mobile band should carry the captured left, got: ${mobile}`);
|
||||
assert.ok(!mobile.includes("display:none"), "an occupying hidden box must stay in layout");
|
||||
});
|
||||
|
||||
it("still emits only the hide for a box an ANCESTOR's visibility:hidden covers", () => {
|
||||
// Inherited hides stay breakpoint noise: the ancestor's own rule (and its geometry
|
||||
// correction) covers the subtree — the child emits its hide, not geometry overrides.
|
||||
const child = nodeAt("n2", "div", {
|
||||
375: { cs: { position: "absolute", left: "10px", visibility: "hidden" }, bbox: { x: 10, y: 0, width: 40, height: 40 }, visible: false },
|
||||
1280: { cs: { position: "absolute", left: "500px" }, bbox: { x: 500, y: 0, width: 40, height: 40 } },
|
||||
1920: { cs: { position: "absolute", left: "500px" }, bbox: { x: 500, y: 0, width: 40, height: 40 } },
|
||||
});
|
||||
const parent = nodeAt("n1", "div", {
|
||||
375: { cs: { position: "relative", visibility: "hidden" }, visible: false },
|
||||
1280: { cs: { position: "relative" } },
|
||||
1920: { cs: { position: "relative" } },
|
||||
}, [child]);
|
||||
const root = nodeAt("n0", "body", {}, [parent]);
|
||||
const css = generateCss(ir3(root), new Map());
|
||||
const mobile = bandRule(css, /max-width/, "n2");
|
||||
assert.ok(mobile.includes("visibility:hidden"), `child should carry the hide, got: ${mobile}`);
|
||||
assert.ok(!mobile.includes("left:10px"), `ancestor-hidden child must not emit geometry, got: ${mobile}`);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user