Setting the file. One moment. Ref Circle Highlight · Motion Graphics · heygen-com/hyperframes · Skills Docs28.10
Module · kinetic-type
Raw file
samples/asset-fusion/_ref-circle-highlight.html
HTML·306 lines·10 KB
2) render with even width/height (ODD width => ffmpeg encode fails!)
10 BOX comes from grounding/PROTOCOL.md (locate.mjs loop) — NEVER eyeball coords.
11 Everything (wash pool, double ring, connector, callout, brackets) is
12 computed from BOX. Set MODE='full' (ring+label+brackets) or 'circle'
13 (ring only). Gotchas are flagged inline.
14 ============================================================ -->
15 <style>
16 :root {
17 --ink: #0a0c10;
18 --amber: #f2c078;
19 --paper: #f5efe6;
20 }
21 * {
22 margin: 0;
23 padding: 0;
24 box-sizing: border-box;
25 }
26 html,
27 body {
28 overflow: hidden;
29 background: var(--ink);
30 }
31 #stage {
32 position: relative;
33 overflow: hidden;
34 background: #fff;
35 font-family:
36 "Inter",
37 -apple-system,
38 system-ui,
39 sans-serif;
40 }
41 .scene {
42 position: absolute;
43 inset: 0;
44 }
45 #shot {
46 position: absolute;
47 inset: 0;
48 width: 100%;
49 height: 100%;
50 object-fit: cover;
51 display: block;
52 z-index: 0;
53 }
54 #wash {
55 position: absolute;
56 inset: 0;
57 z-index: 1;
58 }
59 #ann {
60 position: absolute;
61 inset: 0;
62 z-index: 3;
63 pointer-events: none;
64 overflow: visible;
65 }
66 #ring-a,
67 #ring-b,
68 #conn {
69 stroke: var(--amber);
70 fill: none;
71 stroke-linecap: round;
72 }
73 #ring-a {
74 stroke-width: 8;
75 filter: drop-shadow(0 0 12px rgba(242, 192, 120, 0.5));
76 }
77 #ring-b {
78 stroke-width: 5;
79 opacity: 0.85;
80 }
81 #conn {
82 stroke-width: 5;
83 }
84 #callout {
85 position: absolute;
86 z-index: 4;
87 display: inline-flex;
88 align-items: center;
89 gap: 14px;
90 padding: 15px 28px;
91 background: var(--ink);
92 border: 2px solid var(--amber);
93 border-radius: 999px;
94 box-shadow: 0 14px 40px rgba(8, 9, 12, 0.6);
95 transform-origin: left center;
96 }
97 #callout .dot {
98 width: 12px;
99 height: 12px;
100 border-radius: 50%;
101 background: var(--amber);
102 }
103 #callout .label {
104 font-size: 33px;
105 font-weight: 700;
106 letter-spacing: 0.14em;
107 color: var(--paper);
108 text-transform: uppercase;
109 white-space: nowrap;
110 }
111 .tick {
112 position: absolute;
113 z-index: 5;
114 width: 56px;
115 height: 56px;
116 border: 3px solid var(--paper);
117 opacity: 0.55;
118 }
119 .tick.tl {
120 top: 44px;
121 left: 44px;
122 border-right: none;
123 border-bottom: none;
124 }
125 .tick.tr {
126 top: 44px;
127 right: 44px;
128 border-left: none;
129 border-bottom: none;
130 }
131 .tick.bl {
132 bottom: 44px;
133 left: 44px;
134 border-right: none;
135 border-top: none;
136 }
137 .tick.br {
138 bottom: 44px;
139 right: 44px;
140 border-left: none;
141 border-top: none;
142 }
143 #scan {
144 position: absolute;
145 inset: 0;
146 z-index: 5;
147 pointer-events: none;
148 mix-blend-mode: overlay;
149 background: repeating-linear-gradient(
150 to bottom,
151 rgba(245, 239, 230, 0.05) 0 1px,
152 transparent 1px 4px
153 );
154 }
155 </style>
156 </head>
157 <body>
158 <!-- ⚠ data-width/height + the #stage width/height MUST be set STATICALLY here
159 (the renderer's StaticGuard reads them at compile time, BEFORE JS runs — JS
160 setAttribute is too late and the render falls back to portrait 1080×1920).
161 Keep these EQUAL to CFG.W/CFG.H below, and EVEN. -->
162 <div
163 id="stage"
164 style="width: 1998px; height: 1368px"
165 data-composition-id="circle-highlight"
166 data-start="0"
167 data-duration="6"
168 data-fps="30"
169 data-width="1998"
170 data-height="1368"
171 >
172 <div class="scene clip" data-start="0" data-duration="6" data-track-index="0">
173 <img id="shot" alt="asset" />
174 <div id="wash"></div>
175 <svg id="ann">
176 <ellipse id="ring-a" />
177 <ellipse id="ring-b" />
178 <path id="conn" />
179 </svg>
180 <div id="callout"><span class="dot"></span><span class="label"></span></div>
181 <div id="scan"></div>
182 <span class="tick tl"></span><span class="tick tr"></span><span class="tick bl"></span
183 ><span class="tick br"></span>
184 </div>
185 </div>
186
187 <script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
188 <script>
189 // ====== CONFIG — set these (BOX from the locate protocol) ======
190 const CFG = {
191 asset: "assets/shot.png",
192 W: 1998,
193 H: 1368, // EVEN dims (odd width => encode fails)
194 box: [0.244, 0.277, 0.607, 0.425], // [x0,y0,x1,y1] normalized, from locate.mjs
195 label: "HyperFrames",
196 mode: "full", // 'full' = ring+connector+label+brackets+scan ; 'circle' = ring only
197 };
198 // =====================================================
199 const S = document.getElementById("stage"); // dims are STATIC in the HTML above (must equal CFG.W/H)
200 document.getElementById("shot").src = CFG.asset;
201 const ann = document.getElementById("ann");
202 ann.setAttribute("viewBox", `0 0 ${CFG.W} ${CFG.H}`);
203 ann.setAttribute("width", CFG.W);
204 ann.setAttribute("height", CFG.H);
205
206 // geometry from BOX
207 const [x0, y0, x1, y1] = CFG.box;
208 const cx = ((x0 + x1) / 2) * CFG.W,
209 cy = ((y0 + y1) / 2) * CFG.H;
210 const bw = (x1 - x0) * CFG.W,
211 bh = (y1 - y0) * CFG.H;
212 const rxA = bw / 2 + 40,
213 ryA = bh / 2 + 42;
214 // wash: a lit radial pool centered on the box, dark elsewhere
215 const px = (((x0 + x1) / 2) * 100).toFixed(1),
216 py = (((y0 + y1) / 2) * 100).toFixed(1);
217 const ex = (((x1 - x0) / 2) * 1.15 * 100).toFixed(1),
218 ey = (((y1 - y0) / 2) * 1.4 * 100).toFixed(1);
219 document.getElementById("wash").style.background =
220 `radial-gradient(ellipse ${ex}% ${ey}% at ${px}% ${py}%, rgba(8,9,12,0) 0%, rgba(8,9,12,.1) 42%, rgba(8,9,12,.66) 70%, rgba(8,9,12,.9) 100%)`;
221 const ra = document.getElementById("ring-a"),
222 rb = document.getElementById("ring-b");
223 ra.setAttribute("cx", cx);
224 ra.setAttribute("cy", cy);
225 ra.setAttribute("rx", rxA);
226 ra.setAttribute("ry", ryA);
227 ra.setAttribute("transform", `rotate(-4 ${cx} ${cy})`);
228 rb.setAttribute("cx", cx);
229 rb.setAttribute("cy", cy);
230 rb.setAttribute("rx", rxA - 20);
231 rb.setAttribute("ry", ryA - 16);
232 rb.setAttribute("transform", `rotate(3 ${cx} ${cy})`);
233 // callout: above-right of the box (flip left if box is on the right edge)
234 const right = cx < CFG.W * 0.62;
235 const co = document.getElementById("callout");
236 co.querySelector(".label").textContent = CFG.label;
237 const coX = right ? Math.min(cx + rxA + 10, CFG.W - 360) : Math.max(cx - rxA - 360, 30);
238 const coY = Math.max(cy - ryA - 60, 40);
239 co.style.left = coX + "px";
240 co.style.top = coY + "px";
241 const conn = document.getElementById("conn");
242 const ex0 = cx + (right ? rxA * 0.75 : -rxA * 0.75),
243 ey0 = cy - ryA * 0.7;
244 conn.setAttribute(
245 "d",
246 `M ${ex0} ${ey0} C ${(ex0 + coX) / 2} ${ey0 - 50}, ${coX - 20} ${coY + 70}, ${coX + 2} ${coY + 26}`,
247 );
248
249 // ====== seek-safe timeline ======
250 window.__timelines = window.__timelines || {};
251 const tl = gsap.timeline({ paused: true });
252 // GOTCHA: getTotalLength can read 0 before layout -> dash fails -> a solid
253 // line shows at t=0. Fall back to a constant AND gate with autoAlpha:0.
254 const lenA = ra.getTotalLength() || 1900,
255 lenB = rb.getTotalLength() || 1750,
256 lenC = conn.getTotalLength() || 220;
257 const full = CFG.mode === "full";
258 gsap.set("#wash", { autoAlpha: 0 });
259 gsap.set(ra, { strokeDasharray: lenA, strokeDashoffset: lenA, autoAlpha: 0 });
260 gsap.set(rb, { strokeDasharray: lenB, strokeDashoffset: lenB, autoAlpha: 0 });
261 gsap.set(conn, { strokeDasharray: lenC, strokeDashoffset: lenC, autoAlpha: 0 });
262 gsap.set("#callout", { autoAlpha: 0, scale: 0.6 });
263 gsap.set("#scan", { autoAlpha: 0 });
264 gsap.set(".tick", { autoAlpha: 0 });
265 if (!full) {
266 document.getElementById("conn").remove();
267 document.getElementById("callout").remove();
268 document.getElementById("scan").remove();
269 document.querySelectorAll(".tick").forEach((t) => t.remove());
270 }
271
272 tl.to("#wash", { autoAlpha: 1, duration: 0.9, ease: "power2.out" }, 0.6);
273 if (full) {
274 tl.to("#scan", { autoAlpha: 1, duration: 0.8 }, 0.7);
275 tl.to(".tick", { autoAlpha: 0.55, duration: 0.5, stagger: 0.06 }, 0.8);
276 }
277 tl.to(ra, { autoAlpha: 1, duration: 0.08 }, 1.3);
278 tl.to(ra, { strokeDashoffset: 0, duration: 0.6, ease: "power2.inOut" }, 1.3);
279 tl.to(rb, { autoAlpha: 1, duration: 0.08 }, 1.68);
280 tl.to(rb, { strokeDashoffset: 0, duration: 0.5, ease: "power2.inOut" }, 1.68);
281 if (full) {
282 tl.to(conn, { autoAlpha: 1, duration: 0.05 }, 2.55);
283 tl.to(conn, { strokeDashoffset: 0, duration: 0.35, ease: "power3.out" }, 2.6);
284 tl.to("#callout", { autoAlpha: 1, scale: 1, duration: 0.45, ease: "back.out(2.4)" }, 2.95);
285 }
286 window.__timelines["circle-highlight"] = tl;
287 tl.seek(0);
288 if (!window.__HYPERFRAMES_HOST__) {
289 const fit = () => {
290 const s = Math.min(innerWidth / CFG.W, innerHeight / CFG.H) * 0.96;
291 S.style.transform = `scale(${s})`;
292 S.style.transformOrigin = "center center";
293 S.style.position = "absolute";
294 S.style.left = "50%";
295 S.style.top = "50%";
296 S.style.marginLeft = -(CFG.W / 2) + "px";
297 S.style.marginTop = -(CFG.H / 2) + "px";
298 };
299 addEventListener("resize", fit);
300 fit();
301 tl.eventCallback("onComplete", () => setTimeout(() => tl.restart(), 1000));
302 tl.play();
303 }
304 </script>
305 </body>
306</html>