Setting the file. One moment. Index · Music To Video · heygen-com/hyperframes · Skills DocsGsap Min
(opens in a new tab)
references/templates/logo-split-lockup-pulse/index.html
HTML·596 lines·26 KB
12 {"id":"word3", "type":"string", "label":"Word 3 (optional)", "default":""},
13 {"id":"word4", "type":"string", "label":"Word 4 (optional)", "default":""}
14 ]'
15>
16 <head>
17 <meta charset="utf-8" />
18 <meta name="viewport" content="width=1920, height=1080" />
19 <title>Logo Split / Lockup Pulse — group_7 reference impl</title>
20 <script src="../../motion-primitives/assets/gsap.min.js"></script>
21 <!-- Velocity-driven directional motion blur (registry: motion-blur). Samples each
22 target's GSAP x/y per seek and applies a one-sided ghost trail + top blur
23 proportional to speed; clears at rest. This is what makes the fast mark moves
24 (split / snap) read SILKY instead of stiff. Self-contained (SVG filters). -->
25 <script>
26 (function () {
27 if (!window._hfMbUid) window._hfMbUid = 0;
28 window.attachMotionBlur = function (selector, tl, opts) {
29 opts = opts || {};
30 var blurScale = opts.blurScale !== undefined ? opts.blurScale : 0.008;
31 var blurMax = opts.blurMax !== undefined ? opts.blurMax : 20;
32 var stretchScale = opts.stretchScale !== undefined ? opts.stretchScale : 0.0002;
33 var stretchMax = opts.stretchMax !== undefined ? opts.stretchMax : 0;
34 var axis = opts.axis || "both";
35 var items = Array.isArray(selector) ? selector : [selector];
36 var targets = items.reduce(function (acc, s) {
37 if (typeof s === "string") {
38 document.querySelectorAll(s).forEach(function (el) {
39 acc.push(el);
40 });
41 } else if (s instanceof Element) {
42 acc.push(s);
43 }
44 return acc;
45 }, []);
46 var ns = "http://www.w3.org/2000/svg";
47 var GHOST_CFG = [
48 { frac: 0.25, slope: 0.55 },
49 { frac: 0.55, slope: 0.28 },
50 { frac: 1.0, slope: 0.1 },
51 ];
52 var state = targets.map(function (el) {
53 var uid = "hf-mb-" + window._hfMbUid++;
54 var svg = document.createElementNS(ns, "svg");
55 svg.setAttribute("style", "position:absolute;width:0;height:0;overflow:hidden;");
56 var filter = document.createElementNS(ns, "filter");
57 filter.id = uid;
58 filter.setAttribute("x", "-110%");
59 filter.setAttribute("y", "-25%");
60 filter.setAttribute("width", "260%");
61 filter.setAttribute("height", "150%");
62 var ghosts = GHOST_CFG.map(function (cfg, gi) {
63 var feOff = document.createElementNS(ns, "feOffset");
64 feOff.setAttribute("in", "SourceGraphic");
65 feOff.setAttribute("dx", "0");
66 feOff.setAttribute("dy", "0");
67 feOff.setAttribute("result", "go" + gi);
68 var feGB = document.createElementNS(ns, "feGaussianBlur");
69 feGB.setAttribute("in", "go" + gi);
70 feGB.setAttribute("stdDeviation", "0 0");
71 feGB.setAttribute("result", "gb" + gi);
72 var feCT = document.createElementNS(ns, "feComponentTransfer");
73 feCT.setAttribute("in", "gb" + gi);
74 feCT.setAttribute("result", "gf" + gi);
75 var feFuncA = document.createElementNS(ns, "feFuncA");
76 feFuncA.setAttribute("type", "linear");
77 feFuncA.setAttribute("slope", String(cfg.slope));
78 feCT.appendChild(feFuncA);
79 filter.appendChild(feOff);
80 filter.appendChild(feGB);
81 filter.appendChild(feCT);
82 return { feOff: feOff, feGB: feGB, frac: cfg.frac };
83 });
84 var feTopBlur = document.createElementNS(ns, "feGaussianBlur");
85 feTopBlur.setAttribute("in", "SourceGraphic");
86 feTopBlur.setAttribute("stdDeviation", "0 0");
87 feTopBlur.setAttribute("result", "top");
88 filter.appendChild(feTopBlur);
89 var feMerge = document.createElementNS(ns, "feMerge");
90 for (var mi = GHOST_CFG.length - 1; mi >= 0; mi--) {
91 var mn = document.createElementNS(ns, "feMergeNode");
92 mn.setAttribute("in", "gf" + mi);
93 feMerge.appendChild(mn);
94 }
95 var mnTop = document.createElementNS(ns, "feMergeNode");
96 mnTop.setAttribute("in", "top");
97 feMerge.appendChild(mnTop);
98 filter.appendChild(feMerge);
99 svg.appendChild(filter);
100 document.body.appendChild(svg);
101 return {
102 el: el,
103 ghosts: ghosts,
104 feTopBlur: feTopBlur,
105 filterId: uid,
106 prevX: parseFloat(gsap.getProperty(el, "x")) || 0,
107 prevY: parseFloat(gsap.getProperty(el, "y")) || 0,
108 prevTime: tl.time(),
109 };
110 });
111 var _proxy = { t: 0 };
112 tl.to(
113 _proxy,
114 {
115 t: 1,
116 duration: Math.max(tl.duration(), 0.1),
117 ease: "none",
118 onUpdate: function () {
119 var time = tl.time();
120 state.forEach(function (s) {
121 var x = parseFloat(gsap.getProperty(s.el, "x")) || 0;
122 var y = parseFloat(gsap.getProperty(s.el, "y")) || 0;
123 var dt = time - s.prevTime;
124 if (dt > 0.0005) {
125 var vx = axis !== "y" ? (x - s.prevX) / dt : 0;
126 var vy = axis !== "x" ? (y - s.prevY) / dt : 0;
127 var bx = Math.min(Math.abs(vx) * blurScale, blurMax);
128 var by = Math.min(Math.abs(vy) * blurScale, blurMax);
129 var bxFinal = axis !== "y" ? bx : Math.max(by * 0.08, 0.4);
130 var byFinal = axis !== "x" ? by : Math.max(bx * 0.08, 0.4);
131 var active = bx > 0.3 || by > 0.3;
132 if (active) {
133 s.el.style.filter = "url(#" + s.filterId + ")";
134 s.ghosts.forEach(function (g) {
135 var dx =
136 axis !== "y" ? (vx >= 0 ? -bxFinal * g.frac : bxFinal * g.frac) : 0;
137 var dy =
138 axis !== "x" ? (vy >= 0 ? -byFinal * g.frac : byFinal * g.frac) : 0;
139 g.feOff.setAttribute("dx", dx.toFixed(2));
140 g.feOff.setAttribute("dy", dy.toFixed(2));
141 var gbx =
142 axis !== "y"
143 ? (bxFinal * g.frac * 0.5).toFixed(2)
144 : Math.max(byFinal * g.frac * 0.04, 0.4).toFixed(2);
145 var gby =
146 axis !== "x"
147 ? (byFinal * g.frac * 0.5).toFixed(2)
148 : Math.max(bxFinal * g.frac * 0.04, 0.4).toFixed(2);
149 g.feGB.setAttribute("stdDeviation", gbx + " " + gby);
150 });
151 s.feTopBlur.setAttribute(
152 "stdDeviation",
153 (bxFinal * 0.15).toFixed(2) + " " + (byFinal * 0.15).toFixed(2),
154 );
155 } else {
156 s.el.style.filter = "";
157 s.ghosts.forEach(function (g) {
158 g.feOff.setAttribute("dx", "0");
159 g.feOff.setAttribute("dy", "0");
160 g.feGB.setAttribute("stdDeviation", "0 0");
161 });
162 s.feTopBlur.setAttribute("stdDeviation", "0 0");
163 }
164 s.prevX = x;
165 s.prevY = y;
166 s.prevTime = time;
167 } else if (dt < -0.0005) {
168 s.el.style.filter = "";
169 s.ghosts.forEach(function (g) {
170 g.feOff.setAttribute("dx", "0");
171 g.feOff.setAttribute("dy", "0");
172 g.feGB.setAttribute("stdDeviation", "0 0");
173 });
174 s.feTopBlur.setAttribute("stdDeviation", "0 0");
175 s.prevX = x;
176 s.prevY = y;
177 s.prevTime = time;
178 }
179 });
180 },
181 },
182 0,
183 );
184 };
185 })();
186 </script>
187 <style>
188 @font-face {
189 font-family: "Lockup";
190 src: url("./assets/fredoka-700.woff2") format("woff2");
191 font-weight: 700;
192 font-style: normal;
193 font-display: block;
194 }
195 *,
196 *::before,
197 *::after {
198 margin: 0;
199 padding: 0;
200 box-sizing: border-box;
201 }
202 html,
203 body {
204 background: var(--bg, #249b5f);
205 overflow: hidden;
206 }
207 #root {
208 position: relative;
209 width: 1920px;
210 height: 1080px;
211 overflow: hidden;
212 background: var(--bg, #249b5f);
213 display: flex;
214 align-items: center;
215 justify-content: center;
216 }
217 /* a 0-size anchor at screen center; marks + words are positioned absolutely
218 around it, and the whole group scales/translates for the pulse. */
219 #lockup {
220 position: relative;
221 width: 0;
222 height: 0;
223 }
224 .words {
225 position: absolute;
226 left: 0;
227 top: 0;
228 transform: translate(-50%, -50%);
229 display: flex;
230 align-items: center;
231 gap: 34px;
232 white-space: nowrap;
233 color: var(--text, #e7e3dd);
234 font-family: "Lockup", system-ui, sans-serif;
235 font-size: 200px;
236 font-weight: 700;
237 letter-spacing: -0.045em;
238 line-height: 0.8;
239 }
240 .word {
241 display: none; /* revealed one per onset on the timeline */
242 }
243 .mark {
244 position: absolute;
245 top: 0;
246 left: 0;
247 width: 300px;
248 height: 340px;
249 color: var(--mark, #e7e3dd);
250 }
251 .mark svg {
252 width: 100%;
253 height: 100%;
254 display: block;
255 overflow: visible;
256 }
257 /* edge/vertical centering is set in JS via GSAP xPercent/yPercent so it
258 composes with the animated x (a CSS % transform would be clobbered). */
259 </style>
260 </head>
261 <body>
262 <!-- duration = 2.875s = the green logo lifecycle (seed -> split -> lockup ->
263 snap -> pulse); the source then HARD-CUTS to the next group at f70 (2.917s). -->
264 <div
265 data-hf-id="hf-g7rt"
266 id="root"
267 data-composition-id="group-seven-logo"
268 data-width="1920"
269 data-height="1080"
270 data-start="0"
271 data-duration="2.875"
272 data-root="true"
273 >
274 <div data-hf-id="hf-g7lk" id="lockup">
275 <div data-hf-id="hf-g7ml" class="mark left" id="markLeft"></div>
276 <div data-hf-id="hf-g7wd" class="words" id="words"></div>
277 <div data-hf-id="hf-g7mr" class="mark right" id="markRight"></div>
278 </div>
279 </div>
280
281 <script>
282 (function () {
283 // ════════════════════════════════════════════════════════════════
284 // LOGO SPLIT / LOCKUP PULSE · structural_type: per_onset_sequential
285 // A two-part mark sits joined at center, SPLITS left<->right to open a
286 // gap, GROWS a center word-lockup one word per onset (key word on the
287 // surge), SNAP-CLOSES on a hit, then PULSES with the roll/hihat bed.
288 // Reversed from group_7 (Lille — World Design Capital 2020 sting).
289 // ════════════════════════════════════════════════════════════════
290
291 // ── 1. Content variables (what a user changes) ─────────────────────
292 var defaults = {
293 bgColor: "#249b5f",
294 markColor: "#e7e3dd",
295 textColor: "#e7e3dd",
296 leftMark: "",
297 rightMark: "",
298 word1: "lille",
299 word2: "2020",
300 word3: "",
301 word4: "",
302 };
303 var vars = Object.assign(
304 {},
305 defaults,
306 window.__hyperframes && window.__hyperframes.getVariables
307 ? window.__hyperframes.getVariables()
308 : {},
309 );
310
311 // ── 2. DEFAULT LOGO HALVES (line-art; swap via leftMark / rightMark) ─
312 // Either two related SVGs (a left + a right graphic) OR the two halves of
313 // one logo. currentColor inherits --mark. Thick rounded line-art so they
314 // read as a bold pair bracketing the words (matched to the Lille mark:
315 // a left radial SPARK with a hollow hub, a right CLOUD/SWIRL).
316 var DEFAULT_LEFT = [
317 // radial SPARK: 5 rounded strokes, a tight fan up-and-left from a hollow
318 // hub near the text (NOT converging to a needle — reads as a spark/shine).
319 // hub at the INNER (right) edge so the spark hugs the text; 5 long strokes
320 // fanning ~140 up-and-left — a big radial spark that helps wrap the lockup.
321 '<svg viewBox="0 0 150 150" fill="none" stroke="currentColor"',
322 ' stroke-width="16" stroke-linecap="round">',
323 '<line x1="134" y1="66" x2="112" y2="6"/>',
324 '<line x1="130" y1="72" x2="66" y2="16"/>',
325 '<line x1="126" y1="80" x2="24" y2="42"/>',
326 '<line x1="124" y1="88" x2="10" y2="84"/>',
327 '<line x1="126" y1="96" x2="36" y2="136"/>',
328 "</svg>",
329 ].join("");
330 var DEFAULT_RIGHT = [
331 // CLOUD / backwards-3 SWIRL: two bold stacked lobes bulging right with a
332 // left-pointing middle notch, opening to the left (the Lille cloud).
333 // bold backwards-3 swirl: opening tips at the INNER (left) edge so it hugs the
334 // text, bulging right (mass OUTER). Fills the box height to help wrap the lockup.
335 '<svg viewBox="0 0 150 150" fill="none" stroke="currentColor"',
336 ' stroke-width="16" stroke-linecap="round" stroke-linejoin="round">',
337 '<path d="M14 40',
338 " C50 6 128 14 122 58",
339 " C120 86 80 92 54 78",
340 " C86 86 128 96 122 128",
341 ' C116 154 40 158 14 120"/>',
342 "</svg>",
343 ].join("");
344
345 // ── 3. BEAT MAP (filled from <group>.audiomap.json — see README) ────
346 // group_7.audiomap.json: BPM 92.3. ABSOLUTE audio seconds.
347 // The marks move on only these DISCRETE anchors — NOT on the sustained hi-hat
348 // fill (f17-f86), which is rhythmic texture, not a motion driver. Left + right
349 // always move together (mirrored). After the snap the weld HOLDS dead still.
350 var ENTRY = 0.083; // f2 — joined bracket pair appears at center (perc onset)
351 var SPLIT = 0.25; // f6 — brackets fly apart to full width (perc onset)
352 var WORD_ONSETS = [0.25, 1.0]; // one per word; 1.0 = SURGE@1s (f24) carries the key word
353 var CLOSE = 2.083; // f50 — snap shut: words vanish + brackets weld back together
354 var DUR = 2.875; // f69 — hold, then the source hard-cuts to the next group (f70)
355
356 var MARK_PAD = 14; // gap between a mark's inner edge and the nearest word
357 var WORD_GAP = 34; // must match .words { gap }
358
359 // ── 4. Build content ───────────────────────────────────────────────
360 document.documentElement.style.setProperty("--bg", vars.bgColor);
361 document.documentElement.style.setProperty("--mark", vars.markColor);
362 document.documentElement.style.setProperty("--text", vars.textColor);
363
364 // Insert SVG markup as SANITIZED parsed nodes — never innerHTML, and never
365 // a raw appendChild. DOMParser builds the tree, then cleanSvg() hard-strips
366 // everything that isn't inert drawing: only allow-listed shape elements +
367 // presentation attributes survive, so <script>, <image>/<use>/<foreignObject>,
368 // javascript: hrefs and every on* handler are removed before the nodes ever
369 // enter the live document. A custom mark SVG therefore can't smuggle active
370 // content (CWE-79 DOM-XSS). Non-SVG / malformed input falls back to text.
371 var SVG_OK_TAGS = {
372 svg: 1,
373 g: 1,
374 path: 1,
375 line: 1,
376 polyline: 1,
377 polygon: 1,
378 rect: 1,
379 circle: 1,
380 ellipse: 1,
381 defs: 1,
382 lineargradient: 1,
383 radialgradient: 1,
384 stop: 1,
385 clippath: 1,
386 title: 1,
387 desc: 1,
388 text: 1,
389 tspan: 1,
390 };
391 var SVG_OK_ATTRS = {
392 viewbox: 1,
393 xmlns: 1,
394 width: 1,
395 height: 1,
396 fill: 1,
397 "fill-rule": 1,
398 "fill-opacity": 1,
399 stroke: 1,
400 "stroke-width": 1,
401 "stroke-linecap": 1,
402 "stroke-linejoin": 1,
403 "stroke-dasharray": 1,
404 "stroke-dashoffset": 1,
405 "stroke-opacity": 1,
406 opacity: 1,
407 d: 1,
408 x: 1,
409 y: 1,
410 x1: 1,
411 y1: 1,
412 x2: 1,
413 y2: 1,
414 cx: 1,
415 cy: 1,
416 r: 1,
417 rx: 1,
418 ry: 1,
419 points: 1,
420 transform: 1,
421 offset: 1,
422 "stop-color": 1,
423 "stop-opacity": 1,
424 gradientunits: 1,
425 gradienttransform: 1,
426 "clip-path": 1,
427 "clip-rule": 1,
428 class: 1,
429 id: 1,
430 };
431 function cleanSvg(node) {
432 var attrs = Array.prototype.slice.call(node.attributes || []);
433 for (var a = 0; a < attrs.length; a++) {
434 if (!SVG_OK_ATTRS[attrs[a].name.toLowerCase()]) node.removeAttribute(attrs[a].name);
435 }
436 var kids = Array.prototype.slice.call(node.childNodes);
437 for (var k = 0; k < kids.length; k++) {
438 var c = kids[k];
439 if (c.nodeType === 1) {
440 if (SVG_OK_TAGS[(c.localName || c.nodeName).toLowerCase()]) cleanSvg(c);
441 else node.removeChild(c);
442 } else if (c.nodeType !== 3) {
443 node.removeChild(c);
444 }
445 }
446 }
447 function setSvg(el, markup) {
448 el.textContent = "";
449 var doc = new DOMParser().parseFromString(String(markup), "image/svg+xml");
450 var root = doc.documentElement;
451 if (
452 !root ||
453 root.nodeName.toLowerCase() !== "svg" ||
454 doc.getElementsByTagName("parsererror").length
455 ) {
456 el.textContent = String(markup);
457 return;
458 }
459 cleanSvg(root);
460 el.appendChild(document.importNode(root, true));
461 }
462
463 var markLeft = document.getElementById("markLeft");
464 var markRight = document.getElementById("markRight");
465 setSvg(markLeft, (vars.leftMark && vars.leftMark.trim()) || DEFAULT_LEFT);
466 setSvg(markRight, (vars.rightMark && vars.rightMark.trim()) || DEFAULT_RIGHT);
467
468 var wordsEl = document.getElementById("words");
469 var wordList = [vars.word1, vars.word2, vars.word3, vars.word4].filter(function (w) {
470 return w && String(w).trim() !== "";
471 });
472 var wordEls = wordList.map(function (w) {
473 var s = document.createElement("span");
474 s.className = "word";
475 s.textContent = w;
476 wordsEl.appendChild(s);
477 return s;
478 });
479
480 // ── 5. Build the timeline once the lockup font is ready ─────────────
481 // The word widths drive how far the marks open, so they MUST be measured
482 // in the real (rounded) face, not a fallback. Gate the whole build on
483 // document.fonts so the measurement is correct; the render engine also
484 // waits for fonts before capturing.
485 function build() {
486 // measure each word in the loaded face, then hide them all so a hidden
487 // word occupies NO layout width (the lockup truly grows as words enter).
488 var widths = wordEls.map(function (el) {
489 el.style.display = "inline-block";
490 var w = el.offsetWidth;
491 el.style.display = "none";
492 return w;
493 });
494 function markOffsetFor(k) {
495 if (k <= 0) return 0;
496 var blockW = 0;
497 for (var i = 0; i < k; i++) blockW += widths[i];
498 blockW += WORD_GAP * (k - 1);
499 return blockW / 2 + MARK_PAD;
500 }
501
502 var OFF_FULL = markOffsetFor(wordEls.length); // open width (brackets the full lockup)
503 var OVL = 14; // joined position: welded brackets sit close (small center gap)
504
505 window.__timelines = window.__timelines || {};
506 var tl = gsap.timeline({ paused: true });
507
508 // start state: the two brackets JOINED at center (full-outside / empty-middle),
509 // invisible; words hidden. xPercent/yPercent center each mark on the anchor and
510 // COMPOSE with the animated x (a CSS % transform would be clobbered by GSAP x).
511 gsap.set(markLeft, { autoAlpha: 0, xPercent: -100, yPercent: -50, x: OVL, scale: 0.72 });
512 gsap.set(markRight, { autoAlpha: 0, xPercent: 0, yPercent: -50, x: -OVL, scale: 0.72 });
513 gsap.set("#lockup", { scale: 1, transformOrigin: "50% 50%" });
514
515 // ── MOTION = the few DISCRETE anchors only (the hi-hat bed is texture, not
516 // a motion driver). Left + right always move TOGETHER (mirrored). ──
517
518 // f2 ENTRY — the joined bracket pair BLOOMS in at center (smooth scale-in,
519 // not a 1-frame pop — the soft grow is what makes the start feel silky).
520 tl.to(
521 [markLeft, markRight],
522 { autoAlpha: 1, scale: 1, duration: 0.16, ease: "power2.out" },
523 ENTRY,
524 );
525
526 // f6 SPLIT — both brackets GLIDE apart together to the FULL lockup width in ONE
527 // move: a fast launch + long settle (expo.out), and the velocity motion blur
528 // (attached below) smooths the travel so it reads silky, not stiff. Then HOLD
529 // — words fill the reserved center space; the marks don't track each word.
530 tl.to(markLeft, { x: -OFF_FULL, duration: 0.3, ease: "expo.out" }, SPLIT);
531 tl.to(markRight, { x: OFF_FULL, duration: 0.3, ease: "expo.out" }, SPLIT);
532
533 // word reveals — each word eases in with a soft stretch on its onset (the key
534 // word on the SURGE). Smooth deceleration (power3.out), NO poppy overshoot.
535 wordEls.forEach(function (el, i) {
536 var t =
537 i < WORD_ONSETS.length
538 ? WORD_ONSETS[i]
539 : WORD_ONSETS[WORD_ONSETS.length - 1] + 0.3 * (i - WORD_ONSETS.length + 1);
540 tl.set(el, { display: "inline-block" }, t);
541 tl.fromTo(
542 el,
543 { autoAlpha: 0, scaleX: 0.58, scaleY: 1.09, transformOrigin: "50% 50%" },
544 { autoAlpha: 1, scaleX: 1, scaleY: 1, duration: 0.24, ease: "power3.out" },
545 t,
546 );
547 });
548
549 // f50 SNAP-CLOSE — 1 frame: words vanish + both brackets teleport back together
550 // (the teleport reads as an impact streak through the motion blur).
551 tl.set(wordEls, { autoAlpha: 0, display: "none" }, CLOSE);
552 tl.set(markLeft, { x: OVL }, CLOSE);
553 tl.set(markRight, { x: -OVL }, CLOSE);
554 tl.fromTo(
555 "#lockup",
556 { scale: 0.9 },
557 { scale: 1, duration: 0.22, ease: "power2.out" },
558 CLOSE,
559 );
560
561 // SEE-SAW BOUNCE — the welded unit does a damped vertical spring + a building
562 // rigid tilt (rotation: +deg = left-spark UP / right-cloud DOWN), beat-synced to
563 // the hi-hat hits after the snap: f51 slam-down (lowest), f55 up-rebound (highest),
564 // f58 smaller dip, f63 final flick — amplitude decays, ending tilted to the f70 cut.
565 var B1 = 2.125,
566 B2 = 2.292,
567 B3 = 2.417,
568 B4 = 2.625; // f51 f55 f58 f63
569 tl.set("#lockup", { y: 0, rotation: 0 }, CLOSE);
570 tl.to("#lockup", { y: 34, rotation: -3, duration: B1 - CLOSE, ease: "power2.in" }, CLOSE);
571 tl.to("#lockup", { y: -64, rotation: 10, duration: B2 - B1, ease: "power3.out" }, B1);
572 tl.to("#lockup", { y: -38, rotation: 6, duration: B3 - B2, ease: "power2.inOut" }, B2);
573 tl.to("#lockup", { y: -60, rotation: 10.5, duration: B4 - B3, ease: "power2.inOut" }, B3);
574 tl.to("#lockup", { y: -56, rotation: 10.5, duration: DUR - B4, ease: "sine.out" }, B4);
575
576 // pin duration, then attach velocity motion blur: the brackets (x: split + snap)
577 // and the welded group (y: the ending bounce). MUST be after all tweens.
578 tl.set(document.body, {}, DUR);
579 attachMotionBlur([markLeft, markRight], tl, { axis: "x", blurMax: 26, blurScale: 0.011 });
580 attachMotionBlur("#lockup", tl, { axis: "y", blurMax: 20, blurScale: 0.012 });
581
582 tl.seek(0);
583 window.__timelines["group-seven-logo"] = tl;
584 }
585
586 if (document.fonts && document.fonts.ready) {
587 document.fonts.load('700 200px "Lockup"').then(function () {
588 document.fonts.ready.then(build);
589 });
590 } else {
591 build();
592 }
593 })();
594 </script>
595 </body>
596</html>