Files
hyperframes/registry/components/ascii-trail-reveal/ascii-trail-reveal.html
T
Miguel Ángel 9734578e60 feat(registry): bring back the video-primitive moves (#3169)
Restores the 208 catalog items reverted after their previews 404'd in
production, this time on the payload mechanism rather than the .html files
that caused the outage.

The generator no longer writes a preview document to docs/public. That writer,
and the machinery under it, existed only to produce files the docs host
discards, so it is gone rather than bypassed. Items now embed the composition
itself via a payload, which is what the previous change already does for the
items that were already in the catalog.

The variables explorer is parked, not restored: it drove its preview through
the same unpublished .html path, so it would have shown an empty frame. Items
that declare variables get the live player plus the static variables table, and
reconnecting the explorer to payloads is a follow-up.
2026-08-10 18:46:03 -04:00

381 lines
13 KiB
HTML
Vendored

<!doctype html>
<!--
ascii-trail-reveal: HyperFrames video primitive (intros and reveals)
Concept: a luminous actor follows an authored S-curve across an ASCII
signal grid. Cells flip away behind the actor to reveal the real panel,
while the lagging edge dissolves in a deterministic cascade.
Variables:
- label (string, default "The reveal"): text on the revealed panel.
- accent (green | blue | violet, default "green"): trail and panel accent.
- trail_width (narrow | wide, default "narrow"): width of the reveal tail.
Envelope (fixed IN and OUT, elastic HOLD only, never timeScale):
IN_BASE = 3.20s the actor crosses the authored path, rests briefly,
then dissolves as the final cells complete their flip
HOLD = elastic = max(0, D - IN_BASE), fully revealed content drifts
subtly and remains legible
OUT_BASE = 0.00s the revealed panel is the required end state
If D is shorter than IN_BASE, IN compresses to D and HOLD is zero.
Determinism: glyphs, cell timing jitter, and reveal order come only from
row, column, and index math. No clock or random source participates.
-->
<html
lang="en"
data-composition-id="ascii-trail-reveal"
data-composition-duration="4"
data-composition-variables='[
{ "id": "label", "type": "string", "role": "content", "label": "Panel label", "description": "Text shown on the revealed panel.", "default": "The reveal" },
{ "id": "accent", "type": "enum", "role": "style", "label": "Accent", "description": "Color used by the trail and revealed panel.", "default": "green", "options": [{ "value": "green", "label": "Green" }, { "value": "blue", "label": "Blue" }, { "value": "violet", "label": "Violet" }] },
{ "id": "trail_width", "type": "enum", "role": "layout", "label": "Trail width", "description": "Width of the cell reveal tail around the authored path.", "default": "narrow", "options": [{ "value": "narrow", "label": "Narrow" }, { "value": "wide", "label": "Wide" }] }
]'
>
<head>
<meta charset="UTF-8" />
<title>ASCII Trail Reveal</title>
</head>
<body>
<template>
<div id="root" data-composition-id="ascii-trail-reveal" data-duration="4" data-fps="30">
<style>
*,
*::before,
*::after {
box-sizing: border-box;
}
#root {
position: absolute;
inset: 0;
overflow: hidden;
container-type: size;
isolation: isolate;
color: var(--fg, #f8fafc);
font-family: var(--font-body, Inter, system-ui, sans-serif);
pointer-events: none;
}
.atr-clip {
position: absolute;
inset: 0;
display: grid;
place-items: center;
overflow: hidden;
}
.atr-panel {
position: relative;
width: 88cqw;
height: 76cqh;
overflow: hidden;
border: 0.16cqmin solid color-mix(in srgb, var(--atr-accent) 52%, #475569);
border-radius: 3cqmin;
background: #111820;
box-shadow:
0 4cqh 12cqh rgba(0, 0, 0, 0.45),
inset 0 0 0 0.12cqmin rgba(255, 255, 255, 0.04);
perspective: 80cqmin;
}
.atr-content {
position: absolute;
inset: 0;
display: grid;
place-items: center;
padding: 8cqw;
background:
radial-gradient(
circle at 50% 45%,
color-mix(in srgb, var(--atr-accent) 24%, transparent),
transparent 48%
),
linear-gradient(145deg, #17212c, #0c1219);
transform-origin: 50% 50%;
}
.atr-label {
max-width: 76cqw;
color: #f8fafc;
font-family: var(--font-display, Inter, system-ui, sans-serif);
font-size: clamp(24px, 10cqmin, 112px);
font-weight: 760;
line-height: 0.96;
letter-spacing: -0.045em;
text-align: center;
text-wrap: balance;
text-shadow: 0 0 4cqmin color-mix(in srgb, var(--atr-accent) 28%, transparent);
}
.atr-grid {
--atr-columns: 24;
--atr-rows: 14;
position: absolute;
inset: 0;
z-index: 2;
display: grid;
grid-template-columns: repeat(var(--atr-columns), 1fr);
grid-template-rows: repeat(var(--atr-rows), 1fr);
overflow: hidden;
background: #0b1016;
transform-style: preserve-3d;
}
.atr-cell {
display: grid;
place-items: center;
min-width: 0;
min-height: 0;
overflow: hidden;
border-right: 0.06cqmin solid rgba(148, 163, 184, 0.08);
border-bottom: 0.06cqmin solid rgba(148, 163, 184, 0.08);
background: color-mix(in srgb, #111820 94%, var(--atr-accent));
color: color-mix(in srgb, var(--atr-accent) 72%, #dbeafe);
font-family: var(--font-mono, ui-monospace, "SF Mono", Menlo, monospace);
font-size: clamp(7px, 2.35cqmin, 22px);
font-weight: 650;
line-height: 1;
transform-origin: 50% 50%;
backface-visibility: hidden;
will-change: transform, opacity;
}
.atr-path-layer {
position: absolute;
inset: 0;
z-index: 3;
width: 100%;
height: 100%;
overflow: visible;
}
.atr-path-guide {
fill: none;
stroke: color-mix(in srgb, var(--atr-accent) 34%, transparent);
stroke-width: 2;
stroke-linecap: round;
stroke-dasharray: 3 14;
}
.atr-actor-halo {
fill: color-mix(in srgb, var(--atr-accent) 22%, transparent);
}
.atr-actor-core {
fill: #f8fafc;
stroke: var(--atr-accent);
stroke-width: 7;
filter: drop-shadow(0 0 18px var(--atr-accent));
}
</style>
<div
id="ascii-trail-reveal-clip"
class="atr-clip clip"
data-start="0"
data-duration="4"
data-track-index="0"
>
<div class="atr-panel">
<div class="atr-content">
<div class="atr-label"></div>
</div>
<div class="atr-grid" aria-hidden="true"></div>
<svg
class="atr-path-layer"
viewBox="0 0 1000 600"
preserveAspectRatio="none"
aria-hidden="true"
>
<path class="atr-path-guide"></path>
<g id="ascii-trail-reveal-actor">
<circle class="atr-actor-halo" r="42"></circle>
<circle class="atr-actor-core" r="13"></circle>
</g>
</svg>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<script>
(function () {
"use strict";
var root = document.getElementById("root");
var content = root.querySelector(".atr-content");
var label = root.querySelector(".atr-label");
var grid = root.querySelector(".atr-grid");
var guide = root.querySelector(".atr-path-guide");
var actor = root.querySelector("#ascii-trail-reveal-actor");
var vars =
window.__hyperframes && window.__hyperframes.getVariables
? window.__hyperframes.getVariables()
: {};
var labelText = vars.label == null ? "The reveal" : String(vars.label);
var accent = vars.accent === "blue" || vars.accent === "violet" ? vars.accent : "green";
var trailWidth = vars.trail_width === "wide" ? "wide" : "narrow";
var accentColors = {
green: "var(--brand, #4ade80)",
blue: "var(--accent, #38bdf8)",
violet: "var(--accent-2, #a78bfa)",
};
label.textContent = labelText;
root.style.setProperty("--atr-accent", accentColors[accent]);
var COLS = 24;
var ROWS = 14;
var GLYPHS = " .:-=+*#%@01/\\<>[]{}";
var PATH_POINTS = [
[-80, 460],
[340, 460],
[660, 140],
[1080, 140],
];
var cells = [];
function sCurvePoint(progress) {
var inverse = 1 - progress;
var startWeight = inverse * inverse * inverse;
var firstWeight = 3 * inverse * inverse * progress;
var secondWeight = 3 * inverse * progress * progress;
var endWeight = progress * progress * progress;
return {
x:
startWeight * PATH_POINTS[0][0] +
firstWeight * PATH_POINTS[1][0] +
secondWeight * PATH_POINTS[2][0] +
endWeight * PATH_POINTS[3][0],
y:
startWeight * PATH_POINTS[0][1] +
firstWeight * PATH_POINTS[1][1] +
secondWeight * PATH_POINTS[2][1] +
endWeight * PATH_POINTS[3][1],
};
}
guide.setAttribute(
"d",
"M " +
PATH_POINTS[0].join(" ") +
" C " +
PATH_POINTS.slice(1)
.map(function (point) {
return point.join(" ");
})
.join(" "),
);
for (var row = 0; row < ROWS; row += 1) {
for (var column = 0; column < COLS; column += 1) {
var index = row * COLS + column;
var cell = document.createElement("span");
var glyphIndex = (index * 17 + row * 11 + column * 7) % GLYPHS.length;
cell.className = "atr-cell";
cell.textContent = GLYPHS.charAt(glyphIndex);
grid.appendChild(cell);
cells.push({ element: cell, row: row, column: column, index: index });
}
}
var IN_BASE = 3.2;
var duration = Math.max(0.001, parseFloat(root.dataset.duration || "4"));
var IN = Math.min(IN_BASE, duration);
var HOLD = Math.max(0, duration - IN);
var phaseScale = IN / IN_BASE;
var actorState = { progress: 0 };
function updateActor() {
var point = sCurvePoint(actorState.progress);
actor.setAttribute("transform", "translate(" + point.x + " " + point.y + ")");
}
gsap.set(content, { scale: 0.985 });
gsap.set(guide, { opacity: 0 });
gsap.set(actor, { opacity: 0 });
updateActor();
var tl = gsap.timeline({ paused: true });
tl.to(content, { scale: 1, duration: IN, ease: "power2.out" }, 0);
tl.to(
guide,
{ opacity: 0.72, duration: 0.24 * phaseScale, ease: "power2.out" },
0.18 * phaseScale,
);
tl.to(
actor,
{ opacity: 1, duration: 0.24 * phaseScale, ease: "power2.out" },
0.22 * phaseScale,
);
tl.to(
actorState,
{
progress: 1,
duration: 2.35 * phaseScale,
ease: "power2.out",
onUpdate: updateActor,
},
0.34 * phaseScale,
);
cells.forEach(function (cellData) {
var progress = (cellData.column + 0.5) / COLS;
var pathPoint = sCurvePoint(progress);
var cellY = ((cellData.row + 0.5) / ROWS) * 600;
var distanceFromPath = Math.abs(cellY - pathPoint.y) / 600;
var distanceLag = distanceFromPath * (trailWidth === "wide" ? 0.5 : 0.8);
var indexJitter = ((cellData.index * 37) % 11) * 0.009;
var revealAt = (0.42 + progress * 1.92 + distanceLag + indexJitter) * phaseScale;
var flipDuration = (0.2 + ((cellData.index * 13) % 5) * 0.018) * phaseScale;
tl.to(
cellData.element,
{
rotationY: 88,
scaleX: 0.3,
opacity: 0,
duration: flipDuration,
ease: "power2.in",
},
revealAt,
);
});
tl.to(
guide,
{ opacity: 0, duration: 0.24 * phaseScale, ease: "power2.in" },
2.82 * phaseScale,
);
tl.to(
actor,
{ opacity: 0, duration: 0.24 * phaseScale, ease: "power2.in" },
2.92 * phaseScale,
);
if (HOLD > 0) {
tl.to(
content,
{
scale: 1.008,
duration: HOLD / 2,
ease: "sine.inOut",
yoyo: true,
repeat: 1,
},
IN,
);
}
tl.seek(0);
window.__timelines = window.__timelines || {};
window.__timelines["ascii-trail-reveal"] = tl;
})();
</script>
</div>
</template>
</body>
</html>