Files
hyperframes/skills/music-to-video/references/templates/roll-flipbook-word-cycle/index.html
T

268 lines
11 KiB
HTML

<!doctype html>
<html
lang="en"
data-composition-variables='[
{"id":"bgColor", "type":"color", "label":"Background", "default":"#000000"},
{"id":"textColor", "type":"color", "label":"Text color", "default":"#ffffff"},
{"id":"accentColor","type":"color", "label":"Period accent", "default":"#ff6a32"},
{"id":"flipWords", "type":"string", "label":"Flipbook words (comma-separated)", "default":"Design,Strategy,Branding,Marketing,Production,Campaigns,Experiential,Communication,Event Management"},
{"id":"resolveText","type":"string", "label":"Lock phrase (empty = pure flicker, no lock)", "default":"everything you need"},
{"id":"periodChar", "type":"string", "label":"Trailing accent (empty = none)", "default":"."}
]'
>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=1920, height=1080" />
<title>Roll-Driven Flipbook (Word Cycle → Optional Lock) — gruop_3 reference impl</title>
<script src="../../motion-primitives/assets/gsap.min.js"></script>
<style>
*,
*::before,
*::after {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html,
body {
background: var(--bg, #000000);
overflow: hidden;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}
#root {
position: relative;
width: 1920px;
height: 1080px;
overflow: hidden;
background: var(--bg, #000000);
}
.g3-overlay {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
pointer-events: none;
}
/* grid stack — every layer occupies the same centred cell, so scaling a
layer transforms around its own centre (no translate(-50%) to clobber). */
.g3-stack {
display: grid;
place-items: center;
}
.g3-stack > * {
grid-area: 1 / 1;
}
.g3-layer {
white-space: nowrap;
color: var(--text, #ffffff);
text-shadow:
0 0 18px rgba(255, 255, 255, 0.45),
0 0 46px rgba(255, 255, 255, 0.22);
}
.g3-flip {
font-size: 120px;
font-weight: 800;
letter-spacing: -0.01em;
}
.g3-resolve {
font-size: 116px;
font-weight: 800;
font-style: italic;
letter-spacing: -0.01em;
}
.g3-period {
color: var(--accent, #ff6a32);
font-weight: 800;
font-style: italic;
}
</style>
</head>
<body>
<!-- data-duration = the scene span. Set this to YOUR scene length; the script
reads it back so duration is single-sourced (no hard-coded value in JS).
Rebased to scene-relative time — content starts at ~0, no dead pre-roll. -->
<div
data-hf-id="hf-g3rt"
id="root"
data-composition-id="group-three-recreate"
data-width="1920"
data-height="1080"
data-start="0"
data-duration="3.8"
data-root="true"
>
<div data-hf-id="hf-g3ov" class="g3-overlay">
<div data-hf-id="hf-g3sk" class="g3-stack" id="stack"></div>
</div>
</div>
<script>
(function () {
// ════════════════════════════════════════════════════════════════
// ROLL-DRIVEN FLIPBOOK (Word Cycle → Optional Lock) · dense_multiply
// A hi-hat ROLL drives a centred word that flips every 16th-note (flipbook),
// cycling a word list to FILL the roll. Optionally, when a SURGE/phrase
// follows, the flicker resolves (scramble-decode) into a locked phrase.
// Background is pure black. Every move is a 0ms autoAlpha set on a pre-built
// node (no GSAP textContent-sets, no onUpdate — both fail under seek render).
// ════════════════════════════════════════════════════════════════
var root = document.getElementById("root");
// ── 1. BEAT MAP — the ONLY block you edit per track ─────────────────
// SCENE-RELATIVE time (starts at ~0 — no dead pre-roll). When reusing, map
// your audiomap roll [rollStart,rollEnd] onto [flipStart, flipStart+rollLen],
// i.e. subtract the roll's start offset. bpm stays your track's tempo.
var BEAT = {
bpm: 129.2, // tempo.bpm → 16th-note flip step = 60/bpm/4
flipStart: 0.1, // tiny entrance lead-in — NO dead air before the flicker
flipEnd: 2.19, // flicker length ≈ the source roll (~2.1s); auto-fills with words
resolveStart: 2.33, // lock phrase enters after a short clear (only if a phrase is set)
resolveLock: 3.33, // locks ~1s later (place just before a downbeat in your track)
};
// Scene length is single-sourced from data-duration (edit it in the HTML above).
var DUR = parseFloat(root.getAttribute("data-duration")) || 7.34;
// ── 2. Content variables ────────────────────────────────────────────
var defaults = {
bgColor: "#000000",
textColor: "#ffffff",
accentColor: "#ff6a32",
flipWords:
"Design,Strategy,Branding,Marketing,Production,Campaigns,Experiential,Communication,Event Management",
resolveText: "everything you need",
periodChar: ".",
};
var vars = Object.assign(
{},
defaults,
window.__hyperframes && window.__hyperframes.getVariables
? window.__hyperframes.getVariables()
: {},
);
document.documentElement.style.setProperty("--bg", vars.bgColor);
document.documentElement.style.setProperty("--text", vars.textColor);
document.documentElement.style.setProperty("--accent", vars.accentColor);
var stack = root.querySelector("#stack");
var WORDS = String(vars.flipWords)
.split(",")
.map(function (w) {
return w.trim();
})
.filter(function (w) {
return w !== "";
});
var RESOLVE = String(vars.resolveText || "").trim(); // empty → pure flicker, no lock
var HAS_RESOLVE = RESOLVE !== "";
var PERIOD =
vars.periodChar && String(vars.periodChar).trim() !== "" ? vars.periodChar : "";
// ── 3. Derived timing (auto-fills the roll — adapts to any length / bpm) ─
var FLIP_STEP = 60 / BEAT.bpm / 4; // one word per 16th-note
var N_SWAPS = Math.max(1, Math.round((BEAT.flipEnd - BEAT.flipStart) / FLIP_STEP));
// ── 4. Build nodes ──────────────────────────────────────────────────
var flipNodes = {};
WORDS.forEach(function (w) {
if (!flipNodes[w]) {
var s = document.createElement("span");
s.className = "g3-layer g3-flip";
s.textContent = w;
stack.appendChild(s);
flipNodes[w] = s;
gsap.set(s, { autoAlpha: 0 });
}
});
var resolveNodes = [];
if (HAS_RESOLVE) {
var GLYPHS = "!<>-_/[]{}=+*^?#$%&01ABXYZ";
var decodeAt = function (str, p) {
var n = str.length,
out = "",
step = Math.floor(p * 48);
for (var k = 0; k < n; k++) {
var c = str.charAt(k);
if (c === " ") {
out += " ";
continue;
}
var lock = 0.4 + 0.6 * ((k + 1) / n); // lock left→right, in the back 60%
out += p >= lock ? c : GLYPHS.charAt((k * 7 + step * 3) % GLYPHS.length);
}
return out;
};
var DECODE_STEP = FLIP_STEP / 2; // scramble twice as fast as the flip
var N_STEPS = Math.max(
1,
Math.round((BEAT.resolveLock - BEAT.resolveStart) / DECODE_STEP),
);
for (var s2 = 0; s2 <= N_STEPS; s2++) {
var node = document.createElement("span");
node.className = "g3-layer g3-resolve";
if (s2 === N_STEPS) {
node.appendChild(document.createTextNode(RESOLVE));
if (PERIOD) {
var pd = document.createElement("span");
pd.className = "g3-period";
pd.textContent = PERIOD;
node.appendChild(pd);
}
} else {
node.textContent = decodeAt(RESOLVE, s2 / N_STEPS);
}
stack.appendChild(node);
gsap.set(node, { autoAlpha: 0 });
resolveNodes.push({ node: node, t: BEAT.resolveStart + s2 * DECODE_STEP });
}
}
// ── 5. Timeline — one paused GSAP timeline, absolute audio seconds ──
window.__timelines = window.__timelines || {};
var tl = gsap.timeline({ paused: true });
// PHASE A — flipbook word-cycle on the 16th grid, FILLING the roll window
var prev = null;
for (var i = 0; i < N_SWAPS; i++) {
var t = BEAT.flipStart + i * FLIP_STEP;
var node = flipNodes[WORDS[i % WORDS.length]];
if (prev && prev !== node) tl.set(prev, { autoAlpha: 0 }, t);
tl.set(node, { autoAlpha: 1 }, t);
prev = node;
}
// glitchy reveal of the first word as the roll kicks in (scale is safe here)
tl.fromTo(
flipNodes[WORDS[0]],
{ scale: 0.8, filter: "blur(10px)" },
{ scale: 1, filter: "blur(0px)", duration: 0.12, ease: "power2.out" },
BEAT.flipStart,
);
// PHASE B — resolve (OPTIONAL). With a lock phrase: clear, then scramble-decode
// and lock, holding to the end. Without one: the last word just holds (locks).
if (HAS_RESOLVE) {
if (prev) tl.set(prev, { autoAlpha: 0 }, BEAT.flipEnd); // clear the flicker
var prevR = null;
for (var r = 0; r < resolveNodes.length; r++) {
if (prevR) tl.set(prevR, { autoAlpha: 0 }, resolveNodes[r].t);
tl.set(resolveNodes[r].node, { autoAlpha: 1 }, resolveNodes[r].t);
prevR = resolveNodes[r].node;
}
}
// (if no resolve, `prev` — the last flip word — stays visible to the end)
// Extend the timeline to the full duration with a no-op so the held final
// state renders on every frame to DUR (a set AT the timeline's last instant
// gets dropped by the seek renderer).
tl.set(stack, { opacity: 1 }, DUR);
tl.seek(0);
window.__timelines["group-three-recreate"] = tl;
})();
</script>
</body>
</html>