mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-08 02:36:10 +00:00
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.
298 lines
12 KiB
Plaintext
298 lines
12 KiB
Plaintext
---
|
|
title: "SVG Stroke Trace"
|
|
description: "An authored SVG path draws from its measured length, holds with subtle drift, and optionally fills when the path is closed."
|
|
---
|
|
|
|
import { InstallCommand } from "/snippets/install-command.jsx";
|
|
|
|
<iframe
|
|
className="w-full aspect-video rounded-xl border-0 bg-zinc-100 dark:bg-zinc-800"
|
|
title="svg-stroke-trace preview"
|
|
loading="lazy"
|
|
srcDoc={`<!doctype html><html><head><meta charset="utf-8"><style>html,body{margin:0;height:100%;overflow:hidden;background:transparent}hyperframes-player{display:block;width:100%;height:100%}</style><script src="https://cdn.jsdelivr.net/npm/@hyperframes/player@0.7/dist/hyperframes-player.global.js"><\/script></head><body><script>fetch("/public/catalog/components/svg-stroke-trace.json").then(function(r){return r.json()}).then(function(d){var p=document.createElement("hyperframes-player");p.setAttribute("srcdoc",d.html);p.setAttribute("controls","");p.setAttribute("autoplay","");p.setAttribute("loop","");p.setAttribute("muted","");p.setAttribute("poster","https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/svg-stroke-trace.png");document.body.appendChild(p)});<\/script></body></html>`}
|
|
/>
|
|
|
|
## Install
|
|
|
|
<InstallCommand command="npx hyperframes add svg-stroke-trace" />
|
|
|
|
That writes one file: `compositions/components/svg-stroke-trace.html`.
|
|
|
|
## Paste it into your composition
|
|
|
|
Open `compositions/components/svg-stroke-trace.html` and copy what is inside into your own composition.
|
|
|
|
A component has no size or duration of its own. It takes both from the composition
|
|
you paste it into.
|
|
|
|
## Variables
|
|
|
|
Every one of these has a default, so the piece works untouched. Set the ones you
|
|
want to change on the element:
|
|
|
|
| Variable | Default | Accepts | What it does |
|
|
| --- | --- | --- | --- |
|
|
| `path` | `M 92 328 C 178 142 292 138 366 276 C 430 396 500 414 558 262 C 622 94 724 112 786 274 C 836 406 894 376 930 194` | string | SVG path data for the authored mark. |
|
|
| `stroke_width` | `12` | 2 to 32, step 1 | Width of the traced stroke in SVG viewBox units. |
|
|
| `accent` | `green` | `green`, `blue`, `violet` | Color used by the trace and optional fill. |
|
|
| `exit` | `none` | `none`, `fade`, `up` | Optional departure. Default none: the finished mark holds until the frame cuts. |
|
|
|
|
Set them with `data-variable-values` on the element that mounts it. These are the
|
|
defaults, so this behaves exactly like the preview above until you change one:
|
|
|
|
```html wrap
|
|
<div
|
|
data-composition-id="svg-stroke-trace"
|
|
data-composition-src="compositions/components/svg-stroke-trace.html"
|
|
data-variable-values='{"path":"M 92 328 C 178 142 292 138 366 276 C 430 396 500 414 558 262 C 622 94 724 112 786 274 C 836 406 894 376 930 194","stroke_width":12,"accent":"green","exit":"none"}'
|
|
></div>
|
|
```
|
|
|
|
## Source
|
|
|
|
<Accordion title={`svg-stroke-trace.html`}>
|
|
|
|
```html
|
|
<!doctype html>
|
|
<!--
|
|
svg-stroke-trace: HyperFrames video primitive (intros and reveals)
|
|
|
|
An authored SVG path draws from start to finish using its measured length.
|
|
Closed paths receive a restrained fill after the stroke completes. Open
|
|
paths remain stroke-only, which keeps signatures and waves clean.
|
|
|
|
Variables:
|
|
- path (string): SVG path data for the traced mark.
|
|
- stroke_width (number, default 12): stroke width in viewBox units.
|
|
- accent (green | blue | violet, default green): trace and fill color.
|
|
- exit (none | fade | up, default "none"): optional departure.
|
|
|
|
Envelope, fixed IN and OUT with elastic HOLD only:
|
|
IN_BASE = 1.85s, stroke draw followed by optional closed-path fill
|
|
HOLD = max(0, D - IN - OUT), one subtle vertical drift cycle
|
|
OUT_BASE = 0.45s departure when exit is fade or up, 0s when exit is
|
|
none (the default: the finished mark holds until the cut)
|
|
If D is shorter than IN_BASE + OUT_BASE, IN and OUT compress together.
|
|
The timeline is never time-scaled.
|
|
|
|
Mount contract: the runtime clones only this template. #root fills the host
|
|
box, establishes the container query basis, has no data-width or data-height,
|
|
and registers one paused timeline under the literal svg-stroke-trace key.
|
|
-->
|
|
<html
|
|
lang="en"
|
|
data-composition-id="svg-stroke-trace"
|
|
data-composition-duration="3.5"
|
|
data-composition-variables='[
|
|
{ "id": "path", "type": "string", "role": "content", "label": "Path", "description": "SVG path data for the authored mark.", "default": "M 92 328 C 178 142 292 138 366 276 C 430 396 500 414 558 262 C 622 94 724 112 786 274 C 836 406 894 376 930 194" },
|
|
{ "id": "stroke_width", "type": "number", "role": "style", "label": "Stroke width", "description": "Width of the traced stroke in SVG viewBox units.", "default": 12, "min": 2, "max": 32, "step": 1 },
|
|
{ "id": "accent", "type": "enum", "role": "style", "label": "Accent", "description": "Color used by the trace and optional fill.", "default": "green", "options": [{ "value": "green", "label": "Green" }, { "value": "blue", "label": "Blue" }, { "value": "violet", "label": "Violet" }] },
|
|
{ "id": "exit", "type": "enum", "role": "timing", "label": "Exit", "description": "Optional departure. Default none: the finished mark holds until the frame cuts.", "default": "none", "options": [{ "value": "none", "label": "None" }, { "value": "fade", "label": "Fade" }, { "value": "up", "label": "Up" }] }
|
|
]'
|
|
>
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<title>SVG Stroke Trace</title>
|
|
</head>
|
|
<body>
|
|
<template>
|
|
<div id="root" data-composition-id="svg-stroke-trace" data-duration="3.5" data-fps="30">
|
|
<style>
|
|
*,
|
|
*::before,
|
|
*::after {
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
#root {
|
|
position: absolute;
|
|
inset: 0;
|
|
overflow: hidden;
|
|
container-type: size;
|
|
isolation: isolate;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.sst-clip {
|
|
position: absolute;
|
|
inset: 0;
|
|
display: grid;
|
|
place-items: center;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.sst-stage {
|
|
width: 88cqw;
|
|
height: 70cqh;
|
|
opacity: 0;
|
|
will-change: transform, opacity;
|
|
}
|
|
|
|
.sst-svg {
|
|
display: block;
|
|
width: 100%;
|
|
height: 100%;
|
|
overflow: visible;
|
|
}
|
|
|
|
.sst-fill,
|
|
.sst-stroke {
|
|
stroke-linecap: round;
|
|
stroke-linejoin: round;
|
|
}
|
|
|
|
.sst-fill {
|
|
fill: var(--sst-accent);
|
|
stroke: none;
|
|
opacity: 0;
|
|
}
|
|
|
|
.sst-stroke {
|
|
fill: none;
|
|
stroke: var(--sst-accent);
|
|
filter: drop-shadow(
|
|
0 0 1.8cqmin color-mix(in srgb, var(--sst-accent) 34%, transparent)
|
|
);
|
|
}
|
|
</style>
|
|
|
|
<div
|
|
id="svg-stroke-trace-clip"
|
|
class="sst-clip clip"
|
|
data-start="0"
|
|
data-duration="3.5"
|
|
data-track-index="0"
|
|
>
|
|
<div class="sst-stage" role="img">
|
|
<svg
|
|
class="sst-svg"
|
|
viewBox="0 0 1024 520"
|
|
preserveAspectRatio="xMidYMid meet"
|
|
aria-hidden="true"
|
|
>
|
|
<path class="sst-fill"></path>
|
|
<path class="sst-stroke"></path>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
|
|
<script>
|
|
(function () {
|
|
"use strict";
|
|
|
|
var DEFAULT_PATH =
|
|
"M 92 328 C 178 142 292 138 366 276 C 430 396 500 414 558 262 C 622 94 724 112 786 274 C 836 406 894 376 930 194";
|
|
var root = document.getElementById("root");
|
|
var stage = root.querySelector(".sst-stage");
|
|
var fillPath = root.querySelector(".sst-fill");
|
|
var strokePath = root.querySelector(".sst-stroke");
|
|
var vars =
|
|
window.__hyperframes && window.__hyperframes.getVariables
|
|
? window.__hyperframes.getVariables()
|
|
: {};
|
|
|
|
var suppliedPath = vars.path == null ? DEFAULT_PATH : String(vars.path).trim();
|
|
var pathData = suppliedPath || DEFAULT_PATH;
|
|
var widthValue = vars.stroke_width;
|
|
var strokeWidth = Number.isFinite(widthValue)
|
|
? Math.max(2, Math.min(32, widthValue))
|
|
: 12;
|
|
var accentColors = {
|
|
green: "var(--brand, #71f5a7)",
|
|
blue: "var(--accent, #61a8ff)",
|
|
violet: "var(--accent-2, #c5a3ff)",
|
|
};
|
|
var accent = Object.prototype.hasOwnProperty.call(accentColors, vars.accent)
|
|
? vars.accent
|
|
: "green";
|
|
var exit = vars.exit === "fade" || vars.exit === "up" ? vars.exit : "none";
|
|
|
|
root.style.setProperty("--sst-accent", accentColors[accent]);
|
|
fillPath.setAttribute("d", pathData);
|
|
strokePath.setAttribute("d", pathData);
|
|
strokePath.setAttribute("stroke-width", String(strokeWidth));
|
|
stage.setAttribute("aria-label", "Authored SVG path tracing from start to finish");
|
|
|
|
var pathLength = strokePath.getTotalLength();
|
|
strokePath.style.strokeDasharray = pathLength + " " + pathLength;
|
|
strokePath.style.strokeDashoffset = String(pathLength);
|
|
|
|
// A trailing close command is the single owner of fill behavior.
|
|
// Open signatures and waves never receive an implied closing fill.
|
|
var fillEnabled = /[zZ]\s*$/.test(pathData);
|
|
|
|
var IN_BASE = 1.85;
|
|
var OUT_BASE = exit === "none" ? 0 : 0.45;
|
|
var DRAW_SHARE = 0.84;
|
|
var duration = Math.max(0.001, parseFloat(root.dataset.duration || "3.5"));
|
|
var fixedTotal = Math.max(0.001, IN_BASE + OUT_BASE);
|
|
var phaseScale = duration < fixedTotal ? duration / fixedTotal : 1;
|
|
var IN = IN_BASE * phaseScale;
|
|
var OUT = OUT_BASE * phaseScale;
|
|
var DRAW = IN * DRAW_SHARE;
|
|
var FILL = IN - DRAW;
|
|
var HOLD = Math.max(0, duration - IN - OUT);
|
|
var HOLD_START = IN;
|
|
var OUT_START = IN + HOLD;
|
|
|
|
var tl = gsap.timeline({ paused: true });
|
|
|
|
// IN: the path measurement owns both dash values, so every authored
|
|
// path draws exactly once regardless of its geometry.
|
|
tl.to(stage, { opacity: 1, duration: Math.min(0.28, DRAW), ease: "power2.out" }, 0);
|
|
tl.to(strokePath, { strokeDashoffset: 0, duration: DRAW, ease: "sine.inOut" }, 0);
|
|
|
|
if (fillEnabled) {
|
|
tl.to(fillPath, { opacity: 0.16, duration: FILL, ease: "power2.out" }, DRAW);
|
|
}
|
|
|
|
// HOLD: one finite drift cycle keeps longer mounts alive.
|
|
if (HOLD > 0) {
|
|
tl.to(
|
|
stage,
|
|
{
|
|
y: "-0.7cqh",
|
|
duration: HOLD / 2,
|
|
ease: "sine.inOut",
|
|
yoyo: true,
|
|
repeat: 1,
|
|
},
|
|
HOLD_START,
|
|
);
|
|
}
|
|
|
|
// OUT: optional departure; exit none holds until the frame cuts.
|
|
if (exit === "up") {
|
|
tl.to(
|
|
stage,
|
|
{ opacity: 0, y: "-2.4cqh", duration: OUT, ease: "power2.in" },
|
|
OUT_START,
|
|
);
|
|
} else if (exit === "fade") {
|
|
tl.to(stage, { opacity: 0, duration: OUT, ease: "power2.in" }, OUT_START);
|
|
}
|
|
|
|
tl.seek(0);
|
|
window.__timelines = window.__timelines || {};
|
|
window.__timelines["svg-stroke-trace"] = tl;
|
|
})();
|
|
</script>
|
|
</div>
|
|
</template>
|
|
</body>
|
|
</html>
|
|
```
|
|
|
|
</Accordion>
|
|
|
|
{/* hf:generated-footer */}
|
|
|
|
Tagged `motion-primitive` `intro` `reveal` `svg` `stroke` `line-draw`.
|
|
|
|
## Related topics
|
|
|
|
- [Browse the complete Catalog](/catalog)
|
|
- [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
|
|
- [Build a richer composition](/go-further)
|