Skip to content

feat(registry): curtain-reveal transition - #4323

Open
miguel-heygen wants to merge 1 commit into
mainfrom
feat/curtain-reveal
Open

miguel-heygen wants to merge 1 commit into
mainfrom
feat/curtain-reveal

Conversation

@miguel-heygen

@miguel-heygen miguel-heygen commented Sep 23, 2026

Copy link
Copy Markdown
Collaborator

What

Adds curtain-reveal to the catalog: two pleated stage curtains that gather outward from center, the way real velvet bunches at the tieback, revealing the stage underneath. A static pelmet frames the top; a soft spotlight can bloom in as the curtains part.

Why

"Curtain" returns zero results on the catalog's word-tier search today; agents asking for a physical theatre-curtain reveal have nothing to point at.

Look

Each curtain is a pleated panel (a repeating linear-gradient standing in for fabric folds) with a closed-form sine-wave scalloped hem sampled at a fixed point count, clipped via clip-path. Both curtains part on one authored GSAP tween each with explicit fromTo endpoints; an optional pelmet (its own scalloped trim) stays static, and an optional spotlight glow blooms in as they part.

Structure

Copies the catalog item layout from registry/components/ink-bleed-reveal/ — slot-mount pattern at registry/components/ink-bleed-reveal/ink-bleed-reveal.html:233-247, registry-item.json shape, docs page structure from docs/catalog/components/grade-split-reveal.mdx.

Determinism

No physics state: the scalloped hem is a closed-form sine sample, both curtain tweens have explicit endpoints, no Math.random/wall clock/requestAnimationFrame anywhere. The spotlight sub-tween is clamped so it always ends at or before the curtain-open envelope, so a very short composition (below the base open duration, with no hold) never runs past its declared data-duration.

Rendered demo capture is attached above (rendered via devbox headless Chrome since ai-miga was mid-outage team-wide).

Test plan

  • comment-check clean against origin/main
  • Independent adversarial review, no findings above the confidence bar (report on file)
  • Rendered demo capture attached
  • Seen by home and Miguel before merge

@mintlify

mintlify Bot commented Sep 23, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated
hyperframes 🟢 Ready View Preview Sep 23, 2026, 11:30 AM

💡 Tip: Enable Automations to automatically generate PRs for you.

@miguel-heygen

Copy link
Copy Markdown
Collaborator Author

Rendered demo (rendered via devbox headless Chrome since ai-miga was down for this branch).

curtain-reveal-fixed.mp4

Agents often search for "curtain" or "stage reveal" and find nothing built
for it in the catalog. Adds a component where two pleated stage curtains
gather outward from center, like real velvet bunching at the tieback, to
reveal slotted content underneath.

@terencecho terencecho left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed at 548de3fd.

Scope. 11 files: registry/components/curtain-reveal/{curtain-reveal.html, demo.html, registry-item.json} + docs/catalog/components/curtain-reveal.mdx + entries in docs.json, catalog-index.json, local-vectors.json/bin, catalog-gallery-data.mdx, and the new preview JSON blob. Additive; no touch outside the curtain-reveal item.

Component correctness (curtain-reveal.html).

  • Deterministic. No Math.random, no wall clock, no requestAnimationFrame, no Date.now. Pleat texture is a static repeating-linear-gradient. Scalloped hem via hemPolygon(scallops, ampPct, baseY) samples sin(t * PI * scallops) * ampPct at 25 fixed points (STEPS = 24, i inclusive of both ends) — same trim every render.
  • Envelope clamped to data-duration. IN = Math.min(IN_BASE, duration) where duration = Math.max(0.001, parseFloat(root.dataset.duration || "2.4")), so the fixed-IN case reduces to a fully-compressed part when data-duration < IN_BASE. The two curtain fromTo tweens have duration: IN and start at 0, so they land at exactly IN.
  • Spotlight sub-tween mathematically pinned to end at IN, not past it. spotlightStart = IN * 0.25, spotlightDuration = Math.min(IN * 0.85, IN - spotlightStart). IN - spotlightStart = 0.75 * IN, min(0.85 * IN, 0.75 * IN) = 0.75 * IN, so the spotlight ends at spotlightStart + spotlightDuration = 0.25 * IN + 0.75 * IN = IN exactly. The uncapped version (duration = IN * 0.85) would land at IN * 1.10 and, on a data-duration < IN_BASE mount, escape past data-duration. The clamp is right and the comment above it names the exact failure mode it defuses.
  • tl.set({}, {}, duration) anchors timeline length so GSAP's auto-extend doesn't push past data-duration.
  • Tone table is safe. Object.prototype.hasOwnProperty.call(toneTable, vars.tone) guards against prototype pollution — a vars.tone = "toString" won't resolve toneTable[tone] to a stringified function. Falls back to "red" for unknown values.
  • Slot pattern. Uses hostDoc.querySelector('template[data-slot="curtain-reveal-stage"]') at document level (templates never render in-place, so the caller's markup is inert until this primitive imports it). Falls back to the built-in .cvr-stage-default card if no slot template is present. defaultMark.remove() before appendChild(hostDoc.importNode(...)) — no double-render.
  • Mount contract. Registers one paused timeline under literal "curtain-reveal" key. #root is position: absolute; inset: 0; container-type: size; isolation: isolate; — CSS cqw / cqmin units size correctly against the host box, no data-width / data-height required.
  • No listeners, timers, observers → no teardown needed beyond the runtime's element removal.

Demo (demo.html). Standalone composition mounting the primitive full-bleed via data-composition-src, navy tone with pelmet off — exercises the non-default variable path. Slots a procedural inline-SVG stage scene so the demo ships nothing licensed (matches the grade-split-reveal pattern). Correctly reaches into the template's content via template.content.getElementById(...) rather than document.getElementById (inert templates aren't in the live document).

Catalog-drift check. Cleaner scope than #4331docs.json only bumps the Motion & effects (94 → 95) and Effects (53 → 54) tag counts and inserts catalog/components/curtain-reveal alphabetically between confetti and cut-the-curve. catalog-index.json inserts a single entry alphabetically between cursor-glyph-trail and cut-the-curve. local-vectors.json inserts curtain-reveal alphabetically. No unrelated drift folded in. catalogArtifact.revision bumps to 2c3b540a….

Registry.json placement. curtain-reveal appended at the end of items[] after warm-grain — note this is different from #4331's shape (which reorders the 8 hyperframes:example items to the top). Both PRs bump the artifact revision from 01f77564 (main) to their own hashes, and both pass check:catalog-drift at their heads, so each is internally consistent. Whichever merges first will define the resting order, and the second will rebase-regen through the artifact script. Non-blocking on this PR; loop-owner concern to sequence merges.

CI at head. Rollup SUCCESS. All Studio / Test / Producer / SDK / captures / catalog / lint / format / typecheck / build / windows / analyze / codeql green.

Body hygiene. Author's determinism claim + spotlight-clamp reasoning are both verified in the source. Independent adversarial review recorded (report on file). Rendered demo capture attached. [ ] Seen by home and Miguel before merge is your process, not a stamp gate.

Approving.

— tai

This branch was successfully deployed

1 active deployment
staging - docs 548de3fd Deployed Sep 23, 2026 by mintlify[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants