mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-14 18:01:20 +08:00
c8040d6747
One variant per family, curated from a 249-component loop-template collection generated by Miao Yang. Selection covers 24 well-developed multi-variant families (marquee, carousel, 3d, deck, orbit, globe, flicker, spiral, frames, flip, proximity, wheel, book, carousel3d, grid, field, scale, spin, stack, stories, wipe, gravity, parallax, pages) plus 11 best-in-class single-variant families (bento, corridor, coverflow, fold, tunnel, filmstrip, ring, shelf, vortexspin, swirl, cardtoss). Code-only: each block ships registry-item.json + HTML composition, no preview media. registry.json updated with all 35 entries, kept alphabetically sorted within the hyperframes:block group. Validation: scripts/lint-registry-items.mjs passed, 434 item(s), 0 errors. Co-Authored-By: Miao Yang <miao.yang@heygen.com> Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
263 lines
30 KiB
HTML
Vendored
263 lines
30 KiB
HTML
Vendored
<!doctype html>
|
|
<html
|
|
lang="en"
|
|
data-composition-variables='[{"id":"mediaFit","type":"enum","role":"style","label":"Media · Fit","description":"Natural keeps each image's own ratio; Crop fills the card box at the media aspect below.","default":"natural","options":[{"value":"natural","label":"Natural (original ratio)"},{"value":"crop","label":"Crop"}]},{"id":"cropAspectRatio","type":"enum","role":"style","label":"Media · Aspect","description":"Card aspect used when Fit is Crop.","default":"1:1","options":[{"value":"16:9","label":"16:9"},{"value":"9:16","label":"9:16"},{"value":"1:1","label":"1:1"},{"value":"4:5","label":"4:5"},{"value":"3:2","label":"3:2"},{"value":"3:4","label":"3:4"},{"value":"2:3","label":"2:3"}]},{"id":"backgroundType","type":"enum","role":"style","label":"Background · Type","description":"Gradient (start → end colours), flat Colour, or your own Image.","default":"gradient","options":[{"value":"gradient","label":"Gradient"},{"value":"color","label":"Color"},{"value":"image","label":"Image"}]},{"id":"backgroundGradientStart","type":"color","role":"style","label":"Background · Gradient start","description":"Top colour of the gradient.","default":"#111111"},{"id":"backgroundGradientEnd","type":"color","role":"style","label":"Background · Gradient end","description":"Bottom colour of the gradient.","default":"#444444"},{"id":"backgroundColor","type":"color","role":"style","label":"Background · Color","description":"Flat colour when Type is Color; also the ground under an Image.","default":"#0b0b0f"},{"id":"backgroundImage","type":"image","role":"style","label":"Background · Image","description":"Cover-fitted background when Type is Image.","portrays":["background_image"],"default":""},{"id":"blurAmount","type":"number","role":"style","label":"Background · Blur","description":"Blur on the background image, px.","default":0,"min":0,"max":60,"step":1,"unit":"px"},{"id":"backgroundGradientNoise","type":"number","role":"style","label":"Background · Grain","description":"Film-grain overlay strength.","default":0,"min":0,"max":100,"step":1,"unit":"%"},{"id":"backgroundOpacity","type":"number","role":"style","label":"Background · Opacity","description":"Opacity of the whole background layer (0 = transparent ground).","default":1,"min":0,"max":1,"step":0.05},{"id":"scaleIntensity","type":"number","role":"style","label":"Appearance · Media scale","description":"Strength of the size change.","default":0.2,"min":0,"max":1,"step":0.05},{"id":"borderRadius","type":"number","role":"style","label":"Appearance · Border radius","description":"Card corner radius in px at 1080.","default":24,"min":0,"max":200,"step":1,"unit":"px"},{"id":"direction","type":"enum","role":"style","label":"Animation · Direction","description":"Direction of travel.","default":"anticlockwise","options":[{"value":"anticlockwise","label":"Anticlockwise"},{"value":"clockwise","label":"Clockwise"}]},{"id":"radiusX","type":"number","role":"style","label":"Template options · Radius X","description":"Radius X (engine parameter; see the block header comment).","default":32,"min":0,"max":64,"step":1},{"id":"radiusY","type":"number","role":"style","label":"Template options · Radius Y","description":"Radius Y (engine parameter; see the block header comment).","default":32,"min":0,"max":64,"step":1},{"id":"repeat","type":"number","role":"style","label":"Template options · Repeat","description":"Repeat (engine parameter; see the block header comment).","default":1,"min":0,"max":10,"step":0.1},{"id":"tangent","type":"enum","role":"style","label":"Template options · Tangent","description":"Tangent (engine parameter; see the block header comment).","default":"true","options":[{"value":"true","label":"On"},{"value":"false","label":"Off"}]},{"id":"stack","type":"enum","role":"style","label":"Template options · Stack","description":"Stack (engine parameter; see the block header comment).","default":"lastOnTop","options":[{"value":"lastOnTop","label":"Last On Top"},{"value":"firstOnTop","label":"First On Top"}]},{"id":"image1","type":"image","role":"content","label":"Media · Image 1","description":"Image on card 1 of 8. Any aspect; fitted per mediaFit.","portrays":["subject_image"],"default":"data:image/svg+xml,%3Csvg xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' width%3D'1400' height%3D'1050' viewBox%3D'0 0 1400 1050'%3E%3Crect width%3D'1400' height%3D'1050' fill%3D'%230b0b0f'%2F%3E%3Crect width%3D'1400' height%3D'1050' fill%3D'rgba(255%2C255%2C255%2C0.18)'%2F%3E%3Crect x%3D'2' y%3D'2' width%3D'1396' height%3D'1046' fill%3D'none' stroke%3D'rgba(255%2C255%2C255%2C0.14)' stroke-width%3D'4'%2F%3E%3Cpath d%3D'M252 778.05H621.6' stroke%3D'rgba(255%2C255%2C255%2C0.45)' stroke-width%3D'16.5' stroke-linecap%3D'round'%2F%3E%3Cpath d%3D'M286.65 778.05L379.05 662.55L471.45 778.05Z' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3Ccircle cx%3D'552.3' cy%3D'674.1' r%3D'34.65' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3C%2Fsvg%3E"},{"id":"image2","type":"image","role":"content","label":"Media · Image 2","description":"Image on card 2 of 8. Any aspect; fitted per mediaFit.","portrays":["subject_image"],"default":"data:image/svg+xml,%3Csvg xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' width%3D'1400' height%3D'788' viewBox%3D'0 0 1400 788'%3E%3Crect width%3D'1400' height%3D'788' fill%3D'%230b0b0f'%2F%3E%3Crect width%3D'1400' height%3D'788' fill%3D'rgba(255%2C255%2C255%2C0.18)'%2F%3E%3Crect x%3D'2' y%3D'2' width%3D'1396' height%3D'784' fill%3D'none' stroke%3D'rgba(255%2C255%2C255%2C0.14)' stroke-width%3D'4'%2F%3E%3Cpath d%3D'M252 583.908H529.376' stroke%3D'rgba(255%2C255%2C255%2C0.45)' stroke-width%3D'12.382857142857144' stroke-linecap%3D'round'%2F%3E%3Cpath d%3D'M278.004 583.908L347.348 497.228L416.692 583.908Z' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3Ccircle cx%3D'477.36800000000005' cy%3D'505.896' r%3D'26.004' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3C%2Fsvg%3E"},{"id":"image3","type":"image","role":"content","label":"Media · Image 3","description":"Image on card 3 of 8. Any aspect; fitted per mediaFit.","portrays":["subject_image"],"default":"data:image/svg+xml,%3Csvg xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' width%3D'1050' height%3D'1400' viewBox%3D'0 0 1050 1400'%3E%3Crect width%3D'1050' height%3D'1400' fill%3D'%230b0b0f'%2F%3E%3Crect width%3D'1050' height%3D'1400' fill%3D'rgba(255%2C255%2C255%2C0.18)'%2F%3E%3Crect x%3D'2' y%3D'2' width%3D'1046' height%3D'1396' fill%3D'none' stroke%3D'rgba(255%2C255%2C255%2C0.14)' stroke-width%3D'4'%2F%3E%3Cpath d%3D'M189 995.05H558.6' stroke%3D'rgba(255%2C255%2C255%2C0.45)' stroke-width%3D'16.5' stroke-linecap%3D'round'%2F%3E%3Cpath d%3D'M223.65 995.05L316.05 879.55L408.45 995.05Z' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3Ccircle cx%3D'489.3' cy%3D'891.1' r%3D'34.65' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3C%2Fsvg%3E"},{"id":"image4","type":"image","role":"content","label":"Media · Image 4","description":"Image on card 4 of 8. Any aspect; fitted per mediaFit.","portrays":["subject_image"],"default":"data:image/svg+xml,%3Csvg xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' width%3D'1400' height%3D'1400' viewBox%3D'0 0 1400 1400'%3E%3Crect width%3D'1400' height%3D'1400' fill%3D'%230b0b0f'%2F%3E%3Crect width%3D'1400' height%3D'1400' fill%3D'rgba(255%2C255%2C255%2C0.18)'%2F%3E%3Crect x%3D'2' y%3D'2' width%3D'1396' height%3D'1396' fill%3D'none' stroke%3D'rgba(255%2C255%2C255%2C0.14)' stroke-width%3D'4'%2F%3E%3Cpath d%3D'M252 1037.4H744.8' stroke%3D'rgba(255%2C255%2C255%2C0.45)' stroke-width%3D'22' stroke-linecap%3D'round'%2F%3E%3Cpath d%3D'M298.2 1037.4L421.4 883.4L544.5999999999999 1037.4Z' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3Ccircle cx%3D'652.4000000000001' cy%3D'898.8' r%3D'46.199999999999996' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3C%2Fsvg%3E"},{"id":"image5","type":"image","role":"content","label":"Media · Image 5","description":"Image on card 5 of 8. Any aspect; fitted per mediaFit.","portrays":["subject_image"],"default":"data:image/svg+xml,%3Csvg xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' width%3D'1400' height%3D'1050' viewBox%3D'0 0 1400 1050'%3E%3Crect width%3D'1400' height%3D'1050' fill%3D'%230b0b0f'%2F%3E%3Crect width%3D'1400' height%3D'1050' fill%3D'rgba(255%2C255%2C255%2C0.18)'%2F%3E%3Crect x%3D'2' y%3D'2' width%3D'1396' height%3D'1046' fill%3D'none' stroke%3D'rgba(255%2C255%2C255%2C0.14)' stroke-width%3D'4'%2F%3E%3Cpath d%3D'M252 778.05H621.6' stroke%3D'rgba(255%2C255%2C255%2C0.45)' stroke-width%3D'16.5' stroke-linecap%3D'round'%2F%3E%3Cpath d%3D'M286.65 778.05L379.05 662.55L471.45 778.05Z' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3Ccircle cx%3D'552.3' cy%3D'674.1' r%3D'34.65' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3C%2Fsvg%3E"},{"id":"image6","type":"image","role":"content","label":"Media · Image 6","description":"Image on card 6 of 8. Any aspect; fitted per mediaFit.","portrays":["subject_image"],"default":"data:image/svg+xml,%3Csvg xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' width%3D'1400' height%3D'788' viewBox%3D'0 0 1400 788'%3E%3Crect width%3D'1400' height%3D'788' fill%3D'%230b0b0f'%2F%3E%3Crect width%3D'1400' height%3D'788' fill%3D'rgba(255%2C255%2C255%2C0.18)'%2F%3E%3Crect x%3D'2' y%3D'2' width%3D'1396' height%3D'784' fill%3D'none' stroke%3D'rgba(255%2C255%2C255%2C0.14)' stroke-width%3D'4'%2F%3E%3Cpath d%3D'M252 583.908H529.376' stroke%3D'rgba(255%2C255%2C255%2C0.45)' stroke-width%3D'12.382857142857144' stroke-linecap%3D'round'%2F%3E%3Cpath d%3D'M278.004 583.908L347.348 497.228L416.692 583.908Z' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3Ccircle cx%3D'477.36800000000005' cy%3D'505.896' r%3D'26.004' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3C%2Fsvg%3E"},{"id":"image7","type":"image","role":"content","label":"Media · Image 7","description":"Image on card 7 of 8. Any aspect; fitted per mediaFit.","portrays":["subject_image"],"default":"data:image/svg+xml,%3Csvg xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' width%3D'1050' height%3D'1400' viewBox%3D'0 0 1050 1400'%3E%3Crect width%3D'1050' height%3D'1400' fill%3D'%230b0b0f'%2F%3E%3Crect width%3D'1050' height%3D'1400' fill%3D'rgba(255%2C255%2C255%2C0.18)'%2F%3E%3Crect x%3D'2' y%3D'2' width%3D'1046' height%3D'1396' fill%3D'none' stroke%3D'rgba(255%2C255%2C255%2C0.14)' stroke-width%3D'4'%2F%3E%3Cpath d%3D'M189 995.05H558.6' stroke%3D'rgba(255%2C255%2C255%2C0.45)' stroke-width%3D'16.5' stroke-linecap%3D'round'%2F%3E%3Cpath d%3D'M223.65 995.05L316.05 879.55L408.45 995.05Z' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3Ccircle cx%3D'489.3' cy%3D'891.1' r%3D'34.65' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3C%2Fsvg%3E"},{"id":"image8","type":"image","role":"content","label":"Media · Image 8","description":"Image on card 8 of 8. Any aspect; fitted per mediaFit.","portrays":["subject_image"],"default":"data:image/svg+xml,%3Csvg xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' width%3D'1400' height%3D'1400' viewBox%3D'0 0 1400 1400'%3E%3Crect width%3D'1400' height%3D'1400' fill%3D'%230b0b0f'%2F%3E%3Crect width%3D'1400' height%3D'1400' fill%3D'rgba(255%2C255%2C255%2C0.18)'%2F%3E%3Crect x%3D'2' y%3D'2' width%3D'1396' height%3D'1396' fill%3D'none' stroke%3D'rgba(255%2C255%2C255%2C0.14)' stroke-width%3D'4'%2F%3E%3Cpath d%3D'M252 1037.4H744.8' stroke%3D'rgba(255%2C255%2C255%2C0.45)' stroke-width%3D'22' stroke-linecap%3D'round'%2F%3E%3Cpath d%3D'M298.2 1037.4L421.4 883.4L544.5999999999999 1037.4Z' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3Ccircle cx%3D'652.4000000000001' cy%3D'898.8' r%3D'46.199999999999996' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3C%2Fsvg%3E"}]'
|
|
>
|
|
<head>
|
|
<title>loop-ring</title>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=1080, height=1080" />
|
|
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
|
|
<style>
|
|
* {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
}
|
|
html,
|
|
body {
|
|
width: 1080px;
|
|
height: 1080px;
|
|
overflow: hidden;
|
|
background: #0b0b0f;
|
|
}
|
|
#loop-ring-bg {
|
|
position: absolute;
|
|
inset: 0;
|
|
}
|
|
#loop-ring-stage {
|
|
position: absolute;
|
|
left: 50%;
|
|
top: 50%;
|
|
width: 0;
|
|
height: 0;
|
|
}
|
|
.card img {
|
|
display: block;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div
|
|
id="loop-ring-root"
|
|
data-composition-id="loop-ring"
|
|
data-start="0"
|
|
data-width="1080"
|
|
data-height="1080"
|
|
data-duration="16"
|
|
>
|
|
<div
|
|
id="loop-ring-loop"
|
|
class="clip"
|
|
data-start="0"
|
|
data-duration="16"
|
|
data-track-index="1"
|
|
data-layout-allow-overflow
|
|
>
|
|
<div id="loop-ring-bg"></div>
|
|
<div id="loop-ring-stage"></div>
|
|
</div>
|
|
</div>
|
|
<script>
|
|
// Shared runtime for the the reference tool loop ports. Loaded by every family engine.
|
|
// Everything here is a pure function of the HyperFrames variables + t.
|
|
//
|
|
// Reference constants (measured on the 512x512 preview library, 2026-09-08):
|
|
// Marquee planeSize -> 0.3% of frame height per unit (7 variants, all 1.54px/unit @512)
|
|
// Marquee gap -> 0.2% of frame height per unit
|
|
// 3D perspective -> CSS focal length f = 1000px * (100/perspective) at H=1080
|
|
// (3D01/02/08/11: card widths & far-card spacing match to <3%)
|
|
// Both are calibrated on a SQUARE canvas; for 16:9 we scale by H. ponytail: if the
|
|
// dashboard 16:9 reference shows width-relative scaling, flip REF below to W.
|
|
window.RF = (() => {
|
|
// Variables are injected by the inline script via RF.use(): HyperFrames wraps INLINE scripts in a
|
|
// per-composition scope where getVariables() resolves; this external file only sees the host window.
|
|
const V = {},
|
|
MEDIA = [];
|
|
const use = (vars, media) => {
|
|
Object.assign(V, vars || {});
|
|
MEDIA.splice(0, MEDIA.length, ...(media || []));
|
|
};
|
|
const root = document.querySelector("[data-composition-id]");
|
|
const W = +root.dataset.width,
|
|
H = +root.dataset.height;
|
|
const REF = H;
|
|
const num = (k, d) => (V[k] !== undefined && V[k] !== "" && !isNaN(+V[k]) ? +V[k] : d);
|
|
const str = (k, d) => (V[k] !== undefined && V[k] !== "" ? String(V[k]) : d);
|
|
const on = (k, d) => /^(true|on|1)$/i.test(str(k, d ? "on" : "off")); // spec booleans arrive as "true"/"false", enums as on/off
|
|
|
|
function pick(n) {
|
|
n = Math.max(1, Math.min(MEDIA.length, n | 0));
|
|
return MEDIA.slice(0, n);
|
|
}
|
|
// aspect (w/h) a card takes for a media item under mediaFit + cropAspectRatio
|
|
function aspect(m, fit, cropAR) {
|
|
if (fit === "natural") return m.w / m.h;
|
|
const [a, b] = String(cropAR || "1:1")
|
|
.split(":")
|
|
.map(Number);
|
|
return a && b ? a / b : 1;
|
|
}
|
|
function card(m, w, h, radius) {
|
|
const el = document.createElement("div");
|
|
el.className = "card";
|
|
el.style.cssText = `position:absolute;width:${w}px;height:${h}px;overflow:hidden;border-radius:${radius}px;`;
|
|
const img = document.createElement("img");
|
|
img.src = m.src;
|
|
img.width = Math.round(w);
|
|
img.height = Math.round(h);
|
|
img.style.cssText = `display:block;width:${w}px;height:${h}px;object-fit:cover;`;
|
|
el.appendChild(img);
|
|
return el;
|
|
}
|
|
function background(el) {
|
|
// the editor's Background panel: type gradient | color | image, gradient start/end, grain (noise %), opacity, image blur
|
|
const type = str("backgroundType", "gradient");
|
|
const c = str("backgroundColor", "#0b0b0f");
|
|
el.style.background = c;
|
|
if (type === "gradient") {
|
|
const g1 = str("backgroundGradientStart", "#111111"),
|
|
g2 = str("backgroundGradientEnd", "#444444");
|
|
el.style.background = `linear-gradient(180deg, ${g1}, ${g2})`;
|
|
} else if (type === "image") {
|
|
const im = V.backgroundImage,
|
|
src = im && typeof im === "object" ? im.url || im.src || "" : im || "";
|
|
if (src) {
|
|
el.style.background = `${c} url("${src}") center / cover no-repeat`;
|
|
const b = num("blurAmount", 0);
|
|
if (b > 0) el.style.filter = `blur(${b}px)`;
|
|
}
|
|
}
|
|
el.style.opacity = String(Math.max(0, Math.min(1, num("backgroundOpacity", 1))));
|
|
const noise = num("backgroundGradientNoise", 0);
|
|
if (noise > 0) {
|
|
const g = document.createElement("div");
|
|
g.style.cssText = `position:absolute;inset:0;pointer-events:none;mix-blend-mode:overlay;opacity:${((noise / 100) * 0.7).toFixed(3)};background:url("data:image/svg+xml,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20width%3D'256'%20height%3D'256'%3E%3Cfilter%20id%3D'n'%3E%3CfeTurbulence%20type%3D'fractalNoise'%20baseFrequency%3D'0.9'%20numOctaves%3D'3'%20stitchTiles%3D'stitch'%20seed%3D'7'%2F%3E%3CfeColorMatrix%20values%3D'0%200%200%200%200.5%200%200%200%200%200.5%200%200%200%200%200.5%200%200%200%201%200'%2F%3E%3C%2Ffilter%3E%3Crect%20width%3D'256'%20height%3D'256'%20filter%3D'url(%23n)'%2F%3E%3C%2Fsvg%3E") 0 0 / 256px 256px repeat`;
|
|
el.appendChild(g);
|
|
}
|
|
}
|
|
const expoInOut = (x) =>
|
|
x <= 0
|
|
? 0
|
|
: x >= 1
|
|
? 1
|
|
: x < 0.5
|
|
? Math.pow(2, 20 * x - 10) / 2
|
|
: (2 - Math.pow(2, -20 * x + 10)) / 2;
|
|
const frac = (x) => x - Math.floor(x);
|
|
return {
|
|
V,
|
|
use,
|
|
W,
|
|
H,
|
|
REF,
|
|
num,
|
|
str,
|
|
on,
|
|
MEDIA,
|
|
pick,
|
|
aspect,
|
|
card,
|
|
background,
|
|
expoInOut,
|
|
frac,
|
|
};
|
|
})();
|
|
</script>
|
|
<script>
|
|
(async () => {
|
|
// Ring loop family — N cards evenly spaced on an ellipse, each rotated to follow the
|
|
// curve (tangent), the whole ring spinning continuously.
|
|
// Measured on the 512 preview (2026-09-08, 8 cards): ring radius 163.8px = radiusX% * H (32% -> 163.8);
|
|
// card 102.5px square = scaleIntensity * H (0.2 -> 102.4; total card area 83086px^2 / 8 with r=11 corners);
|
|
// spin linear 4.5deg per 6 frames = 360deg per 16 s = `repeat` revolutions per loop, anticlockwise on screen;
|
|
// cards axis-aligned at 0/90/180/270deg and turned 45deg at 45deg (tangent=true); card sizes constant over the loop.
|
|
// Guesses: tangent card rotation = ring angle + 90 (top of the image faces outward; indistinguishable on the
|
|
// square placeholders); natural fit fixes card HEIGHT = scaleIntensity*H; stack = z-order only.
|
|
// media list [{src,w,h}] is inlined by build.mjs; HyperFrames scopes inline scripts, so hand both to the shared lib explicitly
|
|
const __V = Object.assign(
|
|
{},
|
|
(window.__hyperframes && window.__hyperframes.getVariables
|
|
? window.__hyperframes.getVariables()
|
|
: null) || {},
|
|
{ loopDuration: 16, loopRepeat: 2, imageCount: 8 },
|
|
);
|
|
const __PH = [
|
|
"data:image/svg+xml,%3Csvg xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' width%3D'1400' height%3D'1050' viewBox%3D'0 0 1400 1050'%3E%3Crect width%3D'1400' height%3D'1050' fill%3D'%230b0b0f'%2F%3E%3Crect width%3D'1400' height%3D'1050' fill%3D'rgba(255%2C255%2C255%2C0.18)'%2F%3E%3Crect x%3D'2' y%3D'2' width%3D'1396' height%3D'1046' fill%3D'none' stroke%3D'rgba(255%2C255%2C255%2C0.14)' stroke-width%3D'4'%2F%3E%3Cpath d%3D'M252 778.05H621.6' stroke%3D'rgba(255%2C255%2C255%2C0.45)' stroke-width%3D'16.5' stroke-linecap%3D'round'%2F%3E%3Cpath d%3D'M286.65 778.05L379.05 662.55L471.45 778.05Z' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3Ccircle cx%3D'552.3' cy%3D'674.1' r%3D'34.65' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3C%2Fsvg%3E",
|
|
"data:image/svg+xml,%3Csvg xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' width%3D'1400' height%3D'788' viewBox%3D'0 0 1400 788'%3E%3Crect width%3D'1400' height%3D'788' fill%3D'%230b0b0f'%2F%3E%3Crect width%3D'1400' height%3D'788' fill%3D'rgba(255%2C255%2C255%2C0.18)'%2F%3E%3Crect x%3D'2' y%3D'2' width%3D'1396' height%3D'784' fill%3D'none' stroke%3D'rgba(255%2C255%2C255%2C0.14)' stroke-width%3D'4'%2F%3E%3Cpath d%3D'M252 583.908H529.376' stroke%3D'rgba(255%2C255%2C255%2C0.45)' stroke-width%3D'12.382857142857144' stroke-linecap%3D'round'%2F%3E%3Cpath d%3D'M278.004 583.908L347.348 497.228L416.692 583.908Z' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3Ccircle cx%3D'477.36800000000005' cy%3D'505.896' r%3D'26.004' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3C%2Fsvg%3E",
|
|
"data:image/svg+xml,%3Csvg xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' width%3D'1050' height%3D'1400' viewBox%3D'0 0 1050 1400'%3E%3Crect width%3D'1050' height%3D'1400' fill%3D'%230b0b0f'%2F%3E%3Crect width%3D'1050' height%3D'1400' fill%3D'rgba(255%2C255%2C255%2C0.18)'%2F%3E%3Crect x%3D'2' y%3D'2' width%3D'1046' height%3D'1396' fill%3D'none' stroke%3D'rgba(255%2C255%2C255%2C0.14)' stroke-width%3D'4'%2F%3E%3Cpath d%3D'M189 995.05H558.6' stroke%3D'rgba(255%2C255%2C255%2C0.45)' stroke-width%3D'16.5' stroke-linecap%3D'round'%2F%3E%3Cpath d%3D'M223.65 995.05L316.05 879.55L408.45 995.05Z' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3Ccircle cx%3D'489.3' cy%3D'891.1' r%3D'34.65' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3C%2Fsvg%3E",
|
|
"data:image/svg+xml,%3Csvg xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' width%3D'1400' height%3D'1400' viewBox%3D'0 0 1400 1400'%3E%3Crect width%3D'1400' height%3D'1400' fill%3D'%230b0b0f'%2F%3E%3Crect width%3D'1400' height%3D'1400' fill%3D'rgba(255%2C255%2C255%2C0.18)'%2F%3E%3Crect x%3D'2' y%3D'2' width%3D'1396' height%3D'1396' fill%3D'none' stroke%3D'rgba(255%2C255%2C255%2C0.14)' stroke-width%3D'4'%2F%3E%3Cpath d%3D'M252 1037.4H744.8' stroke%3D'rgba(255%2C255%2C255%2C0.45)' stroke-width%3D'22' stroke-linecap%3D'round'%2F%3E%3Cpath d%3D'M298.2 1037.4L421.4 883.4L544.5999999999999 1037.4Z' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3Ccircle cx%3D'652.4000000000001' cy%3D'898.8' r%3D'46.199999999999996' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3C%2Fsvg%3E",
|
|
"data:image/svg+xml,%3Csvg xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' width%3D'1400' height%3D'1050' viewBox%3D'0 0 1400 1050'%3E%3Crect width%3D'1400' height%3D'1050' fill%3D'%230b0b0f'%2F%3E%3Crect width%3D'1400' height%3D'1050' fill%3D'rgba(255%2C255%2C255%2C0.18)'%2F%3E%3Crect x%3D'2' y%3D'2' width%3D'1396' height%3D'1046' fill%3D'none' stroke%3D'rgba(255%2C255%2C255%2C0.14)' stroke-width%3D'4'%2F%3E%3Cpath d%3D'M252 778.05H621.6' stroke%3D'rgba(255%2C255%2C255%2C0.45)' stroke-width%3D'16.5' stroke-linecap%3D'round'%2F%3E%3Cpath d%3D'M286.65 778.05L379.05 662.55L471.45 778.05Z' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3Ccircle cx%3D'552.3' cy%3D'674.1' r%3D'34.65' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3C%2Fsvg%3E",
|
|
"data:image/svg+xml,%3Csvg xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' width%3D'1400' height%3D'788' viewBox%3D'0 0 1400 788'%3E%3Crect width%3D'1400' height%3D'788' fill%3D'%230b0b0f'%2F%3E%3Crect width%3D'1400' height%3D'788' fill%3D'rgba(255%2C255%2C255%2C0.18)'%2F%3E%3Crect x%3D'2' y%3D'2' width%3D'1396' height%3D'784' fill%3D'none' stroke%3D'rgba(255%2C255%2C255%2C0.14)' stroke-width%3D'4'%2F%3E%3Cpath d%3D'M252 583.908H529.376' stroke%3D'rgba(255%2C255%2C255%2C0.45)' stroke-width%3D'12.382857142857144' stroke-linecap%3D'round'%2F%3E%3Cpath d%3D'M278.004 583.908L347.348 497.228L416.692 583.908Z' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3Ccircle cx%3D'477.36800000000005' cy%3D'505.896' r%3D'26.004' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3C%2Fsvg%3E",
|
|
"data:image/svg+xml,%3Csvg xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' width%3D'1050' height%3D'1400' viewBox%3D'0 0 1050 1400'%3E%3Crect width%3D'1050' height%3D'1400' fill%3D'%230b0b0f'%2F%3E%3Crect width%3D'1050' height%3D'1400' fill%3D'rgba(255%2C255%2C255%2C0.18)'%2F%3E%3Crect x%3D'2' y%3D'2' width%3D'1046' height%3D'1396' fill%3D'none' stroke%3D'rgba(255%2C255%2C255%2C0.14)' stroke-width%3D'4'%2F%3E%3Cpath d%3D'M189 995.05H558.6' stroke%3D'rgba(255%2C255%2C255%2C0.45)' stroke-width%3D'16.5' stroke-linecap%3D'round'%2F%3E%3Cpath d%3D'M223.65 995.05L316.05 879.55L408.45 995.05Z' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3Ccircle cx%3D'489.3' cy%3D'891.1' r%3D'34.65' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3C%2Fsvg%3E",
|
|
"data:image/svg+xml,%3Csvg xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' width%3D'1400' height%3D'1400' viewBox%3D'0 0 1400 1400'%3E%3Crect width%3D'1400' height%3D'1400' fill%3D'%230b0b0f'%2F%3E%3Crect width%3D'1400' height%3D'1400' fill%3D'rgba(255%2C255%2C255%2C0.18)'%2F%3E%3Crect x%3D'2' y%3D'2' width%3D'1396' height%3D'1396' fill%3D'none' stroke%3D'rgba(255%2C255%2C255%2C0.14)' stroke-width%3D'4'%2F%3E%3Cpath d%3D'M252 1037.4H744.8' stroke%3D'rgba(255%2C255%2C255%2C0.45)' stroke-width%3D'22' stroke-linecap%3D'round'%2F%3E%3Cpath d%3D'M298.2 1037.4L421.4 883.4L544.5999999999999 1037.4Z' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3Ccircle cx%3D'652.4000000000001' cy%3D'898.8' r%3D'46.199999999999996' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3C%2Fsvg%3E",
|
|
];
|
|
const __MEDIA = await Promise.all(
|
|
["image1", "image2", "image3", "image4", "image5", "image6", "image7", "image8"].map(
|
|
async (id, i) => {
|
|
const v = __V[id];
|
|
const src = (v && typeof v === "object" ? v.url || v.src : v) || __PH[i];
|
|
const img = new Image();
|
|
img.src = src;
|
|
try {
|
|
await img.decode();
|
|
} catch (e) {}
|
|
return { src, w: img.naturalWidth || 1400, h: img.naturalHeight || 1050 };
|
|
},
|
|
),
|
|
);
|
|
RF.use(__V, __MEDIA);
|
|
const { W, H, REF, num, str, on, pick, aspect, card, background, frac } = RF;
|
|
const DUR = num("loopDuration", 16);
|
|
const FIT = str("mediaFit", "natural");
|
|
const CROP_AR = str("cropAspectRatio", "1:1");
|
|
const SIZE = num("scaleIntensity", 0.2) * REF;
|
|
const RX = (num("radiusX", 32) / 100) * REF;
|
|
const RY = (num("radiusY", 32) / 100) * REF;
|
|
const REPEAT = Math.max(1, Math.round(num("repeat", 1)));
|
|
const TANGENT = /^(true|on|1)$/i.test(str("tangent", "true")); // spec boolean arrives as "true"/"false"
|
|
const DIR = str("direction", "anticlockwise") === "clockwise" ? 1 : -1; // CSS angles are clockwise-positive
|
|
const LAST_TOP = str("stack", "lastOnTop") === "lastOnTop";
|
|
const RADIUS = (num("borderRadius", 24) * REF) / 1080;
|
|
const IMAGES = pick(num("imageCount", 16));
|
|
const N = IMAGES.length;
|
|
|
|
document.getElementById("loop-ring-loop").dataset.duration = DUR;
|
|
background(document.getElementById("loop-ring-bg"));
|
|
const stage = document.getElementById("loop-ring-stage");
|
|
const cards = [];
|
|
for (let i = 0; i < N; i++) {
|
|
const m = IMAGES[i],
|
|
a = aspect(m, FIT, CROP_AR);
|
|
const h = SIZE,
|
|
w = SIZE * a;
|
|
const c = card(m, w, h, RADIUS);
|
|
c.style.left = -w / 2 + "px";
|
|
c.style.top = -h / 2 + "px";
|
|
c.style.zIndex = LAST_TOP ? i + 1 : N - i;
|
|
stage.appendChild(c);
|
|
cards.push({ el: c, theta: (i * 360) / N });
|
|
}
|
|
function renderAt(t) {
|
|
const spin = DIR * REPEAT * 360 * frac(t);
|
|
for (const c of cards) {
|
|
const phi = c.theta + spin,
|
|
r = (phi * Math.PI) / 180;
|
|
const x = RX * Math.cos(r),
|
|
y = RY * Math.sin(r);
|
|
c.el.style.transform =
|
|
`translate(${x}px,${y}px)` + (TANGENT ? ` rotate(${phi + 90}deg)` : "");
|
|
}
|
|
}
|
|
// one paused linear tween drives t; every frame is recomputed from t alone (seek-safe)
|
|
const state = { t: 0 };
|
|
const tl = gsap.timeline({ paused: true });
|
|
tl.to(state, { t: 1, duration: DUR, ease: "none", onUpdate: () => renderAt(state.t) }, 0);
|
|
renderAt(0);
|
|
window.__timelines = window.__timelines || {};
|
|
window.__timelines["loop-ring"] = tl;
|
|
})();
|
|
</script>
|
|
</body>
|
|
</html>
|